Multiple SceneManager support for SceneNodeFactory

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
Thulsa Doom
Posts: 63
Joined: Thu Aug 05, 2004 9:40 am
Location: Germany

Multiple SceneManager support for SceneNodeFactory

Post by Thulsa Doom »

There have been several threads in this forum dealing with
multiple SceneManagers e.g to use with multiple viewports.

Also a scene node factory works fine in a single SceneManager design,
there's a problem using more than one SceneManager,
as each newly created SceneManager by

ISceneManager::createNewSceneManager(bool cloneContent=false)

is not aware of the factory.

Also there is a workaround to the problem
creating two instances of the factory and pass to each SceneManager
a pointer to it's own factory:

ISceneNodeFactory*
Factory1 = new CSceneNodeFactory(device->getSceneManager());
device->getSceneManager()->registerSceneNodeFactory(Factory1);
Factory1->drop();

ISceneNodeFactory*
Factory2 = new CSceneNodeFactory(device->getSceneManager());
device->getSceneManager()->createNewSceneManager()->registerSceneNodeFactory(Factory2);
Factory2->drop();

There is the drawback that it would waste double memory
for the second factory.

The other approch creating only one instance of the factory for
both SceneManagers

Code: Select all

	ISceneNodeFactory*
		Factory = new CSceneNodeFactory(); //will not compile
	

	device->getSceneManager()->registerSceneNodeFactory(Factory);
	device->getSceneManager()->createNewSceneManager()->registerSceneNodeFactory(Factory);
		Factory->drop(); 
will not compile within the given framework.
The reason is, that the factory stores a fixed pointer to
it's hosting SceneManager, as it's passed by the constructor.

However, for managing a SceneNodeFactory with multiple
SceneManagers I made following minimal invasive changes to the engine:

ISceneNodeFactory.h

Code: Select all

// Copyright (C) 2002-2007 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#ifndef __I_SCENE_NODE_FACTORY_H_INCLUDED__
#define __I_SCENE_NODE_FACTORY_H_INCLUDED__

#include "IReferenceCounted.h"
#include "ESceneNodeTypes.h"

namespace irr
{


namespace scene
{
	class ISceneNode;
	// ADDED: forward declarations
	class ISceneManager;	// ADDED
	class CSceneManager;	// ADDED

	class ISceneNodeFactory : public virtual IReferenceCounted
	{
	// ADDED: let the scene manager class have access to the protected method.
	friend CSceneManager;

	public:

		virtual ~ISceneNodeFactory() {}
		virtual ISceneNode* addSceneNode(ESCENE_NODE_TYPE type, ISceneNode* parent=0) = 0;
		virtual ISceneNode* addSceneNode(const c8* typeName, ISceneNode* parent=0) = 0;
		virtual u32 getCreatableSceneNodeTypeCount() const = 0;
		virtual ESCENE_NODE_TYPE getCreateableSceneNodeType(u32 idx) const = 0;
		virtual const c8* getCreateableSceneNodeTypeName(u32 idx) const = 0;
		virtual const c8* getCreateableSceneNodeTypeName(ESCENE_NODE_TYPE type) const = 0;

	protected:

		//! ADDED: let the scene manager class store it's pointer.
		virtual void setSceneManager(ISceneManager* mgr)  = 0;


	};


} // end namespace scene
} // end namespace irr

#endif
As the CDefaultFactory inherits from the interface one has
to change this class too:

CDefaultSceneNodeFactory.h

Code: Select all

// Copyright (C) 2002-2007 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#ifndef __C_DEFAULT_SCENE_NODE_FACTORY_H_INCLUDED__
#define __C_DEFAULT_SCENE_NODE_FACTORY_H_INCLUDED__

#include "ISceneNodeFactory.h"
#include "irrArray.h"
#include "irrString.h"

namespace irr
{
namespace scene
{
	class ISceneNode;
	// DELETED: forward declaration is included now already thru the interface
	// class ISceneManager;

	class CDefaultSceneNodeFactory : public ISceneNodeFactory
	{
	public:

		// ADDED: new constructor taking void
		CDefaultSceneNodeFactory(void);
		// DELETED: old ctor taking pointer of the scene manager
		//CDefaultSceneNodeFactory(ISceneManager* mgr);
		virtual ISceneNode* addSceneNode(ESCENE_NODE_TYPE type, ISceneNode* parent=0);
		virtual ISceneNode* addSceneNode(const c8* typeName, ISceneNode* parent=0);
		virtual u32 getCreatableSceneNodeTypeCount() const;
		virtual const c8* getCreateableSceneNodeTypeName(u32 idx) const;
		virtual ESCENE_NODE_TYPE getCreateableSceneNodeType(u32 idx) const;
		virtual const c8* getCreateableSceneNodeTypeName(ESCENE_NODE_TYPE type) const;

	protected:
		
		//! ADDED: implementation of interface method.
		virtual void setSceneManager(ISceneManager* mgr);


	private:

		ESCENE_NODE_TYPE getTypeFromName(const c8* name) const;

		struct SSceneNodeTypePair
		{
			SSceneNodeTypePair(ESCENE_NODE_TYPE type, const c8* name)
				: Type(type), TypeName(name)
			{}

			ESCENE_NODE_TYPE Type;
			core::stringc TypeName;
		};

		core::array<SSceneNodeTypePair> SupportedSceneNodeTypes;

		ISceneManager* Manager;
	};


} // end namespace scene
} // end namespace irr

#endif
With the implementation of the functions:

CDefaultSceneNodeFactory.cpp

Code: Select all

// ADDED: new constructor taking void replaces old one
CDefaultSceneNodeFactory::CDefaultSceneNodeFactory(void)
{
	// don't grab the scene manager here to prevent cyclic references

	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_CUBE, "cube"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_SPHERE, "sphere"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_TEXT, "text"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_WATER_SURFACE, "waterSurface"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_TERRAIN, "terrain"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_SKY_BOX, "skyBox"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_SHADOW_VOLUME, "shadowVolume"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_OCT_TREE, "octTree"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_MESH, "mesh"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_LIGHT, "light"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_EMPTY, "empty"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_DUMMY_TRANSFORMATION, "dummyTransformation"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_CAMERA, "camera"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_CAMERA_MAYA, "cameraMaya"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_CAMERA_FPS, "cameraFPS"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_BILLBOARD, "billBoard"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_ANIMATED_MESH, "animatedMesh"));
	SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_PARTICLE_SYSTEM, "particleSystem"));
	// SupportedSceneNodeTypes.push_back(SSceneNodeTypePair(ESNT_MD3_SCENE_NODE, "md3"));
}


// ADDED: implementation of interface method
void CDefaultSceneNodeFactory::setSceneManager(ISceneManager* mgr)
{
	Manager = mgr;
};
Last but not least I've changed the sceneManager
that is responsible for managing the factory.

CSceneManager.cpp

Code: Select all

//! constructor
CSceneManager::CSceneManager(video::IVideoDriver* driver, io::IFileSystem* fs,
		gui::ICursorControl* cursorControl, IMeshCache* cache,
		gui::IGUIEnvironment* gui)
: ISceneNode(0, 0), Driver(driver), FileSystem(fs), GUIEnvironment(gui),
	CursorControl(cursorControl), CollisionManager(0), MeshManipulator(0),
	ActiveCamera(0), ShadowColor(150,0,0,0), AmbientLight(0,0,0,0),
	MeshCache(cache), CurrentRendertime(ESNRP_COUNT),
	IRR_XML_FORMAT_SCENE(L"irr_scene"), IRR_XML_FORMAT_NODE(L"node"), IRR_XML_FORMAT_NODE_ATTR_TYPE(L"type")
{
 	...
	// code omitted

	...

	// factories
	ISceneNodeFactory* 
		factory = new CDefaultSceneNodeFactory();
		
	//ADDED: Dont' forget the default scene Manager via new interface method
		factory->setSceneManager(this);

	registerSceneNodeFactory(factory);
	factory->drop();

	ISceneNodeAnimatorFactory* animatorFactory = new CDefaultSceneNodeAnimatorFactory(this);
	registerSceneNodeAnimatorFactory(animatorFactory);
	animatorFactory->drop();
}


//! Returns a scene node factory by index
ISceneNodeFactory* CSceneManager::getSceneNodeFactory(u32 index)
{
	if (index<SceneNodeFactoryList.size())
	{	
		ISceneNodeFactory*
			factory = SceneNodeFactoryList[index];	
			factory->setSceneManager(this);		//ADDED: store the pointer here when it's needed
		return factory;
	}

	return 0;
}
Explanation
The shown code provides multiple SceneManager support for a custom SceneNodeFactory.
The code examle in the upper section will compile now, as the constructor of the factory
will not take any argument. It is no more necessary to create more
than one instance of a custom SceneNodeFactory.

References
http://irrlicht.sourceforge.net/phpBB2/ ... hp?t=25654
http://irrlicht.sourceforge.net/phpBB2/ ... hp?t=23514
http://irrlicht.sourceforge.net/phpBB2/ ... enemanager
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

Wouldn't it be better to store a device pointer and query the device for the active scene manager each time a node is created? You could still create nodes for the wrong scene manager with your solution.
Or we could make the SceneNodeTypePair array static to save the memory even if several factories are created. Not so clean, though, and problematic with embedded devices.
Thulsa Doom
Posts: 63
Joined: Thu Aug 05, 2004 9:40 am
Location: Germany

Post by Thulsa Doom »

Thank you hybrid
for your inspiring answer.

Yes you are right, that to my knowledge
there are two causalities when nodes
with a wrong SceneManager as parent may be created:
1) storing a factory pointer for later use within custom code
2) running irrlicht in a multiple thread environment
As irrlicht is designed as single threaded application for graphics
(also it's possible and useful to run physics, network and sound in a
different thread) we can neglect the second case.
Instead it's a crucial advice to query the factory every time it's needed
by it's hosting SceneManager!

Declaring the SceneNodeTypePair array as static does solve the problem only
to some part, for the default CDefaultSceneNodeFactory, in my opinion.
As consequence every application programmer must implement
his factory in analogue way and declare his custom SceneNodeTypePair array as static too.
Apart from the fact, that the array is only a member and not the hole factory class what still must be instanciated.
I agree that this solution is not so clean, as you pointed out already.
Wouldn't it be better to store a device pointer and query the device for the active scene manager each time a node is created?
I'm not so sure how this could be implemented.
A search in the irrlich source showed up, that the only function
to set a SceneManager is implemented in

CIrrDeviceStub.cpp

Code: Select all

//! Sets the input receiving scene manager. 
void CIrrDeviceStub::setInputReceivingSceneManager(scene::ISceneManager* sceneManager)
{
	if (InputReceivingSceneManager)
		InputReceivingSceneManager->drop();

	InputReceivingSceneManager = sceneManager;

	if (InputReceivingSceneManager)
		InputReceivingSceneManager->grab();
}
May be I'm wrong, I don't know how and where to set an SceneManager in the API as active?
I would appreciate if you could clarify this point a little more.

I personally would prefer something like implemting a interface defined as follows:

Code: Select all

virtual scene::ISceneManager* irr::IrrlichtDevice::getSceneManager(irr::u32 idx=0)  = 0;
which returns a SceneManager by index (the default without index).
This would not hurt any existing user code.
And has advantage that there would be better correspondeance for some physics SDKs (at least one),
which can deal with different scenes that may be renderd or not.
Post Reply