How to add custom scene node types? [SOLVED]

If you are a new Irrlicht Engine user, and have a newbie-question, this is the forum for you. You may also post general programming questions here.
Post Reply
oringe
Posts: 41
Joined: Tue Jul 05, 2011 7:06 am
Location: San Francisco

How to add custom scene node types? [SOLVED]

Post by oringe »

For solution see last post.
Problem:
I'm deriving my custom scene node from ISceneNode and have written my own serialize and deserialize methods which add the custom properties to the IAttributes object to be written into the .irr file when calling smgr->saveScene( ... );
So far so good, I see my custom properties in the xml .irr file, but how do I define the node type?
When I call smgr->loadScene() I can't get it to call my deserialize method because it doesn't recognize the node type.
I want to just override the virtual method: getNodeType( ) but the return type must be ESCENE_NODE_TYPE which is an enum of only irrlicht's built-in types.

I've looked at ISceneNodeFactory, and I'd like to implement its addSceneNode method, but how can I pass it a const c8* typeName?
Must I use a UserDataSerializer, and just find my nodes by name, and leave the type ESNT_UNKNOWN or blank "" in the .irr file?
Last edited by oringe on Sun Jan 27, 2013 6:42 pm, edited 1 time in total.
chronologicaldot
Competition winner
Posts: 688
Joined: Mon Sep 10, 2012 8:51 am

Re: How to add custom scene node types?

Post by chronologicaldot »

If I understand your situation correctly, I believe casting an integer was the suggestion I've heard before for this sort of situation. See my example.

Code: Select all

 
getNodeType()
{
return (ESCENE_NODE_TYPE)(ESNT_COUNT + 1);
}
 
It seems invalid (and it could be if this were C++11 :p) but it works.

Now you need to implement a SceneNodeFactory and add it to the scene manager's list of factories using:
ISceneManager::registerSceneNodeFactory();

The factory itself contains two functions for adding scene nodes, one function that takes the type ESCENE_NODE_TYPE, which you can either have respond to ESNT_COUNT+1 or not, and one function that takes the name as a string, which you'd probably prefer.

I'd like to help you more, but I don't think I fully understand your problem.
oringe
Posts: 41
Joined: Tue Jul 05, 2011 7:06 am
Location: San Francisco

Re: How to add custom scene node types?

Post by oringe »

Thanks, chrono

Yeah, I've used that technique before, just casting an integer might work. But I'll try to clarify my problem.
I want to use smgr->saveScene and loadScene to work with my scene. But I have custom classes that I've derived from ISceneNode.
When the scene manager is loading a .irr file, ( CSceneManager.cpp line2178 )
it calls: SceneLoaderList->loadScene(file, userDataSerializer, rootNode);
which in turn calls: CSceneLoaderIrr::loadScene(...
which in turn calls:

Code: Select all

 
void CSceneLoaderIrr::readSceneNode(io::IXMLReader* reader, ISceneNode* parent,
    ISceneUserDataSerializer* userDataSerializer)
{
    scene::ISceneNode* node = 0;
 
    if (!parent && IRR_XML_FORMAT_SCENE==reader->getNodeName())
        node = SceneManager->getRootSceneNode();
    else if (parent && IRR_XML_FORMAT_NODE==reader->getNodeName())
    {
        // find node type and create it
        core::stringc attrName = reader->getAttributeValue(IRR_XML_FORMAT_NODE_ATTR_TYPE.c_str());
 
        node = SceneManager->addSceneNode(attrName.c_str(), parent);
 
        if (!node)
            os::Printer::log("Could not create scene node of unknown type", attrName.c_str());
    }
    else
        node=parent;
 
    // read attributes
    while(reader->read())
    {
        bool endreached = false;
 
        const wchar_t* name = reader->getNodeName();
 
        switch (reader->getNodeType())
        {
        case io::EXN_ELEMENT_END:
            if ((IRR_XML_FORMAT_NODE  == name) ||
                (IRR_XML_FORMAT_SCENE == name))
            {
                endreached = true;
            }
            break;
        case io::EXN_ELEMENT:
            if (IRR_XML_FORMAT_ATTRIBUTES == name)
            {
                // read attributes
                io::IAttributes* attr = FileSystem->createEmptyAttributes(SceneManager->getVideoDriver());
                attr->read(reader, true);
 
                if (node)
                    node->deserializeAttributes(attr);
 
                attr->drop();
            }
            else
            if (IRR_XML_FORMAT_MATERIALS == name)
                readMaterials(reader, node);
            else
            if (IRR_XML_FORMAT_ANIMATORS == name)
                readAnimators(reader, node);
            else
            if (IRR_XML_FORMAT_USERDATA  == name)
                readUserData(reader, node, userDataSerializer);
            else
            if ((IRR_XML_FORMAT_NODE  == name) ||
                (IRR_XML_FORMAT_SCENE == name))
            {
                readSceneNode(reader, node, userDataSerializer);
            }
            else
            {
                os::Printer::log("Found unknown element in irrlicht scene file",
                        core::stringc(name).c_str());
            }
            break;
        default:
            break;
        }
 
        if (endreached)
            break;
    }
    if (node && userDataSerializer)
        userDataSerializer->OnCreateNode(node);
}
 
This is the relevant function. You can see that the node is created based on the type read from the .irr file.

Code: Select all

    // find node type and create it
        core::stringc attrName = reader->getAttributeValue(IRR_XML_FORMAT_NODE_ATTR_TYPE.c_str());
 
        node = SceneManager->addSceneNode(attrName.c_str(), parent);
 
        if (!node)
            os::Printer::log("Could not create scene node of unknown type", attrName.c_str());
So even if I implemented my own ISceneNodeFactory, and implemented addSceneNode method, how do I get my custom type to pass?
For example, I have
class myQuadPlane : public ISceneNode
I want to directly instantiate a new myQuadPlane upon loading a scene, not create an ISceneNode first,
and then deal with the user data attributes associated with that node as an afterthought.

Furthermore, how do get the saveScene function to write my custom type (even if it is just an int) to the xml file?
I've implemented the serializeAttributes method in my class, but that only adds attributes to the body of the node:

Code: Select all

    <node type="">
        <attributes>
            <string name="Name" value="kxQuadPlane" />
            <int name="Id" value="111" />
            <vector3d name="Position" value="-12.800000, 0.000000, -12.800000" />
            <vector3d name="Rotation" value="90.000000, 0.000000, 0.000000" />
            <vector3d name="Scale" value="1.000000, 1.000000, 1.000000" />
            <bool name="Visible" value="true" />
            <int name="AutomaticCulling" value="1" />
            <int name="DebugDataVisible" value="0" />
            <bool name="IsDebugObject" value="false" />
                    // my attrib
            <float name="QuadSizeWidth" value="0.400000" />
            <float name="QuadSizeHeight" value="0.400000" />
            <int name="QuadCountWidth" value="64" />
            <int name="QuadCountHeight" value="31" />
            <string name="ImgPath" value="./textures/bricks4.png" />
        </attributes>
oringe
Posts: 41
Joined: Tue Jul 05, 2011 7:06 am
Location: San Francisco

Re: How to add custom scene node types? [SOLVED]

Post by oringe »

Solution:
Implement the ISceneNodeFactory interface.

I just copy and pasted from CDefaultSceneNodeFactory.h and .cpp,
and changed a few things:
obviously changed the name, (my)SceneNodeFactory
filled the (Supported)SceneNodeTypes array with my 'casted' custom types like this:

Code: Select all

    kxSceneNodeTypes.push_back( SSceneNodeTypePair( (ESCENE_NODE_TYPE) KSNT_QUAD_PLANE, "kxQuadPlane" ));
And then just rewrote the addSceneNode method to instantiate my node types with their corresponding classes :)

Code: Select all

//! adds a scene node to the scene graph based on its type id
ISceneNode* kxSceneNodeFactory::addSceneNode( ESCENE_NODE_TYPE type, ISceneNode* parent )
{
    switch( type )
    {
    case KSNT_QUAD_PLANE:
        return (ISceneNode*) new kxQuadPlane( Manager->getRootSceneNode(), Manager );
        case THE_REST_OF_YOUR_CUSTOM_TYPES:
                etc...
    default:
        break;
    }
    return 0;
}
 
Also don't forget in the custom class to cast the custom type to ESCENE_NODE_TYPE for the getType method:

Code: Select all

    virtual ESCENE_NODE_TYPE getType() const { return (ESCENE_NODE_TYPE) KSNT_QUAD_PLANE; }
Now I can call smgr->save/loadScene and my custom nodes get written/read properly to the .irr XML file :)
Post Reply