void msModel::SetupJoints() { for (size_t i = 0; i < m_joints.size(); i++) { ms3d_joint_t *joint = &m_joints[i]; joint->parentIndex = FindJointByName(joint->parentName); } for (size_t i = 0; i < m_joints.size(); i++) { ms3d_joint_t *joint = &m_joints[i]; AngleMatrix(joint->rot, joint->matLocalSkeleton); joint->matLocalSkeleton[0][3]= joint->pos[0]; joint->matLocalSkeleton[1][3]= joint->pos[1]; joint->matLocalSkeleton[2][3]= joint->pos[2]; if (joint->parentIndex == -1) { memcpy(joint->matGlobalSkeleton, joint->matLocalSkeleton, sizeof(joint->matGlobalSkeleton)); } else { ms3d_joint_t *parentJoint = &m_joints[joint->parentIndex]; R_ConcatTransforms(parentJoint->matGlobalSkeleton, joint->matLocalSkeleton, joint->matGlobalSkeleton); } SetupTangents(); } }
/* ================ R_AliasSetUpTransform ================ */ static void R_AliasSetUpTransform(const entity_t *currententity) { int i; static float viewmatrix[3][4]; // TODO: should really be stored with the entity instead of being reconstructed // TODO: should use a look-up table // TODO: could cache lazily, stored in the entity // // AngleVectors never change angles, we can convert from const AngleVectors((float *)currententity->angles, s_alias_forward, s_alias_right, s_alias_up ); // TODO: can do this with simple matrix rearrangement memset( aliasworldtransform, 0, sizeof( aliasworldtransform ) ); memset( aliasoldworldtransform, 0, sizeof( aliasworldtransform ) ); for (i=0 ; i<3 ; i++) { aliasoldworldtransform[i][0] = aliasworldtransform[i][0] = s_alias_forward[i]; aliasoldworldtransform[i][0] = aliasworldtransform[i][1] = -s_alias_right[i]; aliasoldworldtransform[i][0] = aliasworldtransform[i][2] = s_alias_up[i]; } aliasworldtransform[0][3] = currententity->origin[0]-r_origin[0]; aliasworldtransform[1][3] = currententity->origin[1]-r_origin[1]; aliasworldtransform[2][3] = currententity->origin[2]-r_origin[2]; aliasoldworldtransform[0][3] = currententity->oldorigin[0]-r_origin[0]; aliasoldworldtransform[1][3] = currententity->oldorigin[1]-r_origin[1]; aliasoldworldtransform[2][3] = currententity->oldorigin[2]-r_origin[2]; // FIXME: can do more efficiently than full concatenation // memcpy( rotationmatrix, t2matrix, sizeof( rotationmatrix ) ); // R_ConcatTransforms (t2matrix, tmatrix, rotationmatrix); // TODO: should be global, set when vright, etc., set VectorCopy (vright, viewmatrix[0]); VectorCopy (vup, viewmatrix[1]); VectorInverse (viewmatrix[1]); VectorCopy (vpn, viewmatrix[2]); viewmatrix[0][3] = 0; viewmatrix[1][3] = 0; viewmatrix[2][3] = 0; // memcpy( aliasworldtransform, rotationmatrix, sizeof( aliastransform ) ); R_ConcatTransforms (viewmatrix, aliasworldtransform, aliastransform); aliasworldtransform[0][3] = currententity->origin[0]; aliasworldtransform[1][3] = currententity->origin[1]; aliasworldtransform[2][3] = currententity->origin[2]; aliasoldworldtransform[0][3] = currententity->oldorigin[0]; aliasoldworldtransform[1][3] = currententity->oldorigin[1]; aliasoldworldtransform[2][3] = currententity->oldorigin[2]; }
static void R_IQMSetUpTransform (int trivial_accept) { int i; float rotationmatrix[3][4]; static float viewmatrix[3][4]; vec3_t forward, left, up; VectorCopy (currententity->transform + 0, forward); VectorCopy (currententity->transform + 4, left); VectorCopy (currententity->transform + 8, up); // TODO: can do this with simple matrix rearrangement for (i = 0; i < 3; i++) { rotationmatrix[i][0] = forward[i]; rotationmatrix[i][1] = left[i]; rotationmatrix[i][2] = up[i]; } rotationmatrix[0][3] = -modelorg[0]; rotationmatrix[1][3] = -modelorg[1]; rotationmatrix[2][3] = -modelorg[2]; // TODO: should be global, set when vright, etc., set VectorCopy (vright, viewmatrix[0]); VectorCopy (vup, viewmatrix[1]); VectorNegate (viewmatrix[1], viewmatrix[1]); VectorCopy (vpn, viewmatrix[2]); // viewmatrix[0][3] = 0; // viewmatrix[1][3] = 0; // viewmatrix[2][3] = 0; R_ConcatTransforms (viewmatrix, rotationmatrix, aliastransform); // do the scaling up of x and y to screen coordinates as part of the transform // for the unclipped case (it would mess up clipping in the clipped case). // Also scale down z, so 1/z is scaled 31 bits for free, and scale down x and y // correspondingly so the projected x and y come out right // FIXME: make this work for clipped case too? if (trivial_accept) { for (i = 0; i < 4; i++) { aliastransform[0][i] *= aliasxscale * (1.0 / ((float) 0x8000 * 0x10000)); aliastransform[1][i] *= aliasyscale * (1.0 / ((float) 0x8000 * 0x10000)); aliastransform[2][i] *= 1.0 / ((float) 0x8000 * 0x10000); } } }
/* ================ R_AliasSetUpTransform ================ */ void R_AliasSetUpTransform(int trivial_accept) { int i; float rotationmatrix[3][4], t2matrix[3][4]; static float tmatrix[3][4]; static float viewmatrix[3][4]; vec3_t angles; // TODO: should really be stored with the entity instead of being reconstructed // TODO: should use a look-up table // TODO: could cache lazily, stored in the entity angles[ROLL] = currententity->angles[ROLL]; angles[PITCH] = -currententity->angles[PITCH]; angles[YAW] = currententity->angles[YAW]; AngleVectors(angles, alias_forward, alias_right, alias_up); tmatrix[0][0] = pmdl->scale[0]; tmatrix[1][1] = pmdl->scale[1]; tmatrix[2][2] = pmdl->scale[2]; tmatrix[0][3] = pmdl->scale_origin[0]; tmatrix[1][3] = pmdl->scale_origin[1]; tmatrix[2][3] = pmdl->scale_origin[2]; // TODO: can do this with simple matrix rearrangement for (i=0 ; i<3 ; i++) { t2matrix[i][0] = alias_forward[i]; t2matrix[i][1] = -alias_right[i]; t2matrix[i][2] = alias_up[i]; } t2matrix[0][3] = -modelorg[0]; t2matrix[1][3] = -modelorg[1]; t2matrix[2][3] = -modelorg[2]; // FIXME: can do more efficiently than full concatenation R_ConcatTransforms(t2matrix, tmatrix, rotationmatrix); // TODO: should be global, set when vright, etc., set VectorCopy(vright, viewmatrix[0]); VectorCopy(vup, viewmatrix[1]); VectorInverse(viewmatrix[1]); VectorCopy(vpn, viewmatrix[2]); // viewmatrix[0][3] = 0; // viewmatrix[1][3] = 0; // viewmatrix[2][3] = 0; R_ConcatTransforms(viewmatrix, rotationmatrix, aliastransform); // do the scaling up of x and y to screen coordinates as part of the transform // for the unclipped case (it would mess up clipping in the clipped case). // Also scale down z, so 1/z is scaled 31 bits for free, and scale down x and y // correspondingly so the projected x and y come out right // FIXME: make this work for clipped case too? if (trivial_accept) { for (i=0 ; i<4 ; i++) { aliastransform[0][i] *= aliasxscale * (1.0 / ((float)0x8000 * 0x10000)); aliastransform[1][i] *= aliasyscale * (1.0 / ((float)0x8000 * 0x10000)); aliastransform[2][i] *= 1.0 / ((float)0x8000 * 0x10000); } } }
void sw32_R_AliasSetUpTransform (int trivial_accept) { int i; float rotationmatrix[3][4], t2matrix[3][4]; static float tmatrix[3][4]; static float viewmatrix[3][4]; VectorCopy (currententity->transform + 0, alias_forward); VectorNegate (currententity->transform + 4, alias_right); VectorCopy (currententity->transform + 8, alias_up); tmatrix[0][0] = pmdl->scale[0]; tmatrix[1][1] = pmdl->scale[1]; tmatrix[2][2] = pmdl->scale[2]; tmatrix[0][3] = pmdl->scale_origin[0]; tmatrix[1][3] = pmdl->scale_origin[1]; tmatrix[2][3] = pmdl->scale_origin[2]; // TODO: can do this with simple matrix rearrangement for (i = 0; i < 3; i++) { t2matrix[i][0] = alias_forward[i]; t2matrix[i][1] = -alias_right[i]; t2matrix[i][2] = alias_up[i]; } t2matrix[0][3] = -modelorg[0]; t2matrix[1][3] = -modelorg[1]; t2matrix[2][3] = -modelorg[2]; // FIXME: can do more efficiently than full concatenation R_ConcatTransforms (t2matrix, tmatrix, rotationmatrix); // TODO: should be global, set when vright, etc., set VectorCopy (vright, viewmatrix[0]); VectorCopy (vup, viewmatrix[1]); VectorNegate (viewmatrix[1], viewmatrix[1]); VectorCopy (vpn, viewmatrix[2]); // viewmatrix[0][3] = 0; // viewmatrix[1][3] = 0; // viewmatrix[2][3] = 0; R_ConcatTransforms (viewmatrix, rotationmatrix, sw32_aliastransform); // do the scaling up of x and y to screen coordinates as part of the transform // for the unclipped case (it would mess up clipping in the clipped case). // Also scale down z, so 1/z is scaled 31 bits for free, and scale down x and y // correspondingly so the projected x and y come out right // FIXME: make this work for clipped case too? if (trivial_accept) { for (i = 0; i < 4; i++) { sw32_aliastransform[0][i] *= sw32_aliasxscale * (1.0 / ((float) 0x8000 * 0x10000)); sw32_aliastransform[1][i] *= sw32_aliasyscale * (1.0 / ((float) 0x8000 * 0x10000)); sw32_aliastransform[2][i] *= 1.0 / ((float) 0x8000 * 0x10000); } } }
void StudioModel::SetUpBones ( void ) { int i; mstudiobone_t *pbones; mstudioseqdesc_t *pseqdesc; mstudioanim_t *panim; static vec3_t pos[MAXSTUDIOBONES]; float bonematrix[3][4]; static vec4_t q[MAXSTUDIOBONES]; static vec3_t pos2[MAXSTUDIOBONES]; static vec4_t q2[MAXSTUDIOBONES]; static vec3_t pos3[MAXSTUDIOBONES]; static vec4_t q3[MAXSTUDIOBONES]; static vec3_t pos4[MAXSTUDIOBONES]; static vec4_t q4[MAXSTUDIOBONES]; if (m_sequence >= m_pstudiohdr->numseq) { m_sequence = 0; } pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + m_sequence; panim = GetAnim( pseqdesc ); CalcRotations( pos, q, pseqdesc, panim, m_frame ); if (pseqdesc->numblends > 1) { float s; panim += m_pstudiohdr->numbones; CalcRotations( pos2, q2, pseqdesc, panim, m_frame ); s = m_blending[0] / 255.0; SlerpBones( q, pos, q2, pos2, s ); if (pseqdesc->numblends == 4) { panim += m_pstudiohdr->numbones; CalcRotations( pos3, q3, pseqdesc, panim, m_frame ); panim += m_pstudiohdr->numbones; CalcRotations( pos4, q4, pseqdesc, panim, m_frame ); s = m_blending[0] / 255.0; SlerpBones( q3, pos3, q4, pos4, s ); s = m_blending[1] / 255.0; SlerpBones( q, pos, q3, pos3, s ); } } pbones = (mstudiobone_t *)((byte *)m_pstudiohdr + m_pstudiohdr->boneindex); for (i = 0; i < m_pstudiohdr->numbones; i++) { QuaternionMatrix( q[i], bonematrix ); bonematrix[0][3] = pos[i][0]; bonematrix[1][3] = pos[i][1]; bonematrix[2][3] = pos[i][2]; if (pbones[i].parent == -1) { memcpy(g_bonetransform[i], bonematrix, sizeof(float) * 12); } else { R_ConcatTransforms (g_bonetransform[pbones[i].parent], bonematrix, g_bonetransform[i]); } } }
/* ================ R_AliasSetUpTransform ================ */ static void R_AliasSetUpTransform (int trivial_accept) { int i; float rotationmatrix[3][4], t2matrix[3][4]; static float tmatrix[3][4]; static float viewmatrix[3][4]; vec3_t angles; float entScale; float xyfact = 1.0, zfact = 1.0; // avoid compiler warning float forward; float yaw, pitch; // TODO: should really be stored with the entity instead of being reconstructed // TODO: should use a look-up table // TODO: could cache lazily, stored in the entity if (currententity->model->flags & EF_FACE_VIEW) { VectorSubtract(currententity->origin,r_origin,angles); VectorSubtract(r_origin,currententity->origin,angles); VectorNormalize(angles); if (angles[1] == 0 && angles[0] == 0) { yaw = 0; if (angles[2] > 0) pitch = 90; else pitch = 270; } else { yaw = (int) (atan2(angles[1], angles[0]) * 180 / M_PI); if (yaw < 0) yaw += 360; forward = sqrt (angles[0]*angles[0] + angles[1]*angles[1]); pitch = (int) (atan2(angles[2], forward) * 180 / M_PI); if (pitch < 0) pitch += 360; } angles[0] = -pitch; angles[1] = yaw; // angles[2] = 0; angles[2] = currententity->angles[ROLL]; } else { angles[ROLL] = currententity->angles[ROLL]; angles[PITCH] = -currententity->angles[PITCH]; if (currententity->model->flags & EF_ROTATE) { angles[YAW] = anglemod( (currententity->origin[0] + currententity->origin[1])*0.8 + (108*cl.time) ); } else { angles[YAW] = currententity->angles[YAW]; } } // For clientside rotation stuff angles[0] += currententity->angleAdd[0]; angles[1] += currententity->angleAdd[1]; angles[2] += currententity->angleAdd[2]; AngleVectors (angles, alias_forward, alias_right, alias_up); if (currententity->scale != 0 && currententity->scale != 100) { entScale = (float)currententity->scale/100.0; switch (currententity->drawflags & SCALE_TYPE_MASKIN) { case SCALE_TYPE_UNIFORM: tmatrix[0][0] = pmdl->scale[0]*entScale; tmatrix[1][1] = pmdl->scale[1]*entScale; tmatrix[2][2] = pmdl->scale[2]*entScale; xyfact = zfact = (entScale-1.0)*127.95; break; case SCALE_TYPE_XYONLY: tmatrix[0][0] = pmdl->scale[0]*entScale; tmatrix[1][1] = pmdl->scale[1]*entScale; tmatrix[2][2] = pmdl->scale[2]; xyfact = (entScale-1.0)*127.95; zfact = 1.0; break; case SCALE_TYPE_ZONLY: tmatrix[0][0] = pmdl->scale[0]; tmatrix[1][1] = pmdl->scale[1]; tmatrix[2][2] = pmdl->scale[2]*entScale; xyfact = 1.0; zfact = (entScale-1.0)*127.95; break; } switch (currententity->drawflags & SCALE_ORIGIN_MASKIN) { case SCALE_ORIGIN_CENTER: tmatrix[0][3] = pmdl->scale_origin[0]-pmdl->scale[0]*xyfact; tmatrix[1][3] = pmdl->scale_origin[1]-pmdl->scale[1]*xyfact; tmatrix[2][3] = pmdl->scale_origin[2]-pmdl->scale[2]*zfact; break; case SCALE_ORIGIN_BOTTOM: tmatrix[0][3] = pmdl->scale_origin[0]-pmdl->scale[0]*xyfact; tmatrix[1][3] = pmdl->scale_origin[1]-pmdl->scale[1]*xyfact; tmatrix[2][3] = pmdl->scale_origin[2]; break; case SCALE_ORIGIN_TOP: tmatrix[0][3] = pmdl->scale_origin[0]-pmdl->scale[0]*xyfact; tmatrix[1][3] = pmdl->scale_origin[1]-pmdl->scale[1]*xyfact; tmatrix[2][3] = pmdl->scale_origin[2]-pmdl->scale[2]*zfact*2.0; break; } } else { tmatrix[0][0] = pmdl->scale[0]; tmatrix[1][1] = pmdl->scale[1]; tmatrix[2][2] = pmdl->scale[2]; tmatrix[0][3] = pmdl->scale_origin[0]; tmatrix[1][3] = pmdl->scale_origin[1]; tmatrix[2][3] = pmdl->scale_origin[2]; } if (currententity->model->flags & EF_ROTATE) { // Floating motion tmatrix[2][3] += sin(currententity->origin[0] + currententity->origin[1] + (cl.time*3)) * 5.5; } // TODO: can do this with simple matrix rearrangement for (i = 0 ; i < 3 ; i++) { t2matrix[i][0] = alias_forward[i]; t2matrix[i][1] = -alias_right[i]; t2matrix[i][2] = alias_up[i]; } t2matrix[0][3] = -modelorg[0]; t2matrix[1][3] = -modelorg[1]; t2matrix[2][3] = -modelorg[2]; // FIXME: can do more efficiently than full concatenation R_ConcatTransforms (t2matrix, tmatrix, rotationmatrix); // TODO: should be global, set when vright, etc., set VectorCopy (vright, viewmatrix[0]); VectorCopy (vup, viewmatrix[1]); VectorInverse (viewmatrix[1]); VectorCopy (vpn, viewmatrix[2]); // viewmatrix[0][3] = 0; // viewmatrix[1][3] = 0; // viewmatrix[2][3] = 0; R_ConcatTransforms (viewmatrix, rotationmatrix, aliastransform); // do the scaling up of x and y to screen coordinates as part of the transform // for the unclipped case (it would mess up clipping in the clipped case). // Also scale down z, so 1/z is scaled 31 bits for free, and scale down x and y // correspondingly so the projected x and y come out right // FIXME: make this work for clipped case too? if (trivial_accept) { for (i = 0 ; i < 4 ; i++) { aliastransform[0][i] *= aliasxscale * (1.0 / ((float)0x8000 * 0x10000)); aliastransform[1][i] *= aliasyscale * (1.0 / ((float)0x8000 * 0x10000)); aliastransform[2][i] *= 1.0 / ((float)0x8000 * 0x10000); } } }
/* <836d8> ../engine/r_studio.c:696 */ void EXT_FUNC SV_StudioSetupBones(model_t *pModel, float frame, int sequence, const vec_t *angles, const vec_t *origin, const unsigned char *pcontroller, const unsigned char *pblending, int iBone, const edict_t *edict) { static vec4_t q1[128]; static vec3_t pos1[128]; static vec4_t q2[128]; static vec3_t pos2[128]; int chainlength; mstudiobone_t *pbones; mstudioseqdesc_t *pseqdesc; int chain[128]; float bonematrix[3][4]; mstudioanim_t *panim; float f; float adj[8]; float s; chainlength = 0; if (sequence < 0 || sequence >= pstudiohdr->numseq) { Con_DPrintf("SV_StudioSetupBones: sequence %i/%i out of range for model %s\n", sequence, pstudiohdr->numseq, pstudiohdr->name); sequence = 0; } pbones = (mstudiobone_t *)((char *)pstudiohdr + pstudiohdr->boneindex); pseqdesc = (mstudioseqdesc_t *)((char *)pstudiohdr + pstudiohdr->seqindex); pseqdesc += sequence; panim = R_GetAnim(pModel, pseqdesc); if (iBone < -1 || iBone >= pstudiohdr->numbones) iBone = 0; if (iBone != -1) { do { chain[chainlength++] = iBone; iBone = pbones[iBone].parent; } while (iBone != -1); } else { chainlength = pstudiohdr->numbones; for (int i = 0; i < pstudiohdr->numbones; i++) { chain[pstudiohdr->numbones - 1 - i] = i; } } f = (pseqdesc->numframes > 1) ? (pseqdesc->numframes - 1) * frame / 256.0f : 0.0f; R_StudioCalcBoneAdj(0.0, adj, pcontroller, pcontroller, 0); s = f - (float)(int)f; for (int i = chainlength - 1; i >= 0; i--) { int bone = chain[i]; R_StudioCalcBoneQuaterion((int)f, s, &pbones[bone], &panim[bone], adj, q1[bone]); R_StudioCalcBonePosition((int)f, s, &pbones[bone], &panim[bone], adj, pos1[bone]); } if (pseqdesc->numblends > 1) { panim = R_GetAnim(pModel, pseqdesc); panim += pstudiohdr->numbones; for (int i = chainlength - 1; i >= 0; i--) { int bone = chain[i]; R_StudioCalcBoneQuaterion((int)f, s, &pbones[bone], &panim[bone], adj, q2[bone]); R_StudioCalcBonePosition((int)f, s, &pbones[bone], &panim[bone], adj, pos2[bone]); } R_StudioSlerpBones(q1, pos1, q2, pos2, *pblending / 255.0f); } AngleMatrix(angles, rotationmatrix); rotationmatrix[0][3] = origin[0]; rotationmatrix[1][3] = origin[1]; rotationmatrix[2][3] = origin[2]; for (int i = chainlength - 1; i >= 0; i--) { int bone = chain[i]; int parent = pbones[bone].parent; QuaternionMatrix(q1[bone], bonematrix); bonematrix[0][3] = pos1[bone][0]; bonematrix[1][3] = pos1[bone][1]; bonematrix[2][3] = pos1[bone][2]; R_ConcatTransforms((parent == -1) ? rotationmatrix : bonetransform[parent], bonematrix, bonetransform[bone]); } }
void msModel::EvaluateJoint(int index, float frame) { ms3d_joint_t *joint = &m_joints[index]; // // calculate joint animation matrix, this matrix will animate matLocalSkeleton // vec3_t pos = { 0.0f, 0.0f, 0.0f }; int numPositionKeys = (int) joint->positionKeys.size(); if (numPositionKeys > 0) { int i1 = -1; int i2 = -1; // find the two keys, where "frame" is in between for the position channel for (int i = 0; i < (numPositionKeys - 1); i++) { if (frame >= joint->positionKeys[i].time && frame < joint->positionKeys[i + 1].time) { i1 = i; i2 = i + 1; break; } } // if there are no such keys if (i1 == -1 || i2 == -1) { // either take the first if (frame < joint->positionKeys[0].time) { pos[0] = joint->positionKeys[0].key[0]; pos[1] = joint->positionKeys[0].key[1]; pos[2] = joint->positionKeys[0].key[2]; } // or the last key else if (frame >= joint->positionKeys[numPositionKeys - 1].time) { pos[0] = joint->positionKeys[numPositionKeys - 1].key[0]; pos[1] = joint->positionKeys[numPositionKeys - 1].key[1]; pos[2] = joint->positionKeys[numPositionKeys - 1].key[2]; } } // there are such keys, so interpolate using hermite interpolation else { ms3d_keyframe_t *p0 = &joint->positionKeys[i1]; ms3d_keyframe_t *p1 = &joint->positionKeys[i2]; ms3d_tangent_t *m0 = &joint->tangents[i1]; ms3d_tangent_t *m1 = &joint->tangents[i2]; // normalize the time between the keys into [0..1] float t = (frame - joint->positionKeys[i1].time) / (joint->positionKeys[i2].time - joint->positionKeys[i1].time); float t2 = t * t; float t3 = t2 * t; // calculate hermite basis float h1 = 2.0f * t3 - 3.0f * t2 + 1.0f; float h2 = -2.0f * t3 + 3.0f * t2; float h3 = t3 - 2.0f * t2 + t; float h4 = t3 - t2; // do hermite interpolation pos[0] = h1 * p0->key[0] + h3 * m0->tangentOut[0] + h2 * p1->key[0] + h4 * m1->tangentIn[0]; pos[1] = h1 * p0->key[1] + h3 * m0->tangentOut[1] + h2 * p1->key[1] + h4 * m1->tangentIn[1]; pos[2] = h1 * p0->key[2] + h3 * m0->tangentOut[2] + h2 * p1->key[2] + h4 * m1->tangentIn[2]; } } vec4_t quat = { 0.0f, 0.0f, 0.0f, 1.0f }; int numRotationKeys = (int) joint->rotationKeys.size(); if (numRotationKeys > 0) { int i1 = -1; int i2 = -1; // find the two keys, where "frame" is in between for the rotation channel for (int i = 0; i < (numRotationKeys - 1); i++) { if (frame >= joint->rotationKeys[i].time && frame < joint->rotationKeys[i + 1].time) { i1 = i; i2 = i + 1; break; } } // if there are no such keys if (i1 == -1 || i2 == -1) { // either take the first key if (frame < joint->rotationKeys[0].time) { AngleQuaternion(joint->rotationKeys[0].key, quat); } // or the last key else if (frame >= joint->rotationKeys[numRotationKeys - 1].time) { AngleQuaternion(joint->rotationKeys[numRotationKeys - 1].key, quat); } } // there are such keys, so do the quaternion slerp interpolation else { float t = (frame - joint->rotationKeys[i1].time) / (joint->rotationKeys[i2].time - joint->rotationKeys[i1].time); vec4_t q1; AngleQuaternion(joint->rotationKeys[i1].key, q1); vec4_t q2; AngleQuaternion(joint->rotationKeys[i2].key, q2); QuaternionSlerp(q1, q2, t, quat); } } // make a matrix from pos/quat float matAnimate[3][4]; QuaternionMatrix(quat, matAnimate); matAnimate[0][3] = pos[0]; matAnimate[1][3] = pos[1]; matAnimate[2][3] = pos[2]; // animate the local joint matrix using: matLocal = matLocalSkeleton * matAnimate R_ConcatTransforms(joint->matLocalSkeleton, matAnimate, joint->matLocal); // build up the hierarchy if joints // matGlobal = matGlobal(parent) * matLocal if (joint->parentIndex == -1) { memcpy(joint->matGlobal, joint->matLocal, sizeof(joint->matGlobal)); } else { ms3d_joint_t *parentJoint = &m_joints[joint->parentIndex]; R_ConcatTransforms(parentJoint->matGlobal, joint->matLocal, joint->matGlobal); } }