[fixed]Scene Node Rotation Precision Errors

You discovered a bug in the engine, and you are sure that it is not a problem of your code? Just post it in here. Please read the bug posting guidelines first.
Post Reply
Halifax
Posts: 1424
Joined: Sun Apr 29, 2007 10:40 pm
Location: $9D95

[fixed]Scene Node Rotation Precision Errors

Post by Halifax »

Text in red means that the information is out of date and has been updated by the text in green.

Problem:
A scene node, specifically ISceneNode, holds its rotation in a vector3df which represents the 3-components of a 3-dimensional vector with a 32-bit IEEE (compiler-dependent) floating-point number. As the rotation of a given scene node increases to larger numbers, the precision becomes less accurate, in turn, affecting the simulation of such things as animators.

Solution:
Clamp the 3-components of the rotation vector to the range [0,360) to keep all simulations uniform. Since the user can set an arbitrary rotation at any time, we must use a generic clamping method that takes the range (-inf,+inf):

Code: Select all

void clampDegree( f32 degree )
{
   // return degree - 360.0 * floor32( degree / 360.0 );
	return fmod( degree, 360.0 ); //as pointed out by sio2
}
To make the interface completely coherent we must clamp the degree values immediately upon modification. This ensures that both the user and classes inheriting from ISceneNode are operating on the same rotation vector. So my recommendation is to add the clamping to ISceneNode::setRotation().

Of course this does assume that a class inheriting from ISceneNode does not manipulate the scale directly. This is a senseless assumption and can be solved by adding a clamping call in ISceneNode::getRelativeTransformation() and ISceneNode::getRotation() to keep the client up-to-date just in case they want the rotation before the relative transformation is calculated. (This would be common in a threaded environment.)
I've only added the rotation vector clamp to the ISceneNode::setRotation() member function because it cannot be added to either ISceneNode::getRotation() or ISceneNode::getRelativeTransformation() since both are constant member functions. You could make the RelativeRotation member mutable, but that solution is not as elegant. I moved the responsibility to any classes that inherit from ISceneNode; they must maintain the clamped rotation in the event that they modify RelativeRotation directly and would like the clamping to be instant, otherwise ISceneNode::updateAbsolutePosition() will handle it for them.

So overall, it must be added to 3 place in ISceneNode.h. I would provide a patch, but I'm currently away from my real development machine.

Code:

Code: Select all

#include <iostream>
#include <cstdio>
#include "Irrlicht.h"

#pragma warning( disable: 4996 )

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

int main( int argc, char** argv )
{
	IrrlichtDevice* device = createDevice(
			EDT_DIRECT3D9,
			dimension2di( 640, 480 ),
			32, false, false, false
		);

	if( !device )
	{
		fprintf( stderr, "Error: Could not create DIRECT3D9 device\n" );
		return 0;
	}

	IVideoDriver* driver = device->getVideoDriver();
	ISceneManager* smgr = device->getSceneManager();

	// Create the camera
	ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS( 0, 75.0f, 0.05f );
	camera->setPosition( vector3df( 0, 0, 15 ) );
	camera->setTarget( vector3df( 0, 0, 0 ) );
	camera->setNearValue( 0.1f );
	camera->setFarValue( 60.0f );

	// Create the original cube
	ISceneNode* cubeOriginal = smgr->addCubeSceneNode( 5.0f );
	cubeOriginal->setPosition( vector3df( -6, 0, 0 ) );
	cubeOriginal->setRotation( vector3df( 0, 0, 0 ) );

	// Create the error cube
	ISceneNode* cubeError = smgr->addCubeSceneNode( 5.0f );
	cubeError->setPosition( vector3df( 6, 0, 0 ) );
	cubeError->setRotation( vector3df( 360 * 500,  360 * 500, 360 * 500 ) );

	// Setup the materials
	cubeOriginal->getMaterial( 0 ).setFlag( EMF_LIGHTING, false );
	cubeOriginal->getMaterial( 0 ).setTexture( 0, driver->getTexture( "wall.bmp" ) );
	cubeError->getMaterial( 0 ).setFlag( EMF_LIGHTING, false );
	cubeError->getMaterial( 0 ).setTexture( 0, driver->getTexture( "wall.bmp" ) );

	// Add the same animator to both cubes and watch the error
	ISceneNodeAnimator* anim = smgr->createRotationAnimator( vector3df( 0.1f, 0.1f, 0.1f ) );
	cubeOriginal->addAnimator( anim );
	anim->drop();

	anim = smgr->createRotationAnimator( vector3df( 0.1f, 0.1f, 0.1f ) );
	cubeError->addAnimator( anim );
	anim->drop();

	// Add some text identifiers
	ITextSceneNode* textOriginal = smgr->addTextSceneNode( 
			device->getGUIEnvironment()->getBuiltInFont(), 
			L"Original", SColor( 0xFFFF0000 ) 
		);
	ITextSceneNode* textError = smgr->addTextSceneNode( 
			device->getGUIEnvironment()->getBuiltInFont(), 
			L"Error", SColor( 0xFFFF0000 ) 
		);

	textOriginal->setPosition( cubeOriginal->getPosition() );
	textError->setPosition( cubeError->getPosition() );

	while( device->run() )
	{
		if( device->isWindowActive() )
		{
			driver->beginScene( true, true, SColor( 0x0 ) );
				smgr->drawAll();
			driver->endScene();

			vector3df originalRotation = cubeOriginal->getRotation();
			vector3df errorRotation = cubeError->getRotation();

			char buf[256];
			sprintf( buf, "Original( %f, %f, %f ); Error( %f, %f, %f )",
				originalRotation.X, originalRotation.Y, originalRotation.Z,
				errorRotation.X, errorRotation.Y, errorRotation.Z );

			stringw caption( buf );
			device->setWindowCaption( caption.c_str() );
		}
	}

	device->drop();
	return 0;
}
Screenshot:
Image
The original scene node starts at (0,0,0) and the error scene node starts at (180000,180000,180000). They both use the same animator, but as evidenced, the error scene node rotates quicker because of reduced precision.

Image
Precision errors fixed.

Executable:
SceneNodeAnimatorTestcase.zip(1.14 MB)
SceneNodeAnimatorTestCase2.zip (715.55 KB)

Patch Tracker:
https://sourceforge.net/tracker/?func=d ... tid=540678
Last edited by Halifax on Sun Jul 19, 2009 9:19 pm, edited 7 times in total.
TheQuestion = 2B || !2B
twilight17
Posts: 362
Joined: Sun Dec 16, 2007 9:25 pm

Post by twilight17 »

Wow! Could this be the reason why Box2D and Irrlicht's rotation were messed up? Anyway, I think this should immediately be added.
Post this userbar I made on other websites to show your support for Irrlicht!
Image
http://img147.imageshack.us/img147/1261 ... wernq4.png
Halifax
Posts: 1424
Joined: Sun Apr 29, 2007 10:40 pm
Location: $9D95

Post by Halifax »

twilight17 wrote:Wow! Could this be the reason why Box2D and Irrlicht's rotation were messed up?
Possibly, but it may not be. Feel free to open up another thread discussing your problem. Maybe I, or someone else, could help you in solving it.
TheQuestion = 2B || !2B
sio2
Competition winner
Posts: 1003
Joined: Thu Sep 21, 2006 5:33 pm
Location: UK

Post by sio2 »

clampDegree() --> fmod() ?
Halifax
Posts: 1424
Joined: Sun Apr 29, 2007 10:40 pm
Location: $9D95

Post by Halifax »

sio2 wrote:clampDegree() --> fmod() ?
Correct.
TheQuestion = 2B || !2B
Halifax
Posts: 1424
Joined: Sun Apr 29, 2007 10:40 pm
Location: $9D95

Post by Halifax »

Update:
I've uploaded the patch to the tracker.I've also updated the Irrlicht build that the test case uses to reflect the changes(it's the same code from the OP):
Image
SceneNodeAnimatorTestCase2.zip (715.55 KB)

I've made a few changes to my solution, so I will describe my reasoning and update the OP. I've only added the rotation vector clamp to the ISceneNode::setRotation() member function because it cannot be added to either ISceneNode::getRotation() or ISceneNode::getRelativeTransformation() since both are constant member functions. You could make the RelativeRotation member mutable, but that solution is not as elegant. I moved the responsibility to any classes that inherit from ISceneNode; they must maintain the clamped rotation in the event that they modify RelativeRotation directly and would like the clamping to be instant, otherwise ISceneNode::updateAbsolutePosition() will handle it for them.
TheQuestion = 2B || !2B
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

Personally I don't think this 'fix' belongs in the engine. Though the cost is small, there is an associated cost and there is no way to avoid it for existing scene node types. It seems that this is just a known issue with the graphics engine, and I'd venture to guess that this is consistent with other engines of this type.

If the user wants to avoid the problem, they can write a simple function to clamp the rotation values as they see fit.

Travis
Halifax
Posts: 1424
Joined: Sun Apr 29, 2007 10:40 pm
Location: $9D95

Post by Halifax »

vitek wrote: It seems that this is just a known issue with the graphics engine, and I'd venture to guess that this is consistent with other engines of this type.
To cite one of Irrlicht's main "competitors": Ogre uses normalized quaternions to represent orientation, thus avoiding this problem.
vitek wrote: If the user wants to avoid the problem, they can write a simple function to clamp the rotation values as they see fit.
I agree. ISceneNode should not be changed, but the animator should at least take responsibility for the errors. What's the use of having an animator that will deteriorate as your application runs? The user shouldn't be responsible for cleaning up after the animator.
TheQuestion = 2B || !2B
bitplane
Admin
Posts: 3204
Joined: Mon Mar 28, 2005 3:45 am
Location: England
Contact:

Post by bitplane »

Halifax wrote: I agree. ISceneNode should not be changed, but the animator should at least take responsibility for the errors. What's the use of having an animator that will deteriorate as your application runs? The user shouldn't be responsible for cleaning up after the animator.
Agreed, in this case the animator is responsible for the rotation and should take care of the rounding errors.
Submit bugs/patches to the tracker!
Need help right now? Visit the chat room
Post Reply