From 6ab3bd45e5e0b12b2cb2c2ee96aab9776ce6640c Mon Sep 17 00:00:00 2001
From: Bill Currie <bill@taniwha.org>
Date: Thu, 10 May 2012 20:38:39 +0900
Subject: [PATCH] Implement IQM animation loading.

Bone poses are converted to dual quaternions + shear + scale for nice
skinning. Will likely be slow for software skinning, but too bad.
---
 include/QF/iqm.h            |   5 ++
 libs/models/iqm/model_iqm.c | 112 +++++++++++++++++++++++++++++++++++-
 2 files changed, 116 insertions(+), 1 deletion(-)

diff --git a/include/QF/iqm.h b/include/QF/iqm.h
index 974741d2e..da9f01d03 100644
--- a/include/QF/iqm.h
+++ b/include/QF/iqm.h
@@ -123,6 +123,9 @@ typedef struct {
 } iqmframe_t;
 
 typedef struct {
+	char       *text;
+	int         num_meshes;
+	iqmmesh    *meshes;
 	byte       *vertices;
 	uint16_t   *elements;
 	int         num_arrays;
@@ -133,6 +136,8 @@ typedef struct {
 	mat4_t     *inverse_baseframe;
 	int         num_frames;
 	iqmframe_t **frames;
+	int         num_anims;
+	iqmanim    *anims;
 } iqm_t;
 
 #endif//__QF_iqm_h__
diff --git a/libs/models/iqm/model_iqm.c b/libs/models/iqm/model_iqm.c
index 33dca1d8e..aa4421a9a 100644
--- a/libs/models/iqm/model_iqm.c
+++ b/libs/models/iqm/model_iqm.c
@@ -348,6 +348,9 @@ load_iqm_meshes (model_t *mod, const iqmheader *hdr, byte *buffer)
 		VectorCopy (tris[i].vertex, iqm->elements + i * 3);
 	if (!(meshes = get_meshes (hdr, buffer)))
 		return false;
+	iqm->num_meshes = hdr->num_meshes;
+	iqm->meshes = malloc (hdr->num_meshes * sizeof (iqmmesh));
+	memcpy (iqm->meshes, meshes, hdr->num_meshes * sizeof (iqmmesh));
 	if (!(joints = get_joints (hdr, buffer)))
 		return false;
 	iqm->num_joints = hdr->num_joints;
@@ -370,8 +373,110 @@ load_iqm_meshes (model_t *mod, const iqmheader *hdr, byte *buffer)
 }
 
 static qboolean
-load_iqm_anims (model_t *mod, iqmheader *hdr, byte *buffer)
+load_iqm_anims (model_t *mod, const iqmheader *hdr, byte *buffer)
 {
+	iqm_t      *iqm = (iqm_t *) mod->aliashdr;
+	iqmanim    *anims;
+	iqmpose    *poses;
+	uint16_t   *framedata;
+	uint32_t    i, j;
+
+	if (hdr->num_poses != hdr->num_joints)
+		return false;
+
+	iqm->num_anims = hdr->num_anims;
+	iqm->anims = malloc (hdr->num_anims * sizeof (iqmanim));
+	anims = (iqmanim *) (buffer + hdr->ofs_anims);
+	for (i = 0; i < hdr->num_anims; i++) {
+		iqm->anims[i].name = LittleLong (anims[i].name);
+		iqm->anims[i].first_frame = LittleLong (anims[i].first_frame);
+		iqm->anims[i].num_frames = LittleLong (anims[i].num_frames);
+		iqm->anims[i].framerate = LittleFloat (anims[i].framerate);
+		iqm->anims[i].flags = LittleLong (anims[i].flags);
+	}
+
+	poses = (iqmpose *) (buffer + hdr->ofs_poses);
+	for (i = 0; i < hdr->num_poses; i++) {
+		poses[i].parent = LittleLong (poses[i].parent);
+		poses[i].mask = LittleLong (poses[i].mask);
+		for (j = 0; j < 10; j++) {
+			poses[i].channeloffset[j] = LittleFloat(poses[i].channeloffset[j]);
+			poses[i].channelscale[j] = LittleFloat (poses[i].channelscale[j]);
+		}
+	}
+
+	framedata = (uint16_t *) (buffer + hdr->ofs_frames);
+	for (i = 0; i < hdr->num_frames * hdr->num_framechannels; i++)
+		framedata[i] = LittleShort (framedata[i]);
+
+	iqm->num_frames = hdr->num_frames;
+	iqm->frames = malloc (hdr->num_frames * sizeof (iqmframe_t *));
+	iqm->frames[0] = malloc (hdr->num_frames * hdr->num_poses
+							 * sizeof (iqmframe_t));
+
+	for (i = 0; i < hdr->num_frames; i++) {
+		iqm->frames[i] = iqm->frames[0] + i * hdr->num_poses;
+		for (j = 0; j < hdr->num_poses; j++) {
+			iqmframe_t *frame = &iqm->frames[i][j];
+			iqmpose    *p = &poses[j];
+			quat_t      rotation;
+			vec3_t      scale, translation;
+			mat4_t      mat;
+
+			translation[0] = p->channeloffset[0];
+			if (p->mask & 0x01)
+				translation[0] += *framedata++ * p->channelscale[0];
+			translation[1] = p->channeloffset[1];
+			if (p->mask & 0x02)
+				translation[1] += *framedata++ * p->channelscale[1];
+			translation[2] = p->channeloffset[2];
+			if (p->mask & 0x04)
+				translation[2] += *framedata++ * p->channelscale[2];
+
+			// QF's quaternions are wxyz while IQM's quaternions are xyzw
+			rotation[1] = p->channeloffset[3];
+			if (p->mask & 0x08)
+				rotation[1] += *framedata++ * p->channelscale[3];
+			rotation[2] = p->channeloffset[4];
+			if (p->mask & 0x10)
+				rotation[2] += *framedata++ * p->channelscale[4];
+			rotation[3] = p->channeloffset[5];
+			if (p->mask & 0x20)
+				rotation[3] += *framedata++ * p->channelscale[5];
+			rotation[0] = p->channeloffset[6];
+			if (p->mask & 0x40)
+				rotation[0] += *framedata++ * p->channelscale[6];
+
+			scale[0] = p->channeloffset[7];
+			if (p->mask & 0x01)
+				scale[0] += *framedata++ * p->channelscale[7];
+			scale[1] = p->channeloffset[8];
+			if (p->mask & 0x02)
+				scale[1] += *framedata++ * p->channelscale[8];
+			scale[2] = p->channeloffset[9];
+			if (p->mask & 0x04)
+				scale[2] += *framedata++ * p->channelscale[9];
+
+			Mat4Init (rotation, scale, translation, mat);
+			if (p->parent >= 0)
+				Mat4Mult (iqm->baseframe[p->parent], mat, mat);
+			Mat4Mult (mat, iqm->inverse_baseframe[j], mat);
+			// convert the matrix to dual quaternion + shear + scale
+			Mat4Decompose (mat, frame->rt.q0.q, frame->shear, frame->scale,
+						   frame->rt.qe.sv.v);
+			// apply the inverse of scale and shear to translation so
+			// everything works out properly in the shader.
+			// Normally v' = T*Sc*Sh*R*v, but with the dual quaternion, we get
+			// v' = Sc*Sh*T'*R*v
+			VectorCompDiv (frame->rt.qe.sv.v, frame->scale, frame->rt.qe.sv.v);
+			VectorUnshear (frame->shear, frame->rt.qe.sv.v, frame->rt.qe.sv.v);
+			// Dual quaternions need 1/2 translation.
+			VectorScale (frame->rt.qe.sv.v, 0.5, frame->rt.qe.sv.v);
+			frame->rt.qe.sv.s = 0;
+			// and tranlation * rotation
+			QuatMult (frame->rt.qe.q, frame->rt.q0.q, frame->rt.qe.q);
+		}
+	}
 	return true;
 }
 
@@ -379,6 +484,7 @@ void
 Mod_LoadIQM (model_t *mod, void *buffer)
 {
 	iqmheader  *hdr = (iqmheader *) buffer;
+	iqm_t      *iqm;
 	uint32_t   *swap;
 
 	if (!strequal (hdr->magic, IQM_MAGIC))
@@ -392,6 +498,10 @@ Mod_LoadIQM (model_t *mod, void *buffer)
 				   hdr->version);
 	if (hdr->filesize != (uint32_t) qfs_filesize)
 		Sys_Error ("%s: invalid filesize", loadname);
+	iqm = calloc (1, sizeof (iqm_t));
+	iqm->text = malloc (hdr->num_text);
+	memcpy (iqm->text, (byte *) buffer + hdr->ofs_text, hdr->num_text);
+	mod->aliashdr = (aliashdr_t *) iqm;
 	if (hdr->num_meshes && !load_iqm_meshes (mod, hdr, (byte *) buffer))
 		Sys_Error ("%s: error loading meshes", loadname);
 	if (hdr->num_anims && !load_iqm_anims (mod, hdr, (byte *) buffer))