OK, first stab:
Calculate the Euler rotations that will produce given direction and up vectors (the right vector is just the cross product of those, so we can ignore it).
You can then interpolate between the current rotation and this calculated rotation, although I can't guarantee that it'll be free of gimbal lock.
If that doesn't do what you want, then I can take another stab at it. Perhaps a hack, conceivably even a slash as well.
Sample app:
Code: Select all
#include <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")
vector3df calculateRotations(vector3df const & direction,
vector3df const & up)
{
vector3df masterDirection(direction);
masterDirection.normalize();
vector3df masterUp(up);
masterUp.normalize();
// project the up vector onto a vector that's orthogonal to the direction
vector3df realUp = masterDirection.crossProduct(masterUp).normalize();
realUp = realUp.crossProduct(masterDirection);
// Get the quaternion to rotate to the required direction
quaternion quatDirection;
quatDirection.rotationFromTo(vector3df(0, 0, 1), masterDirection);
// Apply that rotation to the world up vector
vector3df worldUp(0, 1, 0);
matrix4 mat;
quatDirection.getMatrix(mat);
mat.rotateVect(worldUp);
// Get the quaternion to rotate to the required up
quaternion quatUp;
quatUp.rotationFromTo(worldUp, realUp);
// Concatenate them to get a total rotation
quaternion quat = quatDirection * quatUp;
// Convert to euler rotations
vector3df eulers;
quat.toEuler(eulers); //... in radians
eulers *= RADTODEG; //... and now in degrees
// This is the euler rotations required.
return eulers;
}
void makeRandomTargets(vector3df & targetDirection,
vector3df & targetUp)
{
do
{
targetDirection.set((f32)(rand() % 2000 - 1000),
(f32)(rand() % 2000 - 1000),
(f32)(rand() % 2000 - 1000));
targetUp.set((f32)(rand() % 2000 - 1000),
(f32)(rand() % 2000 - 1000),
(f32)(rand() % 2000 - 1000));
} while(equals(targetDirection.getLengthSQ(), 0.f) ||
equals(targetUp.getLengthSQ(), 0.f) ||
targetDirection == targetUp);
targetDirection.normalize();
targetUp.normalize();
}
bool rotateNode(ISceneNode * node, const vector3df & targetRotations, f32 delta)
{
vector3df rotation = node->getRotation();
vector3df deltaRotation = targetRotations - rotation;
if(equals(deltaRotation.getLengthSQ(), 0.f))
return false; // reached target
const f32 availableRotationMagnitude = delta * 45.f; // degrees per second
// You could also scale the speeds by the largest difference
if(deltaRotation.X < - availableRotationMagnitude)
deltaRotation.X = -availableRotationMagnitude;
else if(deltaRotation.X > availableRotationMagnitude)
deltaRotation.X = availableRotationMagnitude;
if(deltaRotation.Y < - availableRotationMagnitude)
deltaRotation.Y = -availableRotationMagnitude;
else if(deltaRotation.Y > availableRotationMagnitude)
deltaRotation.Y = availableRotationMagnitude;
if(deltaRotation.Z < - availableRotationMagnitude)
deltaRotation.Z = -availableRotationMagnitude;
else if(deltaRotation.Z > availableRotationMagnitude)
deltaRotation.Z = availableRotationMagnitude;
rotation += deltaRotation;
node->setRotation(rotation);
return true;
}
int main()
{
IrrlichtDevice *device =
createDevice( video::EDT_OPENGL, dimension2d<s32>(640, 480), 16,
false, false, false, 0);
IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();
ISceneNode * MrCubey = smgr->addCubeSceneNode();
MrCubey->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
MrCubey->setMaterialFlag(video::EMF_LIGHTING, false);
(void)smgr->addCameraSceneNode(0, vector3df(0, 0, -30));
vector3df targetDirection;
vector3df targetUp;
makeRandomTargets(targetDirection, targetUp);
vector3df targetRotations = calculateRotations(targetDirection, targetUp);
SMaterial material;
material.setTexture(0, 0);
material.Lighting = false;
u32 then = device->getTimer()->getTime();
while(device->run())
{
const u32 now = device->getTimer()->getTime();
const f32 delta = (f32)(now - then) * 0.001f;
then = now;
if(!rotateNode(MrCubey, targetRotations, delta))
{
makeRandomTargets(targetDirection, targetUp);
targetRotations = calculateRotations(targetDirection, targetUp);
}
driver->beginScene(true, true, SColor(255,100,101,140));
smgr->drawAll();
driver->setTransform(video::ETS_WORLD, core::matrix4());
driver->setMaterial(material);
driver->draw3DLine(MrCubey->getAbsolutePosition(),
MrCubey->getAbsolutePosition() + 50.f * targetDirection,
SColor(255, 255, 0, 0));
driver->draw3DLine(MrCubey->getAbsolutePosition(),
MrCubey->getAbsolutePosition() + 50.f * targetUp,
SColor(255, 0, 255, 0));
driver->endScene();
}
device->drop();
return 0;
}