Jump to content
  • Advertisement
Sign in to follow this  
matt77hias

Obtaining specializations from a scene graph

This topic is 390 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

14 minutes ago, matt77hias said:

If I load hierarchical models with sub models, I cannot use non-root nodes, because I cannot access them (unless I use my previous proposals). If a plane has some propeller attached to some other part, I cannot get the propellor.

I still don't understand, can you tell us what the purpose of all of this is? Why do you need to "use non-root nodes" or "get the propeller"? What is it you want to do with the nodes?

I just don't understand what it is you're trying to do with all of this. I don't have enough context to wrap my head around the problem. :(

Maybe I'm just missing something or misunderstanding what was written prior. In any case more information will really help. :)

Edited by Styves

Share this post


Link to post
Share on other sites
Advertisement

I'm going to try and limit the snark a bit here, but believe me, it's hard.

 

Matthias, what you're doing is basically reading us one page at a time of a mystery novel, and then asking us who the killer is. Except it isn't even consecutive pages, or even chronological pages. Just rip a sheet of paper out of the book at total random, read it aloud, and then see who gets the correct killer.

And you've read us only two pages so far.

We don't even know the title of the book or who the characters are. We can't help you find the killer.

Share this post


Link to post
Share on other sites

Ok. I will try to elaborate, though it is not that easy and clear for me as well. Stated differently, if the problem was 100% clear, I just solved it.

The most basic problem, I wanted to solve first is the ability to specify relative/local transformations between a parent and a child object. So I created a Transform class which only encapsulates such a single relative transformation. Next, I wanted to relate these local transformations to world space and I wanted the ability to hierarchically structure local transformations in such a way that changing a local transformation affects the world transformation of all direct and indirect children as well. So this is my scene graph. This is not some general graph describing some arbitrary node connections, but a specialized graph in the sense that a connection between two nodes is always associated with a (direct) relative transformation between those two nodes. The idea is based on the way Unity3D's editor organizes a scene: a scene can have multiple root nodes each having their own hierarchy and each node has at least a local transformation. This conceptually works for lights, cameras and models, but is less suitable for screen-space sprites since 3D transformations are not used for the latter. One still needs them, however, to be able to add child nodes to such sprites which unlike the sprites require the chain of transformation to and from world space. So I created a TransformNode class. Instances of this class constitute the scene graph. A TransformNode has a pointer to its parent node (which does not exist for root nodes) and its child nodes. Furthermore, the TransformNode contains a Transform specifying the local transformation (connection with its parent node or the world itself in case this is a root node). Since, I want to cache the transformation matrices and update them if they are dirty, child TransformNodes must be notified of changes to a Transform of one of their ancestor TransformNodes (to invalidate their matrices). Some callback mechanism can be added to the Transform class for this purpose, but this is not clean: the Transform class is finished and must be a general class that can be used as some utility in other projects as well. Furthermore, adding some listener for example results in less controllable traversals of the hierarchy. So I wrapped the Transform inside the TransformNode. This is also more convenient for the user: instead of dealing with Transforms and TransformNodes, it suffices to use the TransformNode, furthermore it is not even possible anymore to work with a Transform since this is completely hidden by the TransformNode. By wrapping the Transform, cached matrices of the descendants of a TransformNode can be invalidated in a straight forward breadth-first or depth-first traversal. Alternatively one can flush transformation matrices down the graph once per frame. The disadvantage of flushing is the inability to use the transformation matrices for scripting. My approach, however, always results in the correct and up-to-date transformations. For example: if you translate some parent model first, and then retrieve the world-to-object or object-to-world matrix of some child light, camera or model, you will get a matrix taking the re-positioning of the parent into account.

The first problem that arises, however, is what is a parent and child node for a TransformNode. It could be a TransformNode, but like I said the TransformNode (just as for a Transform) merely deals with a hierarchy of transformations. I do not want to add extra data for activating/deactivating nodes, for giving a name to a node. So instead of storing pointer to TransformNodes inside the TransformNode for constituting the hierarchy, I use some other class Node which owns a TransformNode. So implicitly, a hierarchy of TransformNodes exists though indirectly due to the Node. This has two advantages. First, I can access the TransformNode of a Node by just calling a method Node::GetTransform(),  which is cleaner and clearer than directly invoking transformation related functionality on Node itself. Second, I can use Nodes instead of TransformNodes for the propagation of data in my graph. For example: transformation matrices can be invalidated by notifying the TransformNode of each child Node, subtrees can be activated/deactivated by notifying each child Node.

The only tricky part in my Node<->TransformNode implementation is managing the hierarchy. Adding to, removing from and re-adding to a Node is supported. If Nodes are terminated (marked for deletion), the whole subtree will be terminated. If a copy constructor is invoked, a deep-copy of the whole subtree is performed and results in a new subtree (which has no connections with the previous subtree). So far we have some abstract Node class which is finished and is a general enough to be of use as some utility in other projects as well.

The Node class is derived by the SceneNode class which basically adds a name at the moment. I do not consider a human readable name some universal property of a Node, so this is not added to the Node. The SceneNode has ModelNode, CameraNode and LightNode subclass which provide functionality to add a Model, Camera and Light, respectively, which can all have their own hierarchies. For example a Camera is not the same as CameraNode. The Camera deals with the view-to-projection matrix and the camera lens. The CameraNode obviously deals with the Camera and its Transform as well as with additional fluff such as some view settings and a viewport transformation.

For the purposes of rendering, I do not work with the root Nodes or root SceneNodes but I need to distinguish Nodes till some certain specialization level and I want a fast way of iterating these. Therefore, my Scene class contains separate collections for all CameraNodes, all ModelNodes and all specializations of the LightNodes. The disadvantage of working with specialized lights, is the user's inability to define its own lights. On the other hand it is difficult to probably impossible to handle all lights in a similar fashion for a rasterizer compared to a ray tracer. Cameras on the other hand can be specialized by the user, since the Camera interface suffices to work with for rendering. So for example: I never need to distinguish between orthographic and perspective cameras and the user can define a fish eye camera himself that will be used appropriately while rendering.

For rendering purposes, I collect all active CameraNodes, ModelNodes and specialized LightNodes in some buffer which will be passed around for the different render passes. Note that I do not directly use the implicit hierarchies underneath them. There is in fact only one indirectional usage: getting the object-to-world and world-to-object transformation matrix. If one of these matrices is dirty, some child-to-ancestor propagation is possible.

But now there is another problem: on the one hand we have some hierarchy of Nodes and on the other hand we have some flattened collections of Nodes in the Scene. Only the latter will be rendered. For ease of use, I decided that all Nodes must be created by invoking some factory method on the Scene object. So if you want to create an OmniLightNode, you invoke Scene::Create< OmniLightNode >(name, args). This method will create the OmniLightNode and ensures the OmniLightNode is added to the collection of OmniLightNodes. When the user receives his OmniLightNode, that Node is a root Node. If he wants, he can add it to some other Node. Unfortunately, I do not allow or support the other way around: what if the user copies (i.e. deep copy) an existing OmniLightNode, he can not add it to the scene since I do not provide an interface for that. The first reason for not providing an interface is to avoid the need to check for duplicates in the collections of the Scene itself prior to adding. I use a vector and not a set since the vector is more cache coherent and faster to iterate sequentially. Furthermore, adding the same component twice is an exceptional stupid situation that should not have an impact on all additions. The second reason is actually the inability to do so without a visitor. You can create an OmniLightNode and a PerspectiveCamera without using the Scene and add the latter as a child of the former. Or you could invoke the Clone method on some existing Node which will create a deep copy of which the Scene is unaware. If you want to add this Node to the scene, you probably succeed for that specific Node but what about its descendants? They need to be added to the Scene as well and they need to be added to the appropriate collection. The only way this could be achieved is by using a visitor pattern?

But there is even another very similar problem. Lets assume we have some OBJ mesh of some windmill consisting of a base building and the blades. The mesh specifies that the blades are a child of the base building. When I perform a Scene::CreateModel(name, args), I create a ModelNode for the base building and the blades and I add the latter to the former. Both ModelNodes are added to the collections of the Scene and the root ModelNode is returned to the caller. The caller obtains a pointer to a ModelNode which has one ModelNode as a child (i.e. the blades), but how do you use the latter one? If the name is unique, I can query the ModelNode of the building and obtain a Node of the blades (this also requires the name to be present in the Node and not in the SceneNode). Since, I have a Node, I can obtain the TransformationNode and use it in some script for rotating the blades. But I do not have the ModelNode, I have a Node, so if I want to do something fancy such as changing the material, I cannot do that without casting or double dispatching (i.e. visitor).

So I hope this somehow makes a bit sense?

 

 

Edited by matt77hias

Share this post


Link to post
Share on other sites

You are massively over-complicating and over-engineering this to the point that even a huge set of paragraphs detailing it is still confusing.

3 hours ago, matt77hias said:

Stated differently, it the problem was 100% clear, I just solved it.

Now see, there's your real problem. You haven't figured out what the problem you're trying to solve is yet but you're already trying to solve it. This is a big red flag telling you to step back and break down what it is you're really trying to do.

Hint: you mentioned what you want to do in your first line of your first paragraph:

3 hours ago, matt77hias said:

The most basic problem, I wanted to solve first is the ability to specify relative/local transformations between a parent and a child object.

3 hours ago, matt77hias said:

For the purposes of rendering, I do not work with the root Nodes or root SceneNodes but I need to distinguish Nodes till some certain specialization level and I want a fast way of iterating these.

Basically what you seem to want to do want is store transformations separately from the objects in your scene so that you can propagate transformations from parents to children without having to structure the main objects in a matching hierarchy since they currently exist in flat lists for iteration purposes. As a result you need to link the transformations to their parent/children so that changes will be propagated to the child transforms and your objects need access to their corresponding transforms so they can manipulate or use them for rendering.

Hopefully I understood that correctly.

3 hours ago, matt77hias said:

The mesh specifies that the blades are a child of the base building. When I perform a Scene::CreateModel(name, args), I create a ModelNode for the base building and the blades and I add the latter to the former. Both ModelNodes are added to the collections of the Scene and the root ModelNode is returned to the caller. The caller obtains a pointer to a ModelNode which has one ModelNode as a child (i.e. the blades), but how do you use the latter one?

My first word of advice is to stop trying to literally represent your hierarchy through class/struct objects in code because it's one of the core causes of your current dilemma. Your nodes should not own their children independently. This is a violation of the single responsibility principle: your making your nodes responsible not only for their own logic but for managing their children as well.

All of your nodes should exist in the Scene Graph which has the responsibility of managing those nodes.

3 hours ago, matt77hias said:

If you want to add this Node to the scene, you probably succeed for that specific Node but what about its descendants? They need to be added to the Scene as well and they need to be added to the appropriate collection. The only way this could be achieved is by using a visitor pattern?

Stop right now if you're thinking about "visitor patterns" or other even more complicated OOP patterns as a potential solution for this problem, which itself is the result of bad design decision motivated by complicated OOP code. Moving forward with any of these patterns will only add more complexity and will cause you even more trouble in the long run. They aren't the solution to your problem.

Objects should be added to the scene at creation time when their type is known. Anything that needs the type should be done at time where the type is known.

Don't try to move from an abstract object to it's concrete form, this is usually a sign you're doing something wrong. About the only time I've ever seen this be ok is in UI code (working with Qt's qobject_cast) but even then it's rarely necessary.

3 hours ago, matt77hias said:

But I do not have the ModelNode, I have a Node, so if I want to do something fancy such as changing the material, I cannot do that without casting or double dispatching (i.e. visitor).

Why do you only have the abstract node in this case? If you need the concrete object, then get it directly in your script via something like GetModelNode(name) or something. I don't understand why you need to go through the abstract node to access it.

PS: I think you need to revise your naming scheme: what exactly is a node? You have "TransformNode" and "Node"/"ModelNode"/etc, but only one of them seems to be in this "scene graph" (TransformNode). As far as I understood your "TransformNode" is the object that is part of the hierarchy, so this owning "Node" object isn't really a node, it's just an object containing a TransformNode, right? Having two separate concepts of "Node" in the same paragraph is really adding to the confusion.

 

Edited by Styves

Share this post


Link to post
Share on other sites
1 hour ago, Styves said:

Basically what you seem to want to do want is store transformations separately from the objects in your scene so that you can propagate transformations from parents to children without having to structure the main objects in a matching hierarchy since they currently exist in flat lists for iteration purposes. As a result you need to link the transformations to their parent/children so that changes will be propagated to the child transforms and your objects need access to their corresponding transforms so they can manipulate or use them for rendering.

I use transforms in the following way

node_ptr->GetTransform()->SetTranslation(1,2,3); instead of node_ptr->SetTranslation(1,2,3);

Here, node_ptr is of type (const) Node* and node_ptr->GetTransform() is of type (const) TransformNode*. But that is conceptually pretty much the same: it has the same expressive power. The only reason for doing this is not polluting the Node interface with all these transformation related methods. But Nodes, SceneNodes (derives Node), CameraNodes (derives SceneNode), etc. all have access to their TransformNode. So the transformation is not separate from these objects, the functionality is just moved to a member variable.

So for example: Assume we have a car having four wheels each having one neon light attached. The car ModelNode has four wheel child ModelNodes and these have one neon child OmniLightNode. The Scene has a ModelNode collection containing the car ModelNode and the four wheel ModelNodes, and an OmniLightNode collection containing the four neon OmniLightNodes.

1 hour ago, Styves said:

Objects should be added to the scene at creation time when their type is known. Anything that needs the type should be done at time where the type is known.

But what should loading a model from file do? Return a vector containing all the generated ModelNodes instead of only returning a pointer to the root ModelNode? 

Edited by matt77hias

Share this post


Link to post
Share on other sites
On 11/12/2017 at 5:29 AM, matt77hias said:

SharedPtr< Transform > m_transform;
vector< SharedPtr< Node > > m_childs;

Didn't notice this on my first pass, but this smells like you're not really sure who owns the m_transform and m_childs (children?) data.  Why are these not unique_ptrs?  If anyone needs access you have the .get() accessor you can use but ownership is now explicit.
 

2 hours ago, matt77hias said:

But what should loading a model from file do? Return a vector containing all the generated ModelNodes instead of only returning a pointer to the root ModelNode? 

A bit off subject but it will help you in the long run... To answer your question, loading a model from a file should hand you back a block of memory you specified with the data held in it for a factory or function to process, see Hodgman's May 18th post here:

I would then say, that factory/function should return a vector containing all the generated ModelNodes, yes.

Share this post


Link to post
Share on other sites
25 minutes ago, Mike2343 said:

Didn't notice this on my first pass, but this smells like you're not really sure who owns the m_transform and m_childs (children?) data.  Why are these not unique_ptrs?  If anyone needs access you have the .get() accessor you can use but ownership is now explicit.

You are right, I should rethink the Transform member of TransformNode. I remember originally doing this, since I expose my TransformNode's internals to Node (which is a friend of TransformNode), but accessing a std::unique_ptr member is fine.

The childs are shared since these Nodes could be owned by at most one TransformNode, by a Scene and by some scripts as well. The scripts could work with a raw or weak pointer as well, but my factories inside Scene return a std::shared_ptr. I do this to allow adding this std::shared_ptr to the scene graph as well. But I could make the scene the only owner. The reason I did this was to make TransformNode some general class which makes sense on its own independent of the Scene.

Edited by matt77hias

Share this post


Link to post
Share on other sites
2 hours ago, matt77hias said:

Here, node_ptr is of type (const) Node* and node_ptr->GetTransform() is of type (const) TransformNode*. But that is conceptually pretty much the same: it has the same expressive power. The only reason for doing this is not polluting the Node interface with all these transformation related methods. But Nodes, SceneNodes (derives Node), CameraNodes (derives SceneNode), etc. all have access to their TransformNode. So the transformation is not separate from these objects, the functionality is just moved to a member variable.

Your "TransformNode" is not a node then, it's an element or component of a node. Just call it a Transform. ;)

Also I imagine each of these derived nodes live inside your Scene Graph directly as part of some abstract list, and the parent/child relationship is part of each Node. Do I have that right?

2 hours ago, matt77hias said:

But what should loading a model from file do? Return a vector containing all the generated ModelNodes instead of only returning a pointer to the root ModelNode? 

What does a call to load a model look like?

Share this post


Link to post
Share on other sites
15 minutes ago, Styves said:

Your "TransformNode" is not a node then, it's an element or component of a node. Just call it a Transform. ;)

I had already a Transform structure. So I was running out of names.

15 minutes ago, Styves said:

Also I imagine each of these derived nodes live inside your Scene Graph directly as part of some abstract list

Yes.

15 minutes ago, Styves said:

and the parent/child relationship is part of each Node.

Yes (indirectly though via the TransformNode member variable).

15 minutes ago, Styves said:

What does a call to load a model look like?

template<typename SceneNodeT , typename... ConstructorArgsT>
SharedPtr< SceneNodeT >  Create (const string &name, ConstructorArgsT &&... args)
 
SharedPtr< ModelNode >  CreateModel (const ModelDescriptor &desc) //returns the root

 

The reason I have two methods, is that the "args" are passed to the Camera/Model/Light constructor as part of the creation of a single CameraNode/ModelNode/LightNode. The ModelDescriptor, however, contains the specification for creating a complete hierarchy of ModelNodes (each having a Model).

Edited by matt77hias

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!