Details
-
User Story
-
Resolution: Done
-
P1: Critical
-
None
-
None
Description
This epic collects all tasks related to making the engine fully dynamic in the sense that Q3DSGraphObjects can be dynamically added and removed at any time. Besides enabling a future public API that allows spawning nodes like models or layers at run time, this also constitutes the basis of a potential future integration into the editor application.
While property changes, for animatable properties at least, are handled already, this is not sufficient when the runtime is not merely used as a viewer: both arbitrary property changes and the apperance or disappearance of Q3DSGraphObjects needs to be handled as well. The system is modeled after the Qt Quick scenegraph, hence the goal is to have something similar to DirtyNodeAdded/Removed and friends. The way built-in and custom properties are handled may need some changes as well - the simple table of static getters/setters as currently used by the animation system is not sufficient then.
In the end this allows constructing or amending presentations programatically, in the Qt way.
Case 1: let's build a presentation from scratch in C++. This is what the editor would do, but it is not unreasonable to expose this to applications as well. (i.e. "Qt 3D Lite" is finally becoming a reality)
Q3SUipPresentation presentation; presentation.setPresentationWidth(800); presentation.setPresentationHeight(480); Q3DSScene *scene = presentation.newObject<Q3DSScene>("scene"); presentation.setScene(scene); Q3DSSlide *masterSlide = presentation.newObject<Q3DSSlide>("master"); Q3DSSlide *slide1 = presentation.newObject<Q3DSSlide>("slide1"); masterSlide->appendChildNode(slide1); Q3DSSlide *slide2 = presentation.newObject<Q3DSSlide>("slide2"); masterSlide->appendChildNode(slide2); presentation.setMasterSlide(masterSlide); // a scene is expected to have at least one layer as its child Q3DSLayerNode *layer1 = presentation.newObject<Q3DSLayerNode>("layer1"); // properties conveniently default a to a normal, full-size layer scene->appendChildNode(layer1); // each layer uses the first active camera encountered while walking depth-first Q3DSCameraNode *camera1 = presentation.newObject<Q3DSCameraNode>("camera1"); // Defaults to a perspective camera with fov 60, near/far 10/5000. This is // good as it is in many cases. camera1->setPosition(QVector3D(0, 0, -600)); layer1->appendChildNode(camera1); // let's have a light Q3DSLightNode *light1 = presentation.newObject<Q3DSLightNode>("light1"); // Defaults to a white directional light. layer1->appendChildNode(light1); Q3DSModelNode *model1 = presentation.newObject<Q3DSModelNode>("model1"); // A model needs a mesh. Meshes are retrieved via // Q3DSUipPresentation::mesh() which loads or returns a cached one. model1->setMesh(presentation.mesh(QLatin1String("#Cube"))); // let's use a built-in primitive layer1->appendChildNode(model1); // rotate the cube around the X and Y axes model1->setRotation(QVector3D(45, 30, 0)); Q3DSDefaultMaterial *mat1 = presentation.newObject<Q3DSDefaultMaterial>("mat1"); // defaults to a white material with no texture maps model1->appendChildNode(mat1); // now we have a white cube // associate objects with slides masterSlide->addObject(layer1); masterSlide->addObject(camera1); masterSlide->addObject(light1); // put model1 onto slide1, meaning it wont be visible on slide2 slide1->addObject(model1); slide1->addObject(mat1); // done, this is a full presentation with a layer, camera, a light and a cube
Case 1.1: It must be noted that the simple setters are not what the editor would use in many cases. It is rather expected to get the list of properties from the data model metadata.xml, and use Q3DSPropertyChange for setting values:
model1->notifyPropertyChanges({ model1->setPosition(QVector3D(1, 2, 3)) });
vs.
Q3DSPropertyChangeList cl { Q3DSPropertyChange::fromVariant("position", QVector3D(1, 2, 3) };
model1->applyPropertyChanges(cl);
model1->notifyPropertyChanges(cl);
Case 1.2: Image or other object references need some special care when using dynamic changes. Again, this is relevant mainly to the editor, the high level API is straightforward:
Q3DSImage *img1 = ... material->notifyPropertyChange({ material->setDiffuseMap(img1) }); vs. diffuseMapChange = Q3DSPropertyChange::fromVariant(QLatin1String("diffusemap"), QLatin1String("#id_of_some_image")); material->applyPropertyChanges({ diffuseMapChange }); material->resolveReferences(presentation); // !! material->notifyPropertyChanges({ diffuseMapChange });
Case 2: What applications would really be interested in: Mirroring what other 3D tools would do: Import assets (meshes, texture maps) and design the static parts of the scene in the editor, and then:
... load up a .uia/.uip ... grab the Q3DSUipPresentation from the Q3DSEngine // look up any object by id or name auto model = presentation.objectByName<Q3DSModelNode>("NameSetInTheEditor"); // change anything at any time: model->notifyPropertyChange({ model->setRotation(QVector3D(0, 0, 90)) }); // or spawn new objects: Q3DSModel *childModel = presentation.newObject<Q3DSModel>("model2"); childModel->setMesh(presentation.mesh("#Cube")); Q3DSDefaultMaterial *childModelMat = presentation.newObject<Q3DSDefaultMaterial>("mat2"); childModel->appendChildNode(childModelMat); model->appendChildNode(childModel); // magic, a new cube shows up
Which is pretty much what e.g. Unity3D does. (might be incorrect code, just shown here as the concept):
GameObject obj = GameObject.Find("model1"); obj.transform.Rotate(new Vector3(0, 0, 90)); GameObject newObj = (GameObject) Instantiate(prefab); newObj.transform.parent = obj.transform;
Finally, it is worth noting that the scope goes well beyond Q3DSUipPresentation. For instance, Q3DSEngine needs to be able to dynamically add and remove subpresentations at any time.