Use shortest path between quaternions

Post Reply
Dunkelheit
Posts: 2
Joined: Fri Jan 24, 2014 12:27 am

Use shortest path between quaternions

Post by Dunkelheit »

Hi all,

I'm trying to implement a LookAt function into my game. I have an enemy character ,i'd like to keep rotated towards the player wherever it goes.
If the enemy needs to turn left,everything is fine ,it follows the player correctly.But if it needs to turn right,it choses the long path,so it makes a big ugly rotation around itself,instead of chosing the short path.
I have read many forums,and they all said i need to check the dotProduct between the two quaternions,and if it's negative, need to negate one of them.
i tried differents methods,but they gave the same results,still chosing the longest path on "turning right".

Code: Select all

btQuaternion orig = mRigidBody->getWorldTransform().getRotation();

//angle is the angle between the quaternions
btQuaternion dest = orig * btQuaternion(btVector3(0,1,0),angle);

//QuatBlend calculates the dot product and negating,if necessary
btQuaternion result =  QuatBlend(orig,dest,1);

//apply the transformations
mRigidBody->getWorldTransform().setRotation(result);
And the QuatBlend function(found it on a forum somewhere :))

Code: Select all

btQuaternion QuatBlend(const btQuaternion& i, const btQuaternion& f, float blend)
{
     btQuaternion result;
     float dot = i.w()*f.w() + i.x()*f.x() + i.y()*f.y() + i.z()*f.z();
     float blendI = 1.0f - blend;
     if(dot < 0.0f)
     {
          btQuaternion tmpF;
          tmpF.setW(-f.w());
          tmpF.setX(-f.x());
          tmpF.setY(-f.y());
          tmpF.setZ(-f.z());
          result.setW(blendI*i.w() + blend*tmpF.w());
          result.setX(blendI*i.x() + blend*tmpF.x());
          result.setY(blendI*i.y() + blend*tmpF.y());
          result.setZ(blendI*i.w() + blend*tmpF.z());
     }
     else
     {
          result.setW(blendI*i.w() + blend*f.w());
          result.setX(blendI*i.x() + blend*f.x());
          result.setY(blendI*i.y() + blend*f.y());
          result.setZ(blendI*i.z() + blend*f.z());
     }
     result = result.normalize();
     return result;
}
Any help would be appreciated,because i'm losing hair with this very badly :P
c6burns
Posts: 149
Joined: Fri May 24, 2013 6:08 am

Re: Use shortest path between quaternions

Post by c6burns »

OgreMath has both a Vector3::getRotationTo (retrieve the shortest arc quat between two vectors) and a Quaternion::Slerp (spherical linear interpolation between two quats)
Dunkelheit
Posts: 2
Joined: Fri Jan 24, 2014 12:27 am

Re: Use shortest path between quaternions

Post by Dunkelheit »

I have tried both,they have the same result :? Where did i get it wrong?
c6burns
Posts: 149
Joined: Fri May 24, 2013 6:08 am

Re: Use shortest path between quaternions

Post by c6burns »

OK well assuming you are familiar with Ogre, or willing to look into the two methods I mentioned in OgreMath then here is some simple code. Note it is rotating the Ogre head ... which faces unit Z. Also Ogre is Y up. Lastly I assume the turret only aims left and right, not up and down:

Code: Select all

	// current orientation of ogre head
	Ogre::Quaternion qSrc(headNode->getOrientation());

	// current direction of ogre head
	Ogre::Vector3 vSrc(qSrc * Ogre::Vector3::UNIT_Z);

	// direction of camera in ogre head's local space (locking Y and using XZ to rotate)
	Ogre::Vector3 vDest(Ogre::Vector3(mCamera->getPosition().x, vSrc.y, mCamera->getPosition().z) - headNode->getPosition());

	// get our relative rotation then multiply by source orientation to make it our final orientation
	Ogre::Quaternion qDest(vSrc.getRotationTo(vDest) * qSrc);

	// slerp between current orientation and final orientation
	Ogre::Quaternion qSlerpDest = Ogre::Quaternion::Slerp(evt.timeSinceLastFrame, qSrc, qDest);

	// actually set the orientation
	headNode->setOrientation(qSlerpDest);
But now I have a question. Isn't your turrent a physical object, and so you would actually want to align it to fire using torque? For this I use a different method, similar to a steering behaviour. I take the direction vector it is facing, and create two vectors facing slightly left and slightly right (eg. by 5-10 degrees). If the dot product of the left or right vectors and your desired direction vector is higher then torque the body in that direction.

Anyway that's my 2 cents perhaps others have better advice :)
Granyte
Posts: 77
Joined: Tue Dec 27, 2011 11:51 am

Re: Use shortest path between quaternions

Post by Granyte »

c6burns wrote: But now I have a question. Isn't your turrent a physical object, and so you would actually want to align it to fire using torque? For this I use a different method, similar to a steering behaviour. I take the direction vector it is facing, and create two vectors facing slightly left and slightly right (eg. by 5-10 degrees). If the dot product of the left or right vectors and your desired direction vector is higher then torque the body in that direction.

Anyway that's my 2 cents perhaps others have better advice :)

have you implemented a such function ? I would like to have a look at the code (or pseudo)
I have been trying to implement one in the past that would return the torque to apply to face the target with no succes
c6burns
Posts: 149
Joined: Fri May 24, 2013 6:08 am

Re: Use shortest path between quaternions

Post by c6burns »

Sure. First of all it's really helpful to be able to debug draw direction vectors, so if you spend a bit of time on that you will be glad you did. I tore this out of a section of steering code and quickly rewrote it to make sense and be readable for your use case (again, I'm using OgreMath but it's the same principle regardless)

Code: Select all

using namespace Ogre;

// Vector3 vSrc = the vector your body is currently facing
// Vector3 vDst = the vector facing your target

Real fDotFwd = vSrc.dotProduct(vDst);

Quaternion qLeft;
qLeft.FromAngleAxis((Radian)Degree(10.0).valueRadians(), Vector3::UNIT_Y);
Vector3 vLeft(qLeft * vSrc);
Real fDotLeft = vLeft.dotProduct(vDst);

Quaternion qRight;
qRight.FromAngleAxis((Radian)Degree(-10.0).valueRadians(), Vector3::UNIT_Y);
Vector3 vRight(qRight * vSrc);
Real fDotRight = vRight.dotProduct(vDst);

if (fDotLeft > fDotFwd)
{
	// you need to torque the body to turn left (+yaw)
}
else if (fDotRight > fDotFwd)
{
	// you need to torque the body to turn right (-yaw)
}
I consider this a fairly crude example. Someone else might have something more sophisticated to offer, but this is my ugly example :D
Post Reply