Skip to main content
gamedev.net
🔒 Locked 🎮 Unity

Vector rotation.

Started by DrEvil Jan 1, 2005 at 7:56 PM 4 replies 2.3k views
Original Post
DrEvil
DrEvil
Hello everyone. I'm working on a bot for a first person shooter. In order to give the bot a smoother and controllable aiming system I'm using the quaternion class from the Wildmagic Math library along with the following aiming function. Here's the function that handles all my aiming.

bool Client::TurnTowardPosition(const Vector3f &_pos)
{
	Vector3f newFacing = (_pos - m_Position);
	newFacing.Normalize();

	// See how close we are to 
	float fDot = m_Facing.Dot(newFacing);

	// Determine the angle between the heading vector and the target
	float fAngle = Wml::Mathf::ACos(fDot);

	// Just snap the facing if it's very close.
	const double WeaponAimTolerance = 3.0f*Mathf::DEG_TO_RAD;
	if(fAngle < WeaponAimTolerance)
	{
		m_Facing = newFacing;
		return true;
	}

	int iSign = m_Facing.Sign(newFacing);

	Wml::Quaternionf quat;
	quat.GetRotationTo(m_Facing, newFacing);

	m_Facing = (quat * 0.65f) * m_Facing;
	m_Facing.Normalize();

	return false;
}



The line m_Facing = (quat * 0.65f) * m_Facing; is what I did in order to make the rotation happen over time. If I do m_Facing = quat* m_Facing; it does the entire rotation all in the same frame. There seems to be a pretty rare bug where the bot stops rotating. It appears that it happens when the desired facing vector faces opposite the current facing. Anyone know anything about this? For reference, here is the GetRotationTo function that I actually found within the Ogre3d engine.

void Quaternion<Real>::GetRotationTo(const Vector3<Real>& _from, const Vector3<Real>& _to)
{
	// Based on Stan Melax's article in Game Programming Gems
	Vector3<Real> c = _from.Cross(_to);

	// NB if the crossProduct approaches zero, we get unstable because ANY axis will do
	// when v0 == -v1
	Real d = _from.Dot(_to);
	// If dot == 1, vectors are the same
	if (d >= 1.0f)
	{
		*this = Quaternion<Real>::IDENTITY;
	}
	Real s = Math<Real>::Sqrt( (1+d)*2 );
	Real invs = 1 / s;

	X() = c.X() * invs;
	Y() = c.Y() * invs;
	Z() = c.Z() * invs;
	W() = s * (Real)0.5;
}



Could it be that the dot == 1.0 in this, so no quaternion is generated, and hence the bot does not rotate? The wierd thing is that when the bot stops rotating, he stops rotating until he dies, meaning, once he is broke, even as he runs around the level he keeps facing the direction he became "broke" in, even when newer facing vectors are not the opposite of current facing, which leads me to think possibly his facing gets screwed up initially and results in every further check to generate no rotation. When he dies and respawns, or acquires a target he works fine and starts rotating toward the target or waypoints. Any ideas?
Zakwayda
Zakwayda
Quote:
m_Facing = (quat * 0.65f) * m_Facing


Is this working? It's my understanding that linear scaling of quaternions doesn't work this way - that's what slerp is for. I could be wrong, I suppose, but maybe this is a possible source of trouble in your code.

Also, I think Eberly's quaternion class has a function to do what your GetRotationTo() function is doing, so you might try using it instead. AFAIK the GPG version doesn't handle opposite vectors gracefully, which is exactly the problem you mentioned. So you might try using Eberly's function and see if that makes a difference.
DrEvil
DrEvil
Well, that line is the difference between the bot snapping to his newFacing and rotating to it over time. The problem with slerp is that I don't have, nor want to mess with time. I want to define an fMaxRotationAngle per bot, and have the aim function rotate toward his aim points taking this limit into account.

I guess I could compute a time somehow if I know the distance and speed.

Admittedly I don't understand all the math behind quaternions, just how to use them for the most part. Does someone know how to take a quaternion that represents the entire rotation from current facing to new facing and clamp its angle to a maximum angle? Multiplying by a scaler seems to be a dirty hack.

Edit: You're right, the Align function does what my GetRotationTo function did, so I'm going to use that instead. Dunno how I missed that.
sbroumley
sbroumley
jyk is correct - when a quaternion is no longer a unit quaternion then it does not represent a rotation.

What you could do is to compute the rotation (axis and angle) to map "m_Facing" onto "newFacing", then create a quaternion from the results, but use angle*0.65f for the angle when setting up the quaternion.

Something like:

    // Assuming m_Facing and newFacing are already normalized and non-zero    Vector3f Axis  = Vector3Cross( m_Facing, newFacing );    float    Dot   = Vector3Dot  ( m_Facing, newFacing );    float    Angle = 0.0f;    // Clamp dot to valid range incase of float accuracy    if( Dot < -1.0f )        Dot = -1.0f;    else if( Dot > 1.0f )        Dot = 1.0f;    // Valid rotation axis?    if( Axis.Length() > EPSILON )    {        // Compute final axis and angle        Axis.Normalize();        Angle = acos( Dot );    }    else    {        // Directions are the same/opposite so the cross product is undefined        // Something like this might work if your aim direction can never be vertically up:        Axis = Vector3f( 1.0f, 0.0f, 0.0f );        if( Dot > 0 )            Angle = 0;                       // Facing same direction        else            Angle = DegreesToRadians( 180.0f );    // Facing opposite directions    }    // We now have the axis and angle so create a quaternion that will    // apply this rotation over time    quaternion Q;    Q.SetupFromAxisAndAngle( Axis, Angle * 0.65f );    // Apply to current facing direction    m_Facing = Q * m_Facing;    m_Facing.Normalize();


Hope that helps.


Steve Broumley
Dmytry
Dmytry
m_Facing = (quat * 0.65f) * m_Facing;
is wrong, should do turn instantly, unless this lib have very weird/insane overloaded operator* that return quaternion to scalar power.

If you scale quaternion it doesn't change rotation it stores (remember that quaternion can store rotation and scale), so it will have exactly same rotation even if you don't multiply, just different scale, that will go away at normalization. (unless wildmagic have wrong quaternion multiply)

You need to raise quaternion to power , that's is,
m_Facing = quat0.65f * m_Facing;

or to use something like slerp(identity,quat,0.65)

It's the same things, in fact.

As quick fix, instead of raising to power, You can try
if(quat.w<0){
quat.w=-quat.w;
quat.x=-quat.x;
quat.y=-quat.y;
quat.z=-quat.z;
}
quat.w+=1.0f;
// quat.normalize(); // uncomment this line if something works wrong, if wildmagic is incorrect.
m_Facing = quat * m_Facing;
m_Facing.Normalize();// to compensate roundoff errors.


- for unit-length quat, it will compute square root of quat. You can try using other numbers instead of 1.0f

As about rare bug, it is principial problem of quat.GetRotationTo, i think. It is just undefined if you are facing in opposite direction.
Fix:
if(fDot<0.99)m_Facing=m_Facing*any_small_turn;

where any_small_turn may be random or predefined, as you like.
DrEvil
DrEvil
Thanks alot for the suggestions guys. I don't know why the scaler worked, just know that it did, but I've changed my implementation slightly based on what I've learned here. Appreciate it.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.