Advanced Scene Manipulation via the Qt 3D Studio Runtime Private C++ Classes | Runtime 2.1

Qt 3D Studio | Runtime | Advanced Scene Manipulation via the Qt 3D Studio Runtime Private C++ Classes

Qt 3D Studio Runtime 2.1 Manual

# Advanced Scene Manipulation via the Qt 3D Studio Runtime Private C++ Classes

In addition to the Qt 3D Studio Runtime C++ Classes}{public C++ API}, applications needing direct access to the scenegraph of the presentations loaded from `.uip` files can use the runtime's private C++ classes.

Note: These classes offer no source or binary compatibility guarantees.

Note: There is no documentation provided for the private classes. Refer to the examples, this page, and the header files instead.

To link against the module, add this line to your qmake `.pro` file:

      QT += 3dstudioruntime2-private

Then add the following include statement to the application sources:

      #include <private/q3dsengine_p.h>
      #include <private/q3dsuippresentation_p.h>

To access the Q3DSEngine instance, call Q3DSPresentation::engine(). This applies also to Qt Quick applications: the Presentation item in the QML code is an instance of a subclass of Q3DSPresentation, so once it gets passed to C++ code, it can be used as a Q3DSPresentation. Accessing the engine is only possible when a presentation is loaded. If the code needing access to the scene is not invoked by some user interaction but rather has to be done at startup, connect the execution of the C++ logic to a signal like Studio3D::presentationLoaded().

Check out the scenemanip example for a demonstration of manipulating the Qt 3D Studio scene from C++ in a Qt Quick application.

The next step is to get access to the Qt 3D Studio object tree. Query the Q3DSUipPresentation via Q3DSEngine::presentation(). There is a separate object tree for each subpresentation. Q3DSEngine::presentationCount() allows iterating through all of them:

      int presentationCount() const;
      Q3DSUipPresentation *presentation(int index = 0) const;
      Q3DSUipPresentation *presentationByName(const QString &name) const;

The Q3DSUipPresentation instance owns two object trees: one for the scene (that maps mostly 1:1 to the scene, layer, camera, light, model, etc. tree in the left side of the Timline in the Qt 3D Studio editor) and one for the slides.

To access these, use the following accessors:

      Q3DSScene *scene() const;
      Q3DSSlide *masterSlide() const;

Support for dynamically manipulating the slide structure is limited as of now. Certain related operations, like adding or removing animation tracks at runtime is also limited for the time being. For the rest of this document, we will focus on the scene itself.

The scenegraph is quite similar to the Qt Quick scenegraph and QSGNode when it comes to the management of child and sibling nodes. Adding and removing child nodes happens using a very similar API. The equivalent of QSGNode in the Qt 3D Studio world is Q3DSGraphObject. For application purposes the most important member functions are:

      Type type() const
      Q3DSGraphObject *parent() const
      Q3DSGraphObject *firstChild() const
      Q3DSGraphObject *lastChild() const
      Q3DSGraphObject *nextSibling() const
      Q3DSGraphObject *previousSibling() const
      int childCount() const;
      Q3DSGraphObject *childAtIndex(int idx) const;
      void removeChildNode(Q3DSGraphObject *node);
      void removeAllChildNodes();
      void prependChildNode(Q3DSGraphObject *node);
      void appendChildNode(Q3DSGraphObject *node);
      void insertChildNodeBefore(Q3DSGraphObject *node, Q3DSGraphObject *before);
      void insertChildNodeAfter(Q3DSGraphObject *node, Q3DSGraphObject *after);
      void reparentChildNodesTo(Q3DSGraphObject *newParent);

      virtual void applyPropertyChanges(const Q3DSPropertyChangeList &changeList);
      void notifyPropertyChanges(const Q3DSPropertyChangeList &changeList, int changeFlags = -1);

      QVector<QByteArray> propertyNames() const;
      QVector<QVariant> propertyValues() const;

      QVariant property(const char *name);
      bool setProperty(const char *name, const QVariant &v);
      QVector<QByteArray> dynamicPropertyNames() const;
      QVector<QVariant> dynamicPropertyValues() const;

      QVariantMap dynamicProperties() const;
      void clearDynamicProperties();
      Q3DSPropertyChangeList applyDynamicProperties(const QVariantMap &v);

Q3DSLayerNode, Q3DSCameraNode, Q3DSLightNode, Q3DSModelNode, Q3DSGroupNode, Q3DSTextNode are all subclasses of Q3DSNode which in turn is a Q3DSGraphObject. The common base (Q3DSNode) provides common properties like the transformation (position, rotation, scale).

To change a property at runtime, first find out the correct name for the property from the Scene Object Attribute List.

Then do either

      node->notifyPropertyChanges({ node->setPosition(QVector3D(1.0f, 0.5f, 3.0f)) });

or

      Q3DSPropertyChangeList cl { Q3DSPropertyChange::fromVariant("position", QVector3D(1.0f, 0.5f, 3.0f) };
      node->applyPropertyChanges(cl);
      node->notifyPropertyChanges(cl);

The former is more efficient, while the latter allows constructing change lists dynamically with the property names given as strings instead of having to call C++ setter functions.

To get access to an object, for example, the Q3DSModelNode corresponding to a Model in the editor, use the Q3DSUipPresentation's object() or objectByName() functions. The latter is more useful for applications since it works based on the Name (that is defined by the designer in the editor), while the former uses the object id that is unique, but invisible to the designer in the editor.

Note: Names do not have to be unique. This can cause confusion because some names the editor assigns by default are shared between multiple objects. To prevent this, rename the objects in the tree view to something unique.

      auto car = presentation->objectByName<Q3DSModelNode>(QLatin1String("FancySportsCar"));

Besides changing properties, adding or removing objects at runtime is also possible. This will lead to the corresponding graphics objects appearing or disappearing. For example, to spawn a new model object with the built-in cube as its mesh:

      Q3DSModelNode *cube = presentation->newObject<Q3DSModelNode>(QByteArrayLiteral("newCube"));
      cube->setMesh(QLatin1String("#Cube"));
      Q3DSDefaultMaterial *cubeMat = presentation->newObject<Q3DSDefaultMaterial>(QByteArrayLiteral("cubeMaterial"));
      cube->appendChildNode(cubeMat);
      group->appendChildNode(cube);

appendChildNode() takes ownership and inserts the given object in the parent's child list. This is similar to QObject::setParent() but note that QSGNode and Q3DSGraphObject are more powerful since they allow not just appending but also inserting at arbitrary positions in the child list.

When a newly created object gets added to a parent that itself is already part of a hierarchy under a Q3DSScene, the object becomes active, which triggers creating the graphics and 3D resources that are necessary to render the object in the associated layer's texture. In the above example this point is the last appendChildNode() call, assuming `group` is an object already parented into a live Q3DSScene tree.

To properly disconnect an object from the scene, use Q3DSUipPresentation::unlinkObject():

      presentation->unlinkObject(cube);
      delete cube;

Due to the unlinkObject() call, the cube will disappear from the screen. Note that the Q3DSDefaultMaterial child object is unlinked and destroyed as well, just like a child QObject is destroyed together with its parent.

Being part of the scenegraph is not enough to get displayed, however. The concept of slides has a great effect on visibility as well. To associate a newly created object with a slide, call Q3DSSlide::addObject(). For example, assuming the `.uip` file loaded has one slide under the master slide:

      Q3DSSlide *slide = static_cast<Q3DSSlide *>(presentation->masterSlide()->firstChild());
      slide->addObject(cubeMat);
      slide->addObject(cube);

The children of the new object have to be managed manually here, hence the separate addObject(cubeMat) call. It is not required that all descendants of an object belong to the same slide as the object.

When removing objects, make sure to disconnect them from the slide as well. To do this recursively for all objects in the subtree that is about to removed, one can do the following:

      Q3DSUipPresentation::forAllObjectsInSubTree(cube, [slide](Q3DSGraphObject *obj) {
          slide->removeObject(obj);
      });

There are many more possibilities with the Q3DSGraphObject API. Refer to the header files for more insight. It is worth nothing that it is also possible to build Qt 3D Studio scenes completely from C++, without using any `.uip` files. Check the demo application under `tests/manual/standalone` in the runtime's source tree for an example of creating and managing interactive scenes this way. It is also a good example for performing advanced tasks via the private API, like reacting on input events, providing custom mesh data, and dynamically generating texture maps.