Rotation Tutorial

From Valve Developer Community
Jump to: navigation, search


Concept

To do: Add a better Level 2 title. Is this good?

Posted on HLCoders:

"I'm simply trying to rotate the angles of an entity in relation to its position. Changing its angles.y alone for example is not
what I need. If the entity is facing down, I need to change the angles based on its current angles. If it was facing down, I'd
probably need to change x, but if it was in between, some of y and some of x would need to be changed, etc.. So I'm thinking a 
function similar (if not) AngleVectors must be used, but I'm unsure how.."

Tutorial

This is just a simple transform (a rotation) concatenated with the current transform. Intuitively, when you do something like:

pEntity->m_angRotation.y += 1;

It is effectively doing the same thing, but there is existing machinery and convention that makes incremental rotations simpler as long as you do them in the fixed space/order constraints that QAngles provide. For anything else you need to understand the underlying general principle.

Code Introduction

The code is a modified version of NDebugOverlay::EntityBounds() that you can paste in to debugoverlay_shared.cpp and build/modify in your mod to help you to understand. The comments should help you understand the operators a bit - at least at the "black-box" level. Anyway, QAngles / matrix3x4_t / AxisAngle / Quaternion / VMatrix can all hold orientations and be converted into each other (they're basically equivalent as long as we're keeping things simple).

The matrix types also contain position/translation, but any of the other types can be paired with a position vector to represent that kind of transform as well. Since the original question was about orientation, there is a quick example of how to apply a simple rotation to an object.

If you actually wanted to take the rotated QAngles out at the end and set that as the entity's rotation, it would only work if the entity was not in a hieararchy. If there's a parent transform involved, it is needed to factor that in as well (this example does not cover that case).

PS: The code in VPhysics is not based on QAngles so that eliminates some of the confusion there.


Tutorial Code

 
//-----------------------------------------------------------------------------
// Purpose: Draws a box around an entity
//-----------------------------------------------------------------------------

void NDebugOverlay::EntityBounds( const CBaseEntity *pEntity, int r, int g, int b, int a, float flDuration )
{
	const CCollisionProperty *pCollide = pEntity->CollisionProp();
	// Draw the base OBB for the object (default color is orange)
	BoxAngles( pCollide->GetCollisionOrigin(), pCollide->OBBMins(), pCollide->OBBMaxs(), pCollide->GetCollisionAngles(), r, g, b, a, flDuration );

	// This is the axis of rotation in world space
	Vector rotationAxisWs(0,0,1);
	const float rotationAngle = gpGlobals->curtime*10; // 10 degrees per second animated rotation
	//const float rotationAngle = 45; // degrees, Source's convention is that positive rotation is counter-clockwise
	

	// Example 1: Applying the rotation in the local space of the entity

	// Compute rotation axis in entity local space
	// Compute the transform as a matrix so we can concatenate it with the entity's current transform
	Vector rotationAxisLs;

	/* The matrix maps vectors from entity space to world space, since we have a world space 
	vector that we want in entity space we use the inverse operator VectorIRotate instead of VectorRotate
        Note, you could also invert the matrix and use VectorRotate instead */
	VectorIRotate( rotationAxisWs, pEntity->EntityToWorldTransform(), rotationAxisLs );

	/* Build a transform that rotates around that axis in local space by the angle
	If there were an AxisAngleMatrix() routine we could use that directly, but there isn't 
	So convert to a quaternion first, then a matrix */
	Quaternion q;

	// NOTE: Assumes axis is a unit vector, non-unit vectors will bias the resulting rotation angle (but not the axis)
	AxisAngleQuaternion( rotationAxisLs, rotationAngle, q );
	
	// Convert to a matrix
	matrix3x4_t xform;
	QuaternionMatrix( q, vec3_origin, xform );
	
	// Apply the rotation to the entity input space (local)
	matrix3x4_t localToWorldMatrix;
	ConcatTransforms( pEntity->EntityToWorldTransform(), xform, localToWorldMatrix );

	// Extract the compound rotation as a QAngle
	QAngle localAngles;
	MatrixAngles( localToWorldMatrix, localAngles );

	// Draw the rotated box in blue
	BoxAngles( pCollide->GetCollisionOrigin(), pCollide->OBBMins(), pCollide->OBBMaxs(), localAngles, 0, 0, 255, a, flDuration );


	{

		// Example 2: Applying the rotation in world space directly

		/* Build a transform that rotates around that axis in world space by the angle
		NOTE: Add ten degrees so the boxes are separately visible
		Then compute the transform as a matrix so we can concatenate it with the entity's current transform */
		Quaternion q;
		AxisAngleQuaternion( rotationAxisWs, rotationAngle+10, q );

		// Convert to a matrix
		matrix3x4_t xform;
		QuaternionMatrix( q, vec3_origin, xform );

		// Apply the rotation to the entity output space (world)
		matrix3x4_t localToWorldMatrix;
		ConcatTransforms( xform, pEntity->EntityToWorldTransform(), localToWorldMatrix );

		// Extract the compound rotation as a QAngle
		QAngle localAngles;
		MatrixAngles( localToWorldMatrix, localAngles );

		// Draw the rotated + 10 box in yellow
		BoxAngles( pCollide->GetCollisionOrigin(), pCollide->OBBMins(), pCollide->OBBMaxs(), localAngles, 255, 255, 0, a, flDuration );
	}
}