[Irrlicht Tutorial Bug]Tutorial : Custom Scene Node

Discuss about anything related to the Irrlicht Engine, or read announcements about any significant features or usage changes.
Post Reply
Halifax
Posts: 1424
Joined: Sun Apr 29, 2007 10:40 pm
Location: $9D95

[Irrlicht Tutorial Bug]Tutorial : Custom Scene Node

Post by Halifax »

virtual s32 getMaterialCount()

{

return 1;

}
should be
virtual u32 getMaterialCount()

{

return 1;

}
with irrlicht-1.3.1 because it produces errors otherwise.
Last edited by Halifax on Sat Oct 27, 2007 8:47 am, edited 2 times in total.
TheQuestion = 2B || !2B
Halifax
Posts: 1424
Joined: Sun Apr 29, 2007 10:40 pm
Location: $9D95

Post by Halifax »

And also, can someone tell me where to find OnPreRender()?
TheQuestion = 2B || !2B
Halifax
Posts: 1424
Joined: Sun Apr 29, 2007 10:40 pm
Location: $9D95

Post by Halifax »

Well after a lot of sifting, I decided to update the full tutorial. A lot was wrong with it, but now it works with Irrlicht 1.3.1

I have no idea whether it works with Irrlicht 1.4, but if someone could test it for me that would be great.

Updated tutorial:

Code: Select all

#include <irrlicht/Irrlicht.h>

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

#pragma comment(lib, "Irrlicht.lib")

class CMySampleSceneNode : public ISceneNode 
{
	public:
		CMySampleSceneNode(ISceneNode* parent, ISceneManager* mgr, s32 id)
		: ISceneNode(parent, mgr, id)
		{
			driver = SceneManager->getVideoDriver();
			
			Material.Wireframe = false;
			Material.Lighting = false;
			Material.BackfaceCulling = false;
			
			Vertices[0] = S3DVertex(0,0,10, 1,1,0, SColor(255,255,0,0), 0,1);
			Vertices[1] = S3DVertex(5,10,0, 1,0,0, SColor(255,0,255,0), 1,1);
			Vertices[2] = S3DVertex(0,0,0, 0,1,1, SColor(255,0,0,255), 1,0);
			Vertices[3] = S3DVertex(-5,10,0, 0,0,1, SColor(255,0,0,0), 0,0);
			
			Box.reset(Vertices[0].Pos);
			for (s32 i = 0; i < 4; i++)
				Box.addInternalPoint(Vertices[i].Pos);
		}
		
		virtual void OnRegisterSceneNode()
		{
			if (IsVisible)
				ISceneNode::OnRegisterSceneNode();
		}		
		
		virtual void OnAnimate(u32 timeMs)
		{
			ISceneNode::OnAnimate(timeMs);
		}
		
		virtual void render()
		{
			u16 indices[] = {0,2,3, 2,1,3, 1,0,3, 2,0,1};
			
			driver->setMaterial(Material);
			driver->setTransform(ETS_WORLD, AbsoluteTransformation);
			driver->drawIndexedTriangleList(&Vertices[0], 4, &indices[0], 4);
		}
		
		virtual const aabbox3d<f32>& getBoundingBox() const
		{
			return Box;
		}
		
		virtual u32 getMaterialCount()
		{
			return 1;
		}
		
		virtual SMaterial& getMaterial(s32 i)
		{
			return Material;
		}
			
	private:
		aabbox3d<f32> Box;
		S3DVertex Vertices[4];
		SMaterial Material;
		IVideoDriver *driver;
};

int main()
{
	IrrlichtDevice* device = createDevice(
								EDT_OPENGL,
								dimension2d<s32>(640,480),
								32,
								false,
								false,
								false,
								0);
	
	IVideoDriver* driver = device->getVideoDriver();
	ISceneManager* smgr = device->getSceneManager();
	IGUIEnvironment* guienv = device->getGUIEnvironment();
	
	device->setWindowCaption(L"Irrlicht Custom Scene Node!");
	
	smgr->addCameraSceneNode(0, vector3df(0,-30,0), vector3df(0,0,0));
	
	CMySampleSceneNode* myNode = new CMySampleSceneNode(smgr->getRootSceneNode(), smgr, 2);
	
	ISceneNodeAnimator* anim = smgr->createRotationAnimator(vector3df(0.8f, 0, 0.8f));
	myNode->drop();
	myNode->addAnimator(anim);
	anim->drop();
	
	while(device->run() && device->isWindowActive()) {
		driver->beginScene(true, true, SColor(0,255,255,255));
		
		myNode->render();
		smgr->drawAll();
				
		driver->endScene();
	}
	
	device->drop();
	return 0;
}
TheQuestion = 2B || !2B
BlindSide
Admin
Posts: 2821
Joined: Thu Dec 08, 2005 9:09 am
Location: NZ!

Post by BlindSide »

I hope that the crash when using SceneManager->RegisterSceneNodeForRendering() can be fixed so you dont have to draw it manually.

Anyone have an idea why its crashing there? (And so he had to remove it)
ShadowMapping for Irrlicht!: Get it here
Need help? Come on the IRC!: #irrlicht on irc://irc.freenode.net
ultramedia
Posts: 175
Joined: Wed Dec 20, 2006 12:04 pm

Post by ultramedia »

Sorry to be a pain, I'm new to C++, but why do we drop myNode before adding an animator to it (isn't this deleting it?) :

Code: Select all

	// create an instance of our custom node
	CMySampleSceneNode* myNode = new CMySampleSceneNode(smgr->getRootSceneNode(), smgr, 2); 

	ISceneNodeAnimator* anim = smgr->createRotationAnimator(vector3df(0.8f, 0, 0.8f)); 
	myNode->drop();  <<<<<<<<<<<<<<???
	myNode->addAnimator(anim); 
	anim->drop(); 
bitplane
Admin
Posts: 3204
Joined: Mon Mar 28, 2005 3:45 am
Location: England
Contact:

Post by bitplane »

creating a node increases the reference count, but in the ISceneNode constructor the node is also added as a child of the root node. when adding the node as a child of another node, the parent grab()s the child increasing the reference count a second time.
Parent nodes drop all their children when destructed, so when you destroy the scene manager all the nodes in the scene graph get dropped. This automatically destroys them if the reference count is 0.
So, if you don't drop the node once to counter the "new" then the reference count would still be 1 and the node would not be destroyed along with the rest of the scene graph.
see http://irrlicht.sourceforge.net/docu/cl ... known.html
Submit bugs/patches to the tracker!
Need help right now? Visit the chat room
Raedwulf
Posts: 62
Joined: Sat Aug 20, 2005 7:08 am

Post by Raedwulf »

Justification for smart pointers?
http://irrlicht.sourceforge.net/phpBB2/ ... =smart+ptr

I've implemented my own set of smart pointers... maybe just a future experiment to see if this is actually worthwhile...
ultramedia
Posts: 175
Joined: Wed Dec 20, 2006 12:04 pm

Post by ultramedia »

Ok, I read that link to IUnknown, and it made about as much sense to me as that old comedy clip where they do the "whos on first, whats on second" routine.

I know when you've been around a while, it can be annoying when newbies pop up with questions like this, but if you could humor me a little more that it'd be really cool - I get the sense this is one of those basic fundamentals which will make things a lot harder than they need to be if I don't figure it out. I'm going to bring the code in again so I can explain what I DO understand (I think) :

Code: Select all

1.  // create a new object based on our custom node and put a pointer to it in myNode
2.  CMySampleSceneNode* myNode = new CMySampleSceneNode(smgr->getRootSceneNode(), smgr, 2); 
3.
4.  ISceneNodeAnimator* anim = smgr->createRotationAnimator(vector3df(0.8f, 0, 0.8f)); 
5.  myNode->drop(); 
6.  myNode->addAnimator(anim); 
7.  anim->drop(); 
1. Is this right?
2. I understand that an object has been created here, and that myNode is a reference/pointer to it
4. Same here only with anim
5. Here I don't understand. Are we dropping the pointer or the object?
- If its the pointer being dropped isn't the object now "lost in space?"
- if it's the object being dropped isn't the pointer now a suitcase handle without a suitcase so to speak?
- If it's both being dropped why doesn't line 6. get a "object not found" error?
6. Here I don't understand why myNode is still available, but I do understand the rest of the line
7. Same here, anim gets dropped... or does it???

The only thing I can think it might be is that when it goes:

someObjectClass* myNode = new someObjectClass;

it makes the object and names it directly myNode, then creates a pointer also called myNode that points to the object, then straight away drops the pointer. If so, why make the pointer in the first place?

Anyways, I hope these questions aren't too stupid, hopefully someone doesn't mind taking the time to try and explain...

Cheers,
Simon O
ultramedia
Posts: 175
Joined: Wed Dec 20, 2006 12:04 pm

Post by ultramedia »

Ok, I understand now, after having visited the utterly excellent irc channel #irrlicht. Here's my understanding of of it now (so regulars can tell me if its wrong, and newbier than mes can shortcut past my hassles so far)...

Theres a "quirk" in Irrlicht that happens specifically when you instantiate a custom scene node, I'll quote blindside from #irrlicht:

<BlindSide__> ultramedia: the problem with the custom scene nodes is that when you make one it increments the reference counter twice.

All the reference counter does is watch each object made in memory and for each one, keep a track of how many pointers are pointing at it. That way when the number for any object gets to 0 (i.e. no more pointers pointing at it), the system knows it can get rid of the object (or more specifically, use the objects memory space for something else later on if it needs it).

But in Irrlicht, with the custom scene node "quirk", after you've made your object and made a pointer to it, the scenemanager jumps in and makes a pointer for itself too. So the problem is, later on you think time to get rid of the object and delete the pointer assuming the system will abandon the object now, but it doesn't cause the scenemanager has an extra pointer floating around - so you got to use the drop() method to "correct" the reference counter after instantiating a custom scene node.

How'd I do? (I had to edit a couple of times)
rogerborg
Admin
Posts: 3590
Joined: Mon Oct 09, 2006 9:36 am
Location: Scotland - gonnae no slag aff mah Engleesh
Contact:

Post by rogerborg »

First up, let's be clear that reference counting and grab() / drop() are not built-in C++ concepts. However, reference counted objects are a fairly common C++ idiom (regardless of what they call the actual operations), so it's useful to get to grips with this.

The basic idea is that the object decides when to destroy (delete) itself. It does so when everything else that was using the object has told it that they're done with it (by calling drop()). When its reference count drops to 0, it will delete itself.

That means that after you call drop() on an object, you should not assume that it is still valid. Calling drop() can cause it to delete itself, so once you've dropped your reference to it, don't use it again.
ultramedia wrote:

Code: Select all

1.  // create a new object based on our custom node and put a pointer to it in myNode
2.  CMySampleSceneNode* myNode = new CMySampleSceneNode(smgr->getRootSceneNode(), smgr, 2); 
3.
4.  ISceneNodeAnimator* anim = smgr->createRotationAnimator(vector3df(0.8f, 0, 0.8f)); 
5.  myNode->drop(); 
6.  myNode->addAnimator(anim); 
7.  anim->drop(); 
1. Is this right?
It will work, but it's not quite right. After you've called drop() on an object, you shouldn't refer to that object again; it's not safe to assume that it still exists.

In this case, what happens when you call new CMySampleSceneNode() is that the object creates itself with a reference count of 1. This is not a "quirk"; all reference counted objects must created themselves with a reference count of 1 because whatever created the object will (implicitely) want to use it. It can't start with a reference count of 0 on the assumption that something else will grab() a reference to it; while it has a reference count of 0, it's effectively saying "I'm invalid, nobody cares about me", then it should slash its wrists and bleed out like an emo teen.

After it's created itself, it then attempts to add itself to the scene. If that succeeds (and you shouldn't assume that it will) then its parent in the scene heirarchy will grab() another reference to it, increasing the reference count to 2. However, if for some reason it got created but failed to add itself added to the scene, then it would only have a reference count of 1 and calling drop() on it would cause it to delete itself before you call addAnimator() on it.

So a safer order is:

Code: Select all

CMySampleSceneNode* myNode = new CMySampleSceneNode(smgr->getRootSceneNode(), smgr, 2); 
ISceneNodeAnimator* anim = smgr->createRotationAnimator(vector3df(0.8f, 0, 0.8f));
if(anim)
{ 
    myNode->addAnimator(anim); 
    myNode->drop(); 
    anim->drop();
}
Note that I don't check myNode because C++ new should only fail if it can't allocate memory, in which case it should (in standards compliant compilers) throw an exception rather than return 0. However, createRotationAnimator() makes no such contract, and so you should check the return from it before using it.

2. I understand that an object has been created here, and that myNode is a reference/pointer to it
It's a pointer. The language here is going to get tricky, so hold onto your thesaurus.

A pointer is a pointer is a pointer.
A pointer is also a "reference", but only in the common English usage, or when discussing pure C code. It's best to try and avoid calling it that in a C++ context if possible.
A "reference" in C++ refers to a specific construct that is similar to but distinct from a pointer.
A "reference" in the context of reference counted objects can (but doesn't always!) mean a (wait for it) a pointer or a (C++ specific) reference to an object on which grab() (or addRef() or whatever the reference count incrementor) has been called. The semantics aren't crystal clear, but "dropping a reference" genereally means: call pointer->drop() or reference.drop() and then don't use that pointer or reference again.

I know that's a horribly muddled situation, but it's what we're stuck with.
4. Same here only with anim
OK, anim is created with a reference count of 1. When you add it to myNode(), myNode should take a reference to it, increasing its reference count to 2. Calling drop() will decrease it back to 1 again, which is where you want it to be. If something goes wrong when adding it to myNode, then calling drop() will reduce the reference count to 0, and the object will have deleted itself, so again, after you call drop(), don't use that object again.
5. Here I don't understand. Are we dropping the pointer or the object?
We are dropping one (oh, sorry) "reference" (common English usage) to the object that the pointer points to. The pointer is still a valid pointer; it points to the same bit of memory that it did before. However, after calling drop(), the object may have deleted itself, so that memory is no longer safe to use. That's why you should never use a pointer after calling drop() on the object that it points to.

6. Here I don't understand why myNode is still available, but I do understand the rest of the line
It's not safe to assume that it is available.

The object that myNode points to was created with a reference count of 1. When it was added to the scene, its parent (the node returned from smgr->getRootSceneNode()) would have taken a reference to it, increasing its reference count to 2. In most cases, that will work, so after you call drop on it(), its reference count is back to 1, and it still exists. However, this is bad practice. drop() should be the last thing you do on an object. After that, don't touch it, not even to try to check its reference count (the memory may have been re-allocated or overwritten with a magic value by debug free by the time you check it, so you are likely to get a completely bogus but ostensibly >0 value back).

7. Same here, anim gets dropped... or does it???
Yup. It was created with a reference count of 1, you added it to myNode, which takes a reference and increases it to 2, then after you drop it, it goes back to 1 and is still extant. If for some reason the myNode object failed to take a reference, then calling drop() on it would reduce its reference count to 0, and the object would delete itself. This is actually what you'd want to happen, as nothing would be referencing it.

I'd suggest defining yourself a macro to do 'safe drops' on pointers.

Code: Select all

#define SAFE_DROP(x) do { if(x) { x->drop(); x = 0; } } while(0)

...

CMySampleSceneNode* myNode = new CMySampleSceneNode(smgr->getRootSceneNode(), smgr, 2);
ISceneNodeAnimator* anim = smgr->createRotationAnimator(vector3df(0.8f, 0, 0.8f));
if(anim)
{
    myNode->addAnimator(anim);
    SAFE_DROP(myNode);
    SAFE_DROP(anim);
} 
Setting a pointer to 0 after you've called drop() on the object that it points to will help you to catch instances where you accidentally try to use it after calling drop(). In many cases you'll get away with it (like your initial code example), but it will protect you against the occasional case when things go differently to the way you expect.

Note that if you used a macro like that with your original code then myNode would be set to 0 on line 5, and your program would access violate on line 6. This is what you want to happen; forcing potential errors to become actual errors that are easy to catch and fix is good coding practice.

Incidentally, this is one reason why pointers can be considered slightly 'safer' than C++ references in the context of reference counted objects, as C++ provides a clear idiom for making a pointer invalid (i.e. setting it to 0) wheres a C++ reference always appears to be valid until it goes out of scope.
Please upload candidate patches to the tracker.
Need help now? IRC to #irrlicht on irc.freenode.net
How To Ask Questions The Smart Way
Post Reply