template<UnsignedInt dimensions, class T>
Magnum::SceneGraph::Drawable class

Drawable.

Adds drawing functionality to the object. Each Drawable is part of some DrawableGroup and the whole group can be drawn with particular camera using Camera::draw().

Subclassing

The class is used via subclassing and implementing the draw() function. The simplest option is to do it via single inheritance. Example drawable object that draws a blue sphere:

typedef SceneGraph::Object<SceneGraph::MatrixTransformation3D> Object3D;
typedef SceneGraph::Scene<SceneGraph::MatrixTransformation3D> Scene3D;

class RedCubeDrawable: public SceneGraph::Drawable3D {
    public:
        explicit RedCubeDrawable(Object3D& object, SceneGraph::DrawableGroup3D* group):
            SceneGraph::Drawable3D{object, group}
        {
            _mesh = MeshTools::compile(Primitives::cubeSolid());
        }

    private:
        void draw(const Matrix4& transformationMatrix, SceneGraph::Camera3D& camera) override {
            using namespace Math::Literals;

            _shader.setDiffuseColor(0xa5c9ea_rgbf)
                .setTransformationMatrix(transformationMatrix)
                .setNormalMatrix(transformationMatrix.normalMatrix())
                .setProjectionMatrix(camera.projectionMatrix())
                .draw(_mesh);
        }

        GL::Mesh _mesh;
        Shaders::PhongGL _shader;
};

For brevity the class has its own shader and mesh instance and a hardcoded light position, usually you'll have them stored in a central location, shared by multiple objects, and pass only references around.

The transformationMatrix parameter in the draw() function contains transformation of the object (to which the drawable is attached) relative to camera. The camera contains the projection matrix. Some shaders (like the Shaders::PhongGL used in the snippet) have separate functions for setting transformation and projection matrix, but some (such as Shaders::FlatGL) have a single function to set composite transformation and projection matrix. In that case you need to combine the two matrices manually like in the following code. Some shaders might have additional requirements, see their respective documentation for details.

Shaders::FlatGL3D shader;
shader.setTransformationProjectionMatrix(
    camera.projectionMatrix()*transformationMatrix);

Drawing the scene

There is no way to just draw all the drawables in the scene, you need to create some drawable group and add the drawable objects to both the scene and the group. You can also use DrawableGroup::add() and DrawableGroup::remove() instead of passing the group in the constructor. If you don't need to change any properties of the drawable later, you can just "create and forget", the scene graph will take care of all memory management from there, deleting the drawable when the object is attached to is deleted.

Scene3D scene;
SceneGraph::DrawableGroup3D drawables;

Object3D* redCube = new Object3D{&scene};
(*redCube)
    .translate(Vector3::yAxis(-0.3f))
    .rotateX(30.0_degf);
new RedCubeDrawable{*redCube, &drawables};

// ...

The last thing you need is a camera attached to some object (thus using its transformation). With the camera and the drawable group you can perform drawing in your drawEvent() implementation. See Camera2D and Camera3D documentation for more information.

MyApplication::MyApplication(const Arguments& arguments): Platform::Application{arguments} {
    // ...

    _cameraObject = new Object3D{&_scene};
    _cameraObject->translate(Vector3::zAxis(5.0f));
    _camera = new SceneGraph::Camera3D(*_cameraObject);
    _camera->setProjectionMatrix(Matrix4::perspectiveProjection(35.0_degf, 1.0f, 0.001f, 100.0f));
}

void MyApplication::drawEvent() {
    _camera->draw(_drawables);

    // ...

    swapBuffers();
}

Using multiple inheritance

Besides single inheritance, it's also possible to derive your class from both Object and Drawable. This for example allows you to directly access object transformation and parent/child relationship from within the drawable object.

class RedCube: public Object3D, public SceneGraph::Drawable3D {
    public:
        explicit RedCube(Object3D* parent, SceneGraph::DrawableGroup3D* group):
            Object3D{parent}, SceneGraph::Drawable3D{*this, group}
        {
            _mesh = MeshTools::compile(Primitives::cubeSolid());
        }

    private:
        void draw(const Matrix4& transformationMatrix, SceneGraph::Camera3D& camera) override;

        GL::Mesh _mesh;
        Shaders::PhongGL _shader;
};

The draw() implementation is the same as in the above snippet. Note that, in contrast to the single inheritance case, where the Drawable constructor got the holder object, here we pass it *this, because this class is both the holder object and the drawable. Instantiating the drawable object is then done in a single step:

(new RedCube(&scene, &drawables))
    ->translate(Vector3::yAxis(-0.3f))
    .rotateX(30.0_degf);

Using multiple drawable groups to improve performance

You can organize your drawables to multiple groups to minimize OpenGL state changes — for example put all objects using the same shader, the same light setup etc into one group, then put all transparent into another and set common parameters once for whole group instead of setting them again in each draw() implementation. Example:

    // ...

    Shaders::PhongGL _shader;
    SceneGraph::DrawableGroup3D _phongObjects, _transparentObjects;
};

void MyApplication::drawEvent() {
    _shader.setProjectionMatrix(_camera->projectionMatrix())
           .setLightPositions({_lightPositionRelativeToCamera})
           .setLightColors({_lightColor})
           .setAmbientColor(_ambientColor);

    /* Each drawable sets only unique properties such as transformation matrix
       and diffuse color */
    _camera->draw(_phongObjects);

    GL::Renderer::enable(GL::Renderer::Feature::Blending);

    /* Also here */
    _camera->draw(_transparentObjects);

    GL::Renderer::disable(GL::Renderer::Feature::Blending);

    // ...
}

Custom draw order and object culling

By default all contents of a drawable group are drawn, in the order they were added. In some cases you may want to draw them in a different order (for example to have correctly sorted transparent objects) or draw just a subset (for example to cull invisible objects away). That can be achieved using Camera::drawableTransformations() in combination with Camera::draw(const std::vector<std::pair<std::reference_wrapper<Drawable<dimensions, T>>, MatrixTypeFor<dimensions, T>>>&). For example, to have the objects sorted back-to-front, apply std::sort() with a custom predicate on the drawable transformation list:

std::vector<std::pair<std::reference_wrapper<SceneGraph::Drawable3D>, Matrix4>>
    drawableTransformations = camera.drawableTransformations(drawableGroup);

std::sort(drawableTransformations.begin(), drawableTransformations.end(),
    [](const std::pair<std::reference_wrapper<SceneGraph::Drawable3D>, Matrix4>& a,
       const std::pair<std::reference_wrapper<SceneGraph::Drawable3D>, Matrix4>& b) {
        return a.second.translation().z() < b.second.translation().z();
    });

camera.draw(drawableTransformations);

Another use case is object-level culling — assuming each drawable instance provides an absolute AABB, one can calculate the transformations, cull them via e.g. Math::Intersection::rangeFrustum() and then pass the filtered vector to Camera::draw(). To be clear, this approach depends on AABBs provided as relative to world origin, the actual object transformations don't get used in any way except being passed to the draw function:

struct CullableDrawable3D: SceneGraph::Drawable3D {
    Range3D aabb; /* Relative to world origin */

    
};

/* Camera frustum relative to world origin */
auto frustum = Frustum::fromMatrix(camera.projectionMatrix()*camera.cameraMatrix());

/* Erase all items that don't pass the frustum check */
std::vector<std::pair<std::reference_wrapper<SceneGraph::Drawable3D>, Matrix4>>
    drawableTransformations = camera.drawableTransformations(drawableGroup);
drawableTransformations.erase(std::remove_if(
    drawableTransformations.begin(), drawableTransformations.end(),
    [&](const std::pair<std::reference_wrapper<SceneGraph::Drawable3D>, Matrix4>& a) {
        Range3D aabb = static_cast<CullableDrawable3D&>(a.first.get()).aabb;
        return !Math::Intersection::rangeFrustum(aabb, frustum);
    }),
    drawableTransformations.end());

/* Draw just the visible part */
camera.draw(drawableTransformations);

Explicit template specializations

The following specializations are explicitly compiled into SceneGraph library. For other specializations (e.g. using Double type) you have to use Drawable.hpp implementation file to avoid linker errors. See also Template headers and implementation files for more information.

Base classes

template<UnsignedInt dimensions, class Derived, class T>
class AbstractGroupedFeature<dimensions, Drawable<dimensions, T>, T>
Base for grouped features.

Constructors, destructors, conversion operators

Drawable(AbstractObject<dimensions, T>& object, DrawableGroup<dimensions, T>* drawables = nullptr) explicit
Constructor.

Public functions

auto drawables() -> DrawableGroup<dimensions, T>*
Group containing this drawable.
auto drawables() const -> const DrawableGroup<dimensions, T>*
void draw(const MatrixTypeFor<dimensions, T>& transformationMatrix, Camera<dimensions, T>& camera) pure virtual
Draw the object using given camera.

Function documentation

template<UnsignedInt dimensions, class T>
Magnum::SceneGraph::Drawable<dimensions, T>::Drawable(AbstractObject<dimensions, T>& object, DrawableGroup<dimensions, T>* drawables = nullptr) explicit

Constructor.

Parameters
object Object this drawable belongs to
drawables Group this drawable belongs to

Adds the feature to the object and also to the group, if specified. Otherwise you can use DrawableGroup::add().

template<UnsignedInt dimensions, class T>
DrawableGroup<dimensions, T>* Magnum::SceneGraph::Drawable<dimensions, T>::drawables()

Group containing this drawable.

If the drawable doesn't belong to any group, returns nullptr.

template<UnsignedInt dimensions, class T>
const DrawableGroup<dimensions, T>* Magnum::SceneGraph::Drawable<dimensions, T>::drawables() const

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

template<UnsignedInt dimensions, class T>
void Magnum::SceneGraph::Drawable<dimensions, T>::draw(const MatrixTypeFor<dimensions, T>& transformationMatrix, Camera<dimensions, T>& camera) pure virtual

Draw the object using given camera.

Parameters
transformationMatrix Object transformation relative to camera
camera Camera

Projection matrix can be retrieved from Camera::projectionMatrix().