Adding a new actor

Actors are the visual elements of a Met.3D scene. For a full description of the actor class hierarchy, rendering interface, resource lifecycle, and shader loading, see Actors.

Choose a base class:

  • MActor (src/gxfw/mactor.h) — general base. Use for annotation, geometry, or any actor that does not read gridded data from the pipeline.

  • MNWPMultiVarActor (src/gxfw/nwpmultivaractor.h) — extends MActor with managed data variables. Use when the actor needs to load one or more NWP fields.

Important

Met.3D globs its source and resource files in CMake, so after adding a new .h/.cpp file (or an icon under resources/) you must clear the CMake cache and build files and re-run CMake for it to be compiled.

Deriving from MActor

MExampleActor (src/teaching/) is a minimal, heavily commented example actor. It is the best starting point for a new MActor-derived actor. Enable it with:

cmake -DTEACHING=ON ..

The example actor covers constructor boilerplate, property setup, shader loading, geometry upload, and rendering. Read through it alongside the notes below.

Required static methods

Every actor must implement four static methods used by the registration system:

static QString staticActorType()   { return "MyActor"; }   // session file key
static QString staticDisplayName() { return "My actor"; }  // Create actor menu
static QString staticIconPath()    { return ":/icons/actors/missing.png"; }
static QString staticSettingsID()  { return "MyActor"; }   // config group name

Constructor and boilerplate

The constructor takes the owning MSession and forwards it to the base class. Add the one-line getSettingsID() override, and wire up the actor’s identity at the top of the constructor before creating any properties:

explicit MMyActor(MSession *parent);
QString getSettingsID() override { return staticSettingsID(); }

MMyActor::MMyActor(MSession *parent)
    : MActor(parent)
{
    setActorType(staticActorType());
    setName(staticDisplayName());
    actorPropertiesSupGroup.setConfigGroup(getSettingsID());
    // ... create and configure properties ...
}

Important

staticActorType() and each property’s setConfigKey(...) are written into actor configuration and session files. Think carefully on how to name them and keep them stable, as changing them later on breaks compatibility with old configuration files.

Properties

Declare properties as member variables (MFloatProperty, MColorProperty, MEnumProperty, MBoolProperty, MPointFProperty, MButtonProperty, …), configure them in the constructor, and register them with actorPropertiesSupGroup.addSubProperty(). Property-change callbacks either call &MActor::emitActorChangedSignal (redraw only) or a custom slot that requests data, rebuilds geometry, and so on. See Property Framework for the property framework API.

Shaders

GLSL effect files (*.fx.glsl) live in src/glsl/. In initializeActorResources() acquire the (shared) program with glRM->generateEffectProgram(name, shader) and call reloadShaderEffects() only when it reports the program is new. reloadShaderEffects() performs the actual compilation. This method is also invoked on its own when shaders are reloaded from the UI, so keep the compilation logic there.

With the shader effect held as a member (std::shared_ptr<GL::MShaderEffect> shader;):

void MMyActor::initializeActorResources()
{
    MGLResourcesManager *glRM = MGLResourcesManager::getInstance();

    // Returns true if the program was newly created, false if a program
    // of that name already exists and can be reused.
    bool loadShaders = glRM->generateEffectProgram("myactor_shader", shader);

    if (loadShaders)
    {
        reloadShaderEffects();
    }
}

void MMyActor::reloadShaderEffects()
{
    // Register one progress bar task per shader that is compiled below.
    beginCompileShaders(1);

    compileShadersFromFileWithProgressDialog(
                shader, "src/glsl/myactor.fx.glsl");

    endCompileShaders();
}

See Actors → Shader loading for the full pattern.

Cleanup

Release GPU items you created in the destructor via MGLResourcesManager::getInstance()->releaseGPUItem(...) (e.g. vertex buffers), and delete any other owned objects. Shader programs obtained through generateEffectProgram() are owned and reference-counted by the resource manager. These do not have to be released in the destructor.

Render methods

Override whichever render method applies to your actor (see Actors → Rendering interface for when each is called):

  • renderToCurrentContext() — 3-D geometry in world space (lon/lat/pressure).

  • renderToCurrentFullScreenContext() — full-screen 2-D overlay; enable with setActorSupportsFullScreenVisualisation(true) in the constructor.

  • renderToCurrentContextUiLayer() — 2-D UI elements on top of the 3-D view.

Registration

In src/system/applicationconfiguration.cpp, MApplicationConfigurationManager::registerActorFactories():

#include "actors/myactor.h"

// inside MApplicationConfigurationManager::registerActorFactories():
MActorTypeRegistry::registerActorType<MMyActor>("Base");

Available groups: "Base", "Sections && volumes", "Atmospheric features", "Flow visualization", "Diagrams", "Annotation". Groups map to the context-menu categories in the Create actor dialog.

Deriving from MNWPMultiVarActor

MNWPMultiVarActor adds a managed list of MNWPActorVariable objects to MActor. The base class handles the Add variable / Delete variable UI, data requests, time/member synchronisation, and transfer function wiring. See Actors → MNWPMultiVarActor for a description of actor variables and the data flow.

MExampleMultiVarActor (src/teaching/, also enabled with -DTEACHING=ON) is the corresponding minimal, commented example for a data-driven actor. Use that actor as starting point, just as MExampleActor for a plain MActor.

Pure virtual methods

supportedLevelTypes() returns the level types the actor can handle:

QList<MVerticalLevelType> MMyActor::supportedLevelTypes()
{
    return { PRESSURE_LEVELS_3D, HYBRID_SIGMA_PRESSURE_3D,
             AUXILIARY_PRESSURE_3D, SINGLE_LEVEL };
}

createActorVariable() is a factory called whenever the user adds a variable. Return a new instance of the appropriate typed variable class:

MNWPActorVariable* MMyActor::createActorVariable(
        const MSelectableDataVariable& dataSource)
{
    auto* var = new MNWP3DVolumeActorVariable(this);
    var->dataSourceID  = dataSource.dataSourceID;
    var->levelType     = dataSource.levelType;
    var->variableName  = dataSource.variableName;
    return var;
}

Reacting to new data

Override dataFieldChangedEvent() to update your actor whenever a new data field arrives:

void MMyActor::dataFieldChangedEvent()
{
    // rebuild vertex buffers, recompute derived state, …
    emitActorChangedSignal(); // schedule a redraw
}

Reacting to variable list changes

Override these slots to update internal structures when variables are added, changed, or removed. Always call the base implementation first:

  • onAddActorVariable(MNWPActorVariable *var)

  • onChangeActorVariable(MNWPActorVariable *var)

  • onDeleteActorVariable(MNWPActorVariable *var)

Accessing data in the render method

Iterate over variables and guard every grid access with hasData():

void MMyActor::renderToCurrentContext(MSceneViewGLWidget *sceneView)
{
    for (MNWPActorVariable *baseVar : variables)
    {
        if (!baseVar->hasData()) continue;
        auto *var = static_cast<MNWP3DVolumeActorVariable*>(baseVar);

        // var->grid  — current MStructuredGrid
        // var->transferFunction  — active transfer function
        // … bind shader, upload grid to texture, draw …
    }
}

Registration is the same as for MActor. Add the include and factory call in applicationconfiguration.cpp.

Note

If you think your new actor would be useful to other Met.3D users, we would be happy to include it in the main codebase. Please consider submitting a merge request on GitLab.

Adding an icon

If you would like to create a new icon for your actor, make sure it is:

  • grayscale,

  • 112 × 112 pixels,

  • free of text, letters, and fine structures,

  • simple and conveying the functionality of the actor in an abstract way.

  • Line width should be 2px for normal lines and 3px for thicker lines.

We recommend creating the icon as a vector graphic using Inkscape and exporting it as a PNG with an optional alpha channel. The icons live in resources/icons/actors/. Add the PNG file there and update staticIconPath():

static QString staticIconPath() { return ":/icons/actors/myactor.png"; }

Everything under resources/ is compiled into the Qt resource automatically, in Met.3D there is no .qrc file to edit. When adding a new resource, you have to clear the CMake cache before re-building, otherwise the old resources are re-used.