c++ogre

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. SceneNodes 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 SceneNodes 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

Solution

  • 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.