changequote([*,*])dnl changecom([**])dnl define(_P,[*

$1*])dnl define(ENT_title,[*Implementing quaternions into Quake 3*])dnl define(ENT_amp,[*&*])dnl define(ENT_gt,[*>*])dnl define(AUTHOR,[*JackOfAllTrades*])dnl define(BYLINE,[*By $1*])dnl define(FILENAME,[*$1*])dnl define(TYPENAME,[*$1*])dnl define(VARNAME,[*$1*])dnl define(FUNCNAME,[*$1*])dnl define(VERBATIM,[*

$1
*])dnl define(FINDCODE,[*

$1
*])dnl define(ADDCODE,[*

$1
*])dnl define(CVARNAME,[*$1*])dnl define(XREF,[*ifelse([*$2*],,[*$1*],[*$2*])*])dnl ENT_title

ENT_title

_P([* BYLINE(AUTHOR) *]) _P([* One thing I've noticed lately in every FPS game I've played is that gibs don't roll and and tumble properly. Can it really be that hard to do? Well, yes it is, if you use an Euler angle orientation system (like Q3 does). After fooling around a bit I quickly discovered that it was nigh on impossible to rotate an object about an arbitrary axis using Q3's angles system. Just try it: give an object angular velocities on more than one axis and you'll end up with a weird spin/wiggle effect that looks worse than not rotating the object at all. I'd heard of quaternions before and knew that they were said to be good for describing arbitrary rotations, but that's all I knew. After a bit of googling I was ready to try my hand at applying quaternions to Q3. Here is the result. *]) _P([* Start off by adding the following quaternion manipulation functions to FILENAME(q_math.c) (these functions were adapted from Nick Bobic's quaternion article on Gamasutra). I know that modifying the FILENAME(q_*) files is generally frowned upon but I think that in this case FILENAME(q_math.c) is the logical place for these functions. *]) ADDCODE([* void AnglesToQuat (const vec3_t angles, vec4_t quat) { vec3_t a; float cr, cp, cy, sr, sp, sy, cpcy, spsy; a[PITCH] = (M_PI/360.0) * angles[PITCH]; a[YAW] = (M_PI/360.0) * angles[YAW]; a[ROLL] = (M_PI/360.0) * angles[ROLL]; cr = cos(a[ROLL]); cp = cos(a[PITCH]); cy = cos(a[YAW]); sr = sin(a[ROLL]); sp = sin(a[PITCH]); sy = sin(a[YAW]); cpcy = cp * cy; spsy = sp * sy; quat[0] = cr * cpcy + sr * spsy; // w quat[1] = sr * cpcy - cr * spsy; // x quat[2] = cr * sp * cy + sr * cp * sy; // y quat[3] = cr * cp * sy - sr * sp * cy; // z } void QuatToAxis(vec4_t q, vec3_t axis[3]) { float wx, wy, wz, xx, yy, yz, xy, xz, zz, x2, y2, z2; x2 = q[1] + q[1]; y2 = q[2] + q[2]; z2 = q[3] + q[3]; xx = q[1] * x2; xy = q[1] * y2; xz = q[1] * z2; yy = q[2] * y2; yz = q[2] * z2; zz = q[3] * z2; wx = q[0] * x2; wy = q[0] * y2; wz = q[0] * z2; axis[0][0] = 1.0 - (yy + zz); axis[1][0] = xy - wz; axis[2][0] = xz + wy; axis[0][1] = xy + wz; axis[1][1] = 1.0 - (xx + zz); axis[2][1] = yz - wx; axis[0][2] = xz - wy; axis[1][2] = yz + wx; axis[2][2] = 1.0 - (xx + yy); } void QuatMul(const vec4_t q1, const vec4_t q2, vec4_t res) { float A, B, C, D, E, F, G, H; A = (q1[0] + q1[1])*(q2[0] + q2[1]); B = (q1[3] - q1[2])*(q2[2] - q2[3]); C = (q1[0] - q1[1])*(q2[2] + q2[3]); D = (q1[2] + q1[3])*(q2[0] - q2[1]); E = (q1[1] + q1[3])*(q2[1] + q2[2]); F = (q1[1] - q1[3])*(q2[1] - q2[2]); G = (q1[0] + q1[2])*(q2[0] - q2[3]); H = (q1[0] - q1[2])*(q2[0] + q2[3]); res[0] = B + (H - E - F + G)*0.5; res[1] = A - (E + F + G + H)*0.5; res[2] = C + (E - F + G - H)*0.5; res[3] = D + (E - F - G + H)*0.5; } *]) _P([* Don't forget to put their prototypes in FILENAME(q_shared.h): *]) ADDCODE([* void AnglesToQuat (const vec3_t angles, vec4_t quat); void QuatToAxis(vec4_t q, vec3_t m[3]); void QuatMul(const vec4_t q1, const vec4_t q2, vec4_t res); *]) _P([* Also put the following macro definition in FILENAME(q_shared.h): *]) ADDCODE([* #define QuatInit(w,x,y,z,q) ((q)[0]=(w),(q)[1]=(x),(q)[2]=(y),(q)[3]=(z)); *]) _P([* We need a place to store the new quaternions so open FILENAME(cg_local.h) and add the following to the definition of the TYPENAME(localEntity_t) struct: *]) ADDCODE([* vec4_t quatOrient; vec4_t quatRot; vec3_t rotAxis; float angVel; *]) _P([* Now open FILENAME(cg_localents.c) and find the function FUNCNAME(CG_ReflectVelocity()). Inside this function add the following code at the end: *]) ADDCODE([* if (le->leFlags ENT_amp() LEF_TUMBLE) { // collided with a surface so calculate the new rotation axis CrossProduct (trace->plane.normal, velocity, le->rotAxis); le->angVel = VectorNormalize (le->rotAxis) / le->radius; // save current orientation as a rotation from model's base orientation QuatMul (le->quatOrient, le->quatRot, le->quatRot); // reset the orientation QuatInit(1,0,0,0,le->quatOrient); } *]) _P([* Next, scroll down to the function FUNCNAME(CG_AddFragment()) and find the code: *]) FINDCODE([* if ( le->leFlags ENT_amp() LEF_TUMBLE ) { vec3_t angles; BG_EvaluateTrajectory( ENT_amp()le->angles, cg.time, angles ); AnglesToAxis( angles, le->refEntity.axis ); } *]) _P([* And replace it with this: *]) ADDCODE([* if (le->leFlags ENT_amp() LEF_TUMBLE) { vec4_t qrot; // angular rotation for this frame float angle = le->angVel * (cg.time - le->angles.trTime) * 0.001/2; // create the rotation quaternion qrot[0] = cos (angle); // real part VectorScale (le->rotAxis, sin(angle), ENT_amp()qrot[1]);// imaginary part // create the new orientation QuatMul (le->quatOrient, qrot, le->quatOrient); // apply the combined previous rotations around other axes QuatMul (le->quatOrient, le->quatRot, qrot); // convert the orientation into the form the renderer wants QuatToAxis (qrot, le->refEntity.axis); le->angles.trTime = cg.time; } *]) _P([* Open FILENAME(cg_effects.c) and find the function FUNCNAME(CG_LaunchGib()). At the end of this function add the following code: *]) ADDCODE([* le->angles.trType = TR_LINEAR; le->angles.trTime = cg.time; le->angVel = 20 * crandom(); // random angular velocity le->rotAxis[0] = crandom(); // random axis of rotation le->rotAxis[1] = crandom(); le->rotAxis[2] = crandom(); VectorNormalize (le->rotAxis); // normalize the rotation axis QuatInit (1,0,0,0,le->quatRot); QuatInit (1,0,0,0,le->quatOrient); le->radius = 12; le->leFlags = LEF_TUMBLE; *]) _P([* Open FILENAME(cg_weapons.c) and find the function FUNCNAME(CG_MachineGunEjectBrass()). Find the lines: *]) FINDCODE([* le->angles.trDelta[0] = 2; le->angles.trDelta[1] = 1; le->angles.trDelta[2] = 0; *]) _P([* And replace them with: *]) ADDCODE([* AnglesToQuat (le->angles.trBase, le->quatOrient); le->angVel = 10 * random(); le->rotAxis[0] = crandom(); le->rotAxis[1] = crandom(); le->rotAxis[2] = crandom(); VectorNormalize (le->rotAxis); le->radius = 4; QuatInit (1,0,0,0,le->quatRot); *]) _P([* Scroll down to the function FUNCNAME(CG_ShotgunEjectBrass()) and find the lines: *]) FINDCODE([* le->angles.trDelta[0] = 1; le->angles.trDelta[1] = 0.5; le->angles.trDelta[2] = 0; *]) _P([* And replace them with: *]) ADDCODE([* AnglesToQuat (le->angles.trBase, le->quatOrient); le->angVel = 10 * random(); le->rotAxis[0] = crandom(); le->rotAxis[1] = crandom(); le->rotAxis[2] = crandom(); VectorNormalize (le->rotAxis); le->radius = 6; QuatInit (1,0,0,0,le->quatRot); *]) That's it. You're done. Hopefully I haven't left anything out. Compile the cgame module and try it out. You may want to play around with the VARNAME(le-ENT_gt()bounceFactor) and VARNAME(le-ENT_gt()endTime) parameters in the object spawning functions so that the objects stay airborn longer. Also, I found it useful to put the game into slow motion so it is easier to see the object motion while debugging. You can do this by modifying the CVARNAME(timescale) cvar. Values between 0.5 and 0.7 worked good for me. You also might want to temporarily increase the damage in FUNCNAME(G_Damage()) to really get the gibs a-flyin'. I left out the function to convert a quaternion back to Q3's Euler angle format because it wasn't needed in this example. Just in case you're curious or want to experiment further here it is: ADDCODE([* void QuatToAngles (const vec4_t q, vec3_t a) { vec4_t q2; q2[0] = q[0]*q[0]; q2[1] = q[1]*q[1]; q2[2] = q[2]*q[2]; q2[3] = q[3]*q[3]; a[ROLL] = (180.0/M_PI)*atan2 (2*(q[2]*q[3] + q[1]*q[0]) , (-q2[1] - q2[2] + q2[3] + q2[0])); a[PITCH] = (180.0/M_PI)*asin (-2*(q[1]*q[3] - q[2]*q[0])); a[YAW] = (180.0/M_PI)*atan2 (2*(q[1]*q[2] + q[3]*q[0]) , (q2[1] - q2[2] - q2[3] + q2[0])); } *]) Original post: XREF([*http://www.quake3world.com/ubb/Forum4/HTML/006766.html?*])
XREF([*http://glossyshit.tripod.ca/quadtut.htm*],Converted into HTML )by glossy
glossyshit@hotmail.com

Mangled with CSS by PhaethonH
PhaethonH@gmail.com

Permission has been granted by AUTHOR to make verbatim copies of this document and make modifications as needed (which allowed this marked-up version).