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
}
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;
}

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.

Precision errors fixed.
Executable:
SceneNodeAnimatorTestcase.zip(1.14 MB)
SceneNodeAnimatorTestCase2.zip (715.55 KB)
Patch Tracker:
https://sourceforge.net/tracker/?func=d ... tid=540678
