ray ray - 3 months ago 11
C++ Question

How can the OGRE3D SceneManager really find *any* SceneNode?

TL;DR;



How can the
SceneManager
actually find any
SceneNode
regardless of where it happens to be in the graph when:


  1. The
    SceneManager::createSceneNode(...)
    method explicitly claims that the nodes created are not part of the graph?¹, and

  2. SceneNode
    s can independently create their own children without the
    SceneManager
    's knowledge?²



¹ The SM does not automatically turn the scene nodes it creats into children of other nodes (e.g. root); you have to manually call
addChild
on a node for that


² The client can simply write
sceneManager->getRootSceneNode()->createChildSceneNode("Child");
, and the SM wouldn't know about the new child's existence





Background



I was going over the source code in OGRE3D and came across the following piece of documentation on the
SceneManager
class (>> << emphasis added):

/** Retrieves a named SceneNode from the scene graph.
@remarks
If you chose to name a SceneNode as you created it, or if you
happened to make a note of the generated name, you can look it
up >>wherever it is in the scene graph<< using this method.
@note Throws an exception if the named instance does not exist
*/
virtual SceneNode* getSceneNode(const String& name) const;


When you look at the implementation, you see:

SceneNode* SceneManager::getSceneNode(const String& name) const
{
SceneNodeList::const_iterator i = mSceneNodes.find(name);

if (i == mSceneNodes.end())
{
OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND, "SceneNode '" + name + "' not found.",
"SceneManager::getSceneNode");
}

return i->second;
}


So far, so good. We can see that the SM searches for your requested
SceneNode
in its own
SceneNodeList
named
mSceneNodes
. The part I'm trying to figure out is that the documentation claims it can find a node "wherever it is in the scene graph". New
SceneNode
s are only added into the
mSceneNodes
list when using
SceneManager::createSceneNode(...)
. The documentation for the SM's
createSceneNode
method says (>> << emphasis added):

/** Creates an instance of a SceneNode with a given name.
@remarks
Note that this >>does not add the SceneNode to the scene hierarchy<<.
This method is for convenience, since it allows an instance to
be created for which the SceneManager is responsible for
allocating and releasing memory, which is convenient in complex
scenes.
@par
To include the returned SceneNode in the scene, use the addChild
method of the SceneNode which is to be it's parent.
@par
Note that this method takes a name parameter, which makes the node easier to
retrieve directly again later.
*/
virtual SceneNode* createSceneNode(const String& name);


At the same time, if you look at the
SceneNode
class, it has its own
createChild(const String& name, ...)
method, which clearly does not add its own children into the
SceneManager
's list, as shown below:

SceneNode* SceneNode::createChildSceneNode(const Vector3& inTranslate,
const Quaternion& inRotate)
{
return static_cast<SceneNode*>(this->createChild(inTranslate, inRotate));
}
//-----------------------------------------------------------------------
SceneNode* SceneNode::createChildSceneNode(const String& name, const Vector3& inTranslate,
const Quaternion& inRotate)
{
return static_cast<SceneNode*>(this->createChild(name, inTranslate, inRotate));
}


This means that if the client program says
node.createChildSceneNode(...);
, the
SceneManager
would not be aware of the existence of the new child node, AFAIK, so it'd never be able to find it.

I've been studying the source code for a while now and I've not found an answer to this question. I looked at the
BspSceneManager
and the
BspSceneNode
just to see if I could spot something else, but came up empty.




For the sake of completeness/reference, the most recent commit currently available in the master branch is:

commit 3b13abbdcce146b2813a6cc3bedf16d1d6084340
Author: mkultra333 <unknown>
Date: Sun May 8 19:31:39 2016 +0800

Answer

It's no wonder this part confuses you because it's part of over-OOP from more than a decade ago. Some like it, some hate it.

The answer however, is quite simple if once you know what to look for: The code for Node::createChild is the following:

Node* newNode = createChildImpl( sceneType );
//...
return newNode;

It actually delegates creation to createChildImpl (the "implementer"). This function is a pure virtual function, thus SceneNode must overload.

When we go to SceneNode::createChildImpl, we get:

Node* SceneNode::createChildImpl(const String& name)
{
    return mCreator->_createSceneNode( name );
}

mCreator is a SceneManager pointer variable. So there you go: The SceneManager does get informed when a SceneNode is created via createChildSceneNode.

Note however an implementation (e.g. BspSceneNode in BspSceneManager) may overload createChildImpl and not inform the SceneManager; or they may.

Placing a breakpoint inside SceneManager::_createSceneNode and checking out the callstack will save you a lot of headaches though.