예제 #1
0
float *getChannelValue(skcHeader_t *h, const char *name, int frameNum) {

	int channelIndex;
	skcFrame_t *f;
	float *values;

	channelIndex = getChannelIndexInternal(h,name);

	if(channelIndex == -1)
		return 0;

	f = (skcFrame_t *)( (byte *)h + sizeof(*h) + sizeof(*f) * frameNum );

	values = (float*)((byte *)h+f->ofsValues);

	values += (4 * channelIndex);

	vecIndex++;
	if(vecIndex >= sizeof(vecBuf) / sizeof(vecBuf[0])) {
		vecIndex = 0;
	}

	QuatCopy(values, vecBuf[vecIndex]);
	return vecBuf[vecIndex];
}
예제 #2
0
/*
==============
RE_BlendSkeleton
==============
*/
int RE_BlendSkeleton(refSkeleton_t * skel, const refSkeleton_t * blend, float frac)
{
	int             i;
	vec3_t          lerpedOrigin;
	quat_t          lerpedQuat;
	vec3_t          bounds[2];

	if(skel->numBones != blend->numBones)
	{
		ri.Printf(PRINT_WARNING, "RE_BlendSkeleton: different number of bones %d != %d\n", skel->numBones, blend->numBones);
		return qfalse;
	}

	// lerp between the 2 bone poses
	for(i = 0; i < skel->numBones; i++)
	{
		VectorLerp(skel->bones[i].origin, blend->bones[i].origin, frac, lerpedOrigin);
		QuatSlerp(skel->bones[i].rotation, blend->bones[i].rotation, frac, lerpedQuat);

		VectorCopy(lerpedOrigin, skel->bones[i].origin);
		QuatCopy(lerpedQuat, skel->bones[i].rotation);
	}

	// calculate a bounding box in the current coordinate system
	for(i = 0; i < 3; i++)
	{
		bounds[0][i] = skel->bounds[0][i] < blend->bounds[0][i] ? skel->bounds[0][i] : blend->bounds[0][i];
		bounds[1][i] = skel->bounds[1][i] > blend->bounds[1][i] ? skel->bounds[1][i] : blend->bounds[1][i];
	}
	VectorCopy(bounds[0], skel->bounds[0]);
	VectorCopy(bounds[1], skel->bounds[1]);

	return qtrue;
}
예제 #3
0
ex_value_t *
new_quaternion_val (const float *quaternion_val)
{
	ex_value_t  val;
	memset (&val, 0, sizeof (val));
	val.type = ev_quat;
	QuatCopy (quaternion_val, val.v.quaternion_val);
	return find_value (&val);
}
예제 #4
0
float *findRotChannel(skcHeader_t *h, const char *name, int frameNum, int parentIndex) {
#if 0
	char channelName[32];
	strcpy(channelName,name);
	strcat(channelName," rot");
	return getChannelValue(h,channelName,frameNum);
#else
static int i = 0;
	static quat_t qs[1024];
	float *q;
	char channelName[32];
	float *f;
	float len;
	
	i++;
	i %= 1024;
	q = qs[i];

	strcpy(channelName,name);
	strcat(channelName," rot");
	f = getChannelValue(h,channelName,frameNum);
	if(f == 0) {
		//return quat_identity;
		QuatSet(q,0,0,0,-1);
	} else {
		QuatCopy(f,q);
	}
	//QuatInverse(q);
	////if(parentIndex == -1)
	//	QuatInverse(q);
	len = QuatNormalize(q);
	if(abs(len-1.f) > 0.1) {
		T_Error("Non-normalized quat in skc file (%f)\n",len);
	}
#if 1
	FixQuatForMD5_P(q);
#endif
	return q;
#endif
}
예제 #5
0
Vec3 TurretComponent::AbsoluteAnglesToRelativeAngles(const Vec3 absoluteAngles) const {
	quat_t torsoRotation;
	quat_t absoluteRotation;
	quat_t relativeRotation;
	vec3_t relativeAngles;

	AnglesToQuat(TorsoAngles().Data(), torsoRotation);
	AnglesToQuat(absoluteAngles.Data(), absoluteRotation);

	// This is the inverse of RelativeAnglesToAbsoluteAngles. See the comment there for details.
	quat_t inverseTorsoOrientation;
	QuatCopy(torsoRotation, inverseTorsoOrientation);
	QuatInverse(inverseTorsoOrientation);
	QuatMultiply(inverseTorsoOrientation, absoluteRotation, relativeRotation);

	QuatToAngles(relativeRotation, relativeAngles);

	/*turretLogger.Debug("AbsoluteAnglesToRelativeAngles: %s → %s. Torso angles: %s.",
		Utility::Print(absoluteAngles), Utility::Print(Vec3::Load(relativeAngles)), TorsoAngles()
	);*/

	return Vec3::Load(relativeAngles);
}
예제 #6
0
tAnim_t *appendSKC(tModel_t *m, const char *fname, float scale) {
	int len;
	skcHeader_t *h;
	skcFrame_t *f; //, *firstFrame;
	tAnim_t *out;
	tFrame_t *of;
//	const char *c;
	int i, j;
	int cFlags[512];
	bone_t baseFrame[512];
	int numAnimatedComponents;

	T_Printf("Loading MoHAA skc animation file %s...\n",fname);

	len = F_LoadBuf(fname,(byte**)&h);

	if(len == -1) {
		T_Printf("Cannot open %s\n",fname);
		return 0;
	}

	memset(cFlags,0,sizeof(cFlags));

	out = T_Malloc(sizeof(tAnim_t));
	out->frameRate = 1.f / h->frameTime;
	out->numBones = m->numBones;
	out->numFrames = h->numFrames;
	out->frames = T_Malloc(sizeof(tFrame_t)*h->numFrames);
	out->boneData = T_Malloc(sizeof(tAnimBone_t)*m->numBones);

	// copy frame bounding boxes
	f = (skcFrame_t *)( (byte *)h + sizeof(*h) );
	of = out->frames;
	for(i = 0; i < h->numFrames; i++,of++,f++) {
		//anim->frames[i].radius = f->radius;
		VectorCopy(f->bounds[1],of->maxs);
		VectorCopy(f->bounds[0],of->mins);
	}

	// detect which components changes
	for(j = 0; j < m->numBones; j++) {
		float *baseRot, *testRot;
		float *basePos, *testPos;

		basePos = findPosChannel(h,m->bones[j].name,0);
		if(basePos == 0) {
			VectorSet(baseFrame[j].p,0,0,0);
		} else {
			VectorScale(basePos,scale,basePos);
			VectorCopy(basePos,baseFrame[j].p);
			for(i = 1; i < h->numFrames; i++) {
				testPos = findPosChannel(h,m->bones[j].name,i);
				VectorScale(testPos,scale,testPos);
				// detect X change
				if(testPos[0] != basePos[0]) {
					cFlags[j] |= COMPONENT_BIT_TX;
				}
				// detect Y change
				if(testPos[1] != basePos[1]) {
					cFlags[j] |= COMPONENT_BIT_TY;
				}
				// detect Z change
				if(testPos[2] != basePos[2]) {
					cFlags[j] |= COMPONENT_BIT_TZ;
				}
			}	
		}

		baseRot = findRotChannel(h,m->bones[j].name,0,m->bones[j].parent);
		if(baseRot == 0) {
			QuatSet(baseFrame[j].q,0,0,0,-1);
		} else {
			QuatCopy(baseRot,baseFrame[j].q);
			for(i = 1; i < h->numFrames; i++) {
				testRot = findRotChannel(h,m->bones[j].name,i,m->bones[j].parent);
				// detect X change
				if(testRot[0] != baseRot[0]) {
					cFlags[j] |= COMPONENT_BIT_QX;
				}
				// detect Y change
				if(testRot[1] != baseRot[1]) {
					cFlags[j] |= COMPONENT_BIT_QY;
				}
				// detect Z change
				if(testRot[2] != baseRot[2]) {
					cFlags[j] |= COMPONENT_BIT_QZ;
				}
				// NOTE: quaternion W component is not stored at all in md5 files
			}	
		}
	}

	// count the number of animated components and copy some bone data
	numAnimatedComponents = 0;
	for(j = 0; j < m->numBones; j++) {
		//int c;

		out->boneData[j].firstComponent = numAnimatedComponents;
		//c = 0;

		for(i = 0; i < 6; i++) {
			if(cFlags[j] & (1 << i)) {
				numAnimatedComponents++;
			//	c++;
			}
		}

		//out->boneData[j].numAnimatedComponents = c;
		out->boneData[j].componentBits = cFlags[j];
		strcpy(out->boneData[j].name,m->bones[j].name);
		out->boneData[j].parent = m->bones[j].parent;
	}

	// copy results out
	out->baseFrame = T_Malloc(sizeof(bone_t)*m->numBones);
	memcpy(out->baseFrame,baseFrame,sizeof(bone_t)*m->numBones);
	out->numAnimatedComponents = numAnimatedComponents;
	of = out->frames;
	for(i = 0; i < h->numFrames; i++,of++) {
		int c;
		float *cp;

		cp = of->components = T_Malloc(numAnimatedComponents*sizeof(float));

		//c = 0;
		for(j = 0; j < m->numBones; j++) {
			float *pos, *rot;

			pos = findPosChannel(h,m->bones[j].name,i);
			if(pos) {
				VectorScale(pos,scale,pos);
				// write X change
				if(cFlags[j] & COMPONENT_BIT_TX) {
					*cp = pos[0];
					cp++;
				}
				// write Y change
				if(cFlags[j] & COMPONENT_BIT_TY) {
					*cp = pos[1];
					cp++;
				}
				// write Z change
				if(cFlags[j] & COMPONENT_BIT_TZ) {
					*cp = pos[2];
					cp++;
				}
			}

			rot = findRotChannel(h,m->bones[j].name,i,m->bones[j].parent);
			if(rot) {
				// write X change
				if(cFlags[j] & COMPONENT_BIT_QX) {
					*cp = rot[0];
					cp++;
				}
				// write Y change
				if(cFlags[j] & COMPONENT_BIT_QY) {
					*cp = rot[1];
					cp++;
				}
				// write Z change
				if(cFlags[j] & COMPONENT_BIT_QZ) {
					*cp = rot[2];
					cp++;
				}	
			}
		}

		c = cp - of->components;
		assert(c == numAnimatedComponents);
	}

#if 0
	// validate generated tAnim_t components
	for(i = 0; i < out->numFrames; i++) {
		bone_t *b = setupMD5AnimBones(out,0); 
		for(j = 0; j < m->numBones; j++, b++) {
			float *o;
			o = findRotChannel_raw(h,m->bones[j].name,i,m->bones[j].parent);
			if(o) {
				T_Printf("Generated: %f %f %f %f, original %f %f %f %f\n",b->q[0],b->q[1],b->q[2],b->q[3],
					o[0],o[1],o[2],o[3]);
			} else {
				T_Printf("Generated: %f %f %f %f, original <none>\n",b->q[0],b->q[1],b->q[2],b->q[3]);
			}

			
		}
	}

#endif

	// generate baseFrame, but only once,
	// from the first appended SKC
	if(m->baseFrame == 0) {
#if 0
		// FIXME!
		bone_t *b = setupMD5AnimBones(out,0); 
		//for(i = 0; i < m->numBones; i++) {
		//	QuatInverse(b[i].q);
		//}
#else
		bone_t b[512];
		for(i = 0; i < m->numBones; i++) {
			float *p, *q;
			
			p = findPosChannel(h,m->bones[i].name,0);
			if(p) {
				VectorScale(p,scale,b[i].p);
			} else {
				VectorSet(b[i].p,0,0,0);
			}

			q = findRotChannel(h,m->bones[i].name,0,m->bones[i].parent);
			if(q) {
				QuatCopy(q,b[i].q);
			} else {
				QuatSet(b[i].q,0,0,0,1);
			}
		}
#endif
		md5AnimateBones(m,b);
		m->baseFrame = T_Malloc(m->numBones*sizeof(bone_t));
		memcpy(m->baseFrame,b,m->numBones*sizeof(bone_t));
	}

	F_FreeBuf((byte*)h);

	T_Printf("Succesfully loaded MoHAA animation %s\n",fname);


	return out;
}
예제 #7
0
/*
==============
RE_BuildSkeleton
==============
*/
int RE_BuildSkeleton(refSkeleton_t * skel, qhandle_t hAnim, int startFrame, int endFrame, float frac, qboolean clearOrigin)
{
	skelAnimation_t *skelAnim;

	skelAnim = R_GetAnimationByHandle(hAnim);

	if(skelAnim->type == AT_MD5 && skelAnim->md5)
	{
		int             i;
		md5Animation_t *anim;
		md5Channel_t   *channel;
		md5Frame_t     *newFrame, *oldFrame;
		vec3_t          newOrigin, oldOrigin, lerpedOrigin;
		quat_t          newQuat, oldQuat, lerpedQuat;
		int             componentsApplied;

		anim = skelAnim->md5;

		// Validate the frames so there is no chance of a crash.
		// This will write directly into the entity structure, so
		// when the surfaces are rendered, they don't need to be
		// range checked again.
		/*
		   if((startFrame >= anim->numFrames) || (startFrame < 0) || (endFrame >= anim->numFrames) || (endFrame < 0))
		   {
		   ri.Printf(PRINT_DEVELOPER, "RE_BuildSkeleton: no such frame %d to %d for '%s'\n", startFrame, endFrame, anim->name);
		   //startFrame = 0;
		   //endFrame = 0;
		   }
		 */

		Q_clamp(startFrame, 0, anim->numFrames - 1);
		Q_clamp(endFrame, 0, anim->numFrames - 1);

		// compute frame pointers
		oldFrame = &anim->frames[startFrame];
		newFrame = &anim->frames[endFrame];

		// calculate a bounding box in the current coordinate system
		for(i = 0; i < 3; i++)
		{
			skel->bounds[0][i] =
				oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i];
			skel->bounds[1][i] =
				oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i];
		}

		for(i = 0, channel = anim->channels; i < anim->numChannels; i++, channel++)
		{
			// set baseframe values
			VectorCopy(channel->baseOrigin, newOrigin);
			VectorCopy(channel->baseOrigin, oldOrigin);

			QuatCopy(channel->baseQuat, newQuat);
			QuatCopy(channel->baseQuat, oldQuat);

			componentsApplied = 0;

			// update tranlation bits
			if(channel->componentsBits & COMPONENT_BIT_TX)
			{
				oldOrigin[0] = oldFrame->components[channel->componentsOffset + componentsApplied];
				newOrigin[0] = newFrame->components[channel->componentsOffset + componentsApplied];
				componentsApplied++;
			}

			if(channel->componentsBits & COMPONENT_BIT_TY)
			{
				oldOrigin[1] = oldFrame->components[channel->componentsOffset + componentsApplied];
				newOrigin[1] = newFrame->components[channel->componentsOffset + componentsApplied];
				componentsApplied++;
			}

			if(channel->componentsBits & COMPONENT_BIT_TZ)
			{
				oldOrigin[2] = oldFrame->components[channel->componentsOffset + componentsApplied];
				newOrigin[2] = newFrame->components[channel->componentsOffset + componentsApplied];
				componentsApplied++;
			}

			// update quaternion rotation bits
			if(channel->componentsBits & COMPONENT_BIT_QX)
			{
				((vec_t *) oldQuat)[0] = oldFrame->components[channel->componentsOffset + componentsApplied];
				((vec_t *) newQuat)[0] = newFrame->components[channel->componentsOffset + componentsApplied];
				componentsApplied++;
			}

			if(channel->componentsBits & COMPONENT_BIT_QY)
			{
				((vec_t *) oldQuat)[1] = oldFrame->components[channel->componentsOffset + componentsApplied];
				((vec_t *) newQuat)[1] = newFrame->components[channel->componentsOffset + componentsApplied];
				componentsApplied++;
			}

			if(channel->componentsBits & COMPONENT_BIT_QZ)
			{
				((vec_t *) oldQuat)[2] = oldFrame->components[channel->componentsOffset + componentsApplied];
				((vec_t *) newQuat)[2] = newFrame->components[channel->componentsOffset + componentsApplied];
			}

			QuatCalcW(oldQuat);
			QuatNormalize(oldQuat);

			QuatCalcW(newQuat);
			QuatNormalize(newQuat);

#if 1
			VectorLerp(oldOrigin, newOrigin, frac, lerpedOrigin);
			QuatSlerp(oldQuat, newQuat, frac, lerpedQuat);
#else
			VectorCopy(newOrigin, lerpedOrigin);
			QuatCopy(newQuat, lerpedQuat);
#endif

			// copy lerped information to the bone + extra data
			skel->bones[i].parentIndex = channel->parentIndex;

			if(channel->parentIndex < 0 && clearOrigin)
			{
				VectorClear(skel->bones[i].origin);
				QuatClear(skel->bones[i].rotation);

				// move bounding box back
				VectorSubtract(skel->bounds[0], lerpedOrigin, skel->bounds[0]);
				VectorSubtract(skel->bounds[1], lerpedOrigin, skel->bounds[1]);
			}
			else
			{
				VectorCopy(lerpedOrigin, skel->bones[i].origin);
			}

			QuatCopy(lerpedQuat, skel->bones[i].rotation);

#if defined(REFBONE_NAMES)
			Q_strncpyz(skel->bones[i].name, channel->name, sizeof(skel->bones[i].name));
#endif
		}

		skel->numBones = anim->numChannels;
		skel->type = SK_RELATIVE;
		return qtrue;
	}
	else if(skelAnim->type == AT_PSA && skelAnim->psa)
	{
		int             i;
		psaAnimation_t *anim;
		axAnimationKey_t *newKey, *oldKey;
		axReferenceBone_t *refBone;
		vec3_t          newOrigin, oldOrigin, lerpedOrigin;
		quat_t          newQuat, oldQuat, lerpedQuat;
		refSkeleton_t   skeleton;

		anim = skelAnim->psa;

		Q_clamp(startFrame, 0, anim->info.numRawFrames - 1);
		Q_clamp(endFrame, 0, anim->info.numRawFrames - 1);

		ClearBounds(skel->bounds[0], skel->bounds[1]);

		skel->numBones = anim->info.numBones;
		for(i = 0, refBone = anim->bones; i < anim->info.numBones; i++, refBone++)
		{
			oldKey = &anim->keys[startFrame * anim->info.numBones + i];
			newKey = &anim->keys[endFrame * anim->info.numBones + i];

			VectorCopy(newKey->position, newOrigin);
			VectorCopy(oldKey->position, oldOrigin);

			QuatCopy(newKey->quat, newQuat);
			QuatCopy(oldKey->quat, oldQuat);

			//QuatCalcW(oldQuat);
			//QuatNormalize(oldQuat);

			//QuatCalcW(newQuat);
			//QuatNormalize(newQuat);

			VectorLerp(oldOrigin, newOrigin, frac, lerpedOrigin);
			QuatSlerp(oldQuat, newQuat, frac, lerpedQuat);

			// copy lerped information to the bone + extra data
			skel->bones[i].parentIndex = refBone->parentIndex;

			if(refBone->parentIndex < 0 && clearOrigin)
			{
				VectorClear(skel->bones[i].origin);
				QuatClear(skel->bones[i].rotation);

				// move bounding box back
				VectorSubtract(skel->bounds[0], lerpedOrigin, skel->bounds[0]);
				VectorSubtract(skel->bounds[1], lerpedOrigin, skel->bounds[1]);
			}
			else
			{
				VectorCopy(lerpedOrigin, skel->bones[i].origin);
			}

			QuatCopy(lerpedQuat, skel->bones[i].rotation);

#if defined(REFBONE_NAMES)
			Q_strncpyz(skel->bones[i].name, refBone->name, sizeof(skel->bones[i].name));
#endif

			// calculate absolute values for the bounding box approximation
			VectorCopy(skel->bones[i].origin, skeleton.bones[i].origin);
			QuatCopy(skel->bones[i].rotation, skeleton.bones[i].rotation);

			if(refBone->parentIndex >= 0)
			{
				vec3_t          rotated;
				quat_t          quat;
				refBone_t      *parent;
				refBone_t      *bone;

				bone = &skeleton.bones[i];
				parent = &skeleton.bones[refBone->parentIndex];

				QuatTransformVector(parent->rotation, bone->origin, rotated);

				VectorAdd(parent->origin, rotated, bone->origin);

				QuatMultiply1(parent->rotation, bone->rotation, quat);
				QuatCopy(quat, bone->rotation);

				AddPointToBounds(bone->origin, skel->bounds[0], skel->bounds[1]);
			}
		}

		skel->numBones = anim->info.numBones;
		skel->type = SK_RELATIVE;
		return qtrue;
	}

	//ri.Printf(PRINT_WARNING, "RE_BuildSkeleton: bad animation '%s' with handle %i\n", anim->name, hAnim);

	// FIXME: clear existing bones and bounds?
	return qfalse;
}
예제 #8
0
파일: pr_exec.c 프로젝트: luaman/qforge-1
/*
	PR_ExecuteProgram

	The interpretation main loop
*/
VISIBLE void
PR_ExecuteProgram (progs_t * pr, func_t fnum)
{
	int         exitdepth, profile, startprofile;
	pr_uint_t   pointer;
	dstatement_t *st;
	edict_t    *ed;
	pr_type_t  *ptr;
	pr_type_t   old_val = {0}, *watch = 0;

	// make a stack frame
	exitdepth = pr->pr_depth;
	startprofile = profile = 0;

	Sys_PushSignalHook (signal_hook, pr);

	if (!PR_CallFunction (pr, fnum)) {
		// called a builtin instead of progs code
		Sys_PopSignalHook ();
		return;
	}
	st = pr->pr_statements + pr->pr_xstatement;

	if (pr->watch) {
		watch = pr->watch;
		old_val = *watch;
	}

	while (1) {
		pr_type_t  *op_a, *op_b, *op_c;

		st++;
		++pr->pr_xstatement;
		if (pr->pr_xstatement != st - pr->pr_statements)
			PR_RunError (pr, "internal error");
		if (++profile > 1000000 && !pr->no_exec_limit) {
			PR_RunError (pr, "runaway loop error");
		}

		op_a = pr->pr_globals + st->a;
		op_b = pr->pr_globals + st->b;
		op_c = pr->pr_globals + st->c;

		if (pr->pr_trace)
			PR_PrintStatement (pr, st, 1);

		switch (st->op) {
			case OP_ADD_F:
				OPC.float_var = OPA.float_var + OPB.float_var;
				break;
			case OP_ADD_V:
				VectorAdd (OPA.vector_var, OPB.vector_var, OPC.vector_var);
				break;
			case OP_ADD_Q:
				QuatAdd (OPA.quat_var, OPB.quat_var, OPC.quat_var);
				break;
			case OP_ADD_S:
				OPC.string_var = PR_CatStrings (pr,
												PR_GetString (pr,
															  OPA.string_var),
												PR_GetString (pr,
															  OPB.string_var));
				break;
			case OP_SUB_F:
				OPC.float_var = OPA.float_var - OPB.float_var;
				break;
			case OP_SUB_V:
				VectorSubtract (OPA.vector_var, OPB.vector_var, OPC.vector_var);
				break;
			case OP_SUB_Q:
				QuatSubtract (OPA.quat_var, OPB.quat_var, OPC.quat_var);
				break;
			case OP_MUL_F:
				OPC.float_var = OPA.float_var * OPB.float_var;
				break;
			case OP_MUL_V:
				OPC.float_var = DotProduct (OPA.vector_var, OPB.vector_var);
				break;
			case OP_MUL_FV:
				{
					// avoid issues with the likes of x = x.x * x;
					// makes for faster code, too
					float       scale = OPA.float_var;
					VectorScale (OPB.vector_var, scale, OPC.vector_var);
				}
				break;
			case OP_MUL_VF:
				{
					// avoid issues with the likes of x = x * x.x;
					// makes for faster code, too
					float       scale = OPB.float_var;
					VectorScale (OPA.vector_var, scale, OPC.vector_var);
				}
				break;
			case OP_MUL_Q:
				QuatMult (OPA.quat_var, OPB.quat_var, OPC.quat_var);
				break;
			case OP_MUL_QV:
				QuatMultVec (OPA.quat_var, OPB.vector_var, OPC.vector_var);
				break;
			case OP_MUL_FQ:
				{
					// avoid issues with the likes of x = x.s * x;
					// makes for faster code, too
					float       scale = OPA.float_var;
					QuatScale (OPB.quat_var, scale, OPC.quat_var);
				}
				break;
			case OP_MUL_QF:
				{
					// avoid issues with the likes of x = x * x.s;
					// makes for faster code, too
					float       scale = OPB.float_var;
					QuatScale (OPA.quat_var, scale, OPC.quat_var);
				}
				break;
			case OP_CONJ_Q:
				QuatConj (OPA.quat_var, OPC.quat_var);
				break;
			case OP_DIV_F:
				OPC.float_var = OPA.float_var / OPB.float_var;
				break;
			case OP_BITAND:
				OPC.float_var = (int) OPA.float_var & (int) OPB.float_var;
				break;
			case OP_BITOR:
				OPC.float_var = (int) OPA.float_var | (int) OPB.float_var;
				break;
			case OP_BITXOR_F:
				OPC.float_var = (int) OPA.float_var ^ (int) OPB.float_var;
				break;
			case OP_BITNOT_F:
				OPC.float_var = ~ (int) OPA.float_var;
				break;
			case OP_SHL_F:
				OPC.float_var = (int) OPA.float_var << (int) OPB.float_var;
				break;
			case OP_SHR_F:
				OPC.float_var = (int) OPA.float_var >> (int) OPB.float_var;
				break;
			case OP_SHL_I:
				OPC.integer_var = OPA.integer_var << OPB.integer_var;
				break;
			case OP_SHR_I:
				OPC.integer_var = OPA.integer_var >> OPB.integer_var;
				break;
			case OP_SHR_U:
				OPC.uinteger_var = OPA.uinteger_var >> OPB.integer_var;
				break;
			case OP_GE_F:
				OPC.float_var = OPA.float_var >= OPB.float_var;
				break;
			case OP_LE_F:
				OPC.float_var = OPA.float_var <= OPB.float_var;
				break;
			case OP_GT_F:
				OPC.float_var = OPA.float_var > OPB.float_var;
				break;
			case OP_LT_F:
				OPC.float_var = OPA.float_var < OPB.float_var;
				break;
			case OP_AND:	// OPA and OPB have to be float for -0.0
				OPC.integer_var = FNZ (OPA) && FNZ (OPB);
				break;
			case OP_OR:		// OPA and OPB have to be float for -0.0
				OPC.integer_var = FNZ (OPA) || FNZ (OPB);
				break;
			case OP_NOT_F:
				OPC.integer_var = !FNZ (OPA);
				break;
			case OP_NOT_V:
				OPC.integer_var = VectorIsZero (OPA.vector_var);
				break;
			case OP_NOT_Q:
				OPC.integer_var = QuatIsZero (OPA.quat_var);
				break;
			case OP_NOT_S:
				OPC.integer_var = !OPA.string_var ||
					!*PR_GetString (pr, OPA.string_var);
				break;
			case OP_NOT_FN:
				OPC.integer_var = !OPA.func_var;
				break;
			case OP_NOT_ENT:
				OPC.integer_var = !OPA.entity_var;
				break;
			case OP_EQ_F:
				OPC.integer_var = OPA.float_var == OPB.float_var;
				break;
			case OP_EQ_V:
				OPC.integer_var = VectorCompare (OPA.vector_var,
												 OPB.vector_var);
				break;
			case OP_EQ_Q:
				OPC.integer_var = QuatCompare (OPA.quat_var, OPB.quat_var);
				break;
			case OP_EQ_E:
				OPC.integer_var = OPA.integer_var == OPB.integer_var;
				break;
			case OP_EQ_FN:
				OPC.integer_var = OPA.func_var == OPB.func_var;
				break;
			case OP_NE_F:
				OPC.integer_var = OPA.float_var != OPB.float_var;
				break;
			case OP_NE_V:
				OPC.integer_var = !VectorCompare (OPA.vector_var,
												  OPB.vector_var);
				break;
			case OP_NE_Q:
				OPC.integer_var = !QuatCompare (OPA.quat_var, OPB.quat_var);
				break;
			case OP_LE_S:
			case OP_GE_S:
			case OP_LT_S:
			case OP_GT_S:
			case OP_NE_S:
			case OP_EQ_S:
				{
					int cmp = strcmp (PR_GetString (pr, OPA.string_var),
									  PR_GetString (pr, OPB.string_var));
					switch (st->op) {
						case OP_LE_S: cmp = (cmp <= 0); break;
						case OP_GE_S: cmp = (cmp >= 0); break;
						case OP_LT_S: cmp = (cmp < 0); break;
						case OP_GT_S: cmp = (cmp > 0); break;
						case OP_NE_S: break;
						case OP_EQ_S: cmp = !cmp; break;
						default:      break;
					}
					OPC.integer_var = cmp;
				}
				break;
			case OP_NE_E:
				OPC.integer_var = OPA.integer_var != OPB.integer_var;
				break;
			case OP_NE_FN:
				OPC.integer_var = OPA.func_var != OPB.func_var;
				break;

			// ==================
			case OP_STORE_F:
			case OP_STORE_ENT:
			case OP_STORE_FLD:			// integers
			case OP_STORE_S:
			case OP_STORE_FN:			// pointers
			case OP_STORE_I:
			case OP_STORE_P:
				OPB.integer_var = OPA.integer_var;
				break;
			case OP_STORE_V:
				VectorCopy (OPA.vector_var, OPB.vector_var);
				break;
			case OP_STORE_Q:
				QuatCopy (OPA.quat_var, OPB.quat_var);
				break;

			case OP_STOREP_F:
			case OP_STOREP_ENT:
			case OP_STOREP_FLD:		// integers
			case OP_STOREP_S:
			case OP_STOREP_FN:		// pointers
			case OP_STOREP_I:
			case OP_STOREP_P:
				pointer = OPB.integer_var;
				if (pr_boundscheck->int_val) {
					PR_BoundsCheck (pr, pointer, ev_integer);
				}
				ptr = pr->pr_globals + pointer;
				ptr->integer_var = OPA.integer_var;
				break;
			case OP_STOREP_V:
				pointer = OPB.integer_var;
				if (pr_boundscheck->int_val) {
					PR_BoundsCheck (pr, pointer, ev_vector);
				}
				ptr = pr->pr_globals + pointer;
				VectorCopy (OPA.vector_var, ptr->vector_var);
				break;
			case OP_STOREP_Q:
				pointer = OPB.integer_var;
				if (pr_boundscheck->int_val) {
					PR_BoundsCheck (pr, pointer, ev_quat);
				}
				ptr = pr->pr_globals + pointer;
				QuatCopy (OPA.quat_var, ptr->quat_var);
				break;

			case OP_ADDRESS:
				if (pr_boundscheck->int_val) {
					if (OPA.entity_var < 0
						|| OPA.entity_var >= pr->pr_edictareasize)
						PR_RunError (pr, "Progs attempted to address an out "
									 "of bounds edict");
					if (OPA.entity_var == 0 && pr->null_bad)
						PR_RunError (pr, "assignment to world entity");
					if (OPB.uinteger_var >= pr->progs->entityfields)
						PR_RunError (pr, "Progs attempted to address an "
									 "invalid field in an edict");
				}
				ed = PROG_TO_EDICT (pr, OPA.entity_var);
				OPC.integer_var = &ed->v[OPB.integer_var] - pr->pr_globals;
				break;
			case OP_ADDRESS_VOID:
			case OP_ADDRESS_F:
			case OP_ADDRESS_V:
			case OP_ADDRESS_Q:
			case OP_ADDRESS_S:
			case OP_ADDRESS_ENT:
			case OP_ADDRESS_FLD:
			case OP_ADDRESS_FN:
			case OP_ADDRESS_I:
			case OP_ADDRESS_P:
				OPC.integer_var = st->a;
				break;

			case OP_LOAD_F:
			case OP_LOAD_FLD:
			case OP_LOAD_ENT:
			case OP_LOAD_S:
			case OP_LOAD_FN:
			case OP_LOAD_I:
			case OP_LOAD_P:
				if (pr_boundscheck->int_val) {
					if (OPA.entity_var < 0
						|| OPA.entity_var >= pr->pr_edictareasize)
						PR_RunError (pr, "Progs attempted to read an out of "
									 "bounds edict number");
					if (OPB.uinteger_var >= pr->progs->entityfields)
						PR_RunError (pr, "Progs attempted to read an invalid "
									 "field in an edict");
				}
				ed = PROG_TO_EDICT (pr, OPA.entity_var);
				OPC.integer_var = ed->v[OPB.integer_var].integer_var;
				break;
			case OP_LOAD_V:
				if (pr_boundscheck->int_val) {
					if (OPA.entity_var < 0
						|| OPA.entity_var >= pr->pr_edictareasize)
						PR_RunError (pr, "Progs attempted to read an out of "
									 "bounds edict number");
					if (OPB.uinteger_var + 2 >= pr->progs->entityfields)
						PR_RunError (pr, "Progs attempted to read an invalid "
									 "field in an edict");
				}
				ed = PROG_TO_EDICT (pr, OPA.entity_var);
				memcpy (&OPC, &ed->v[OPB.integer_var], 3 * sizeof (OPC));
				break;
			case OP_LOAD_Q:
				if (pr_boundscheck->int_val) {
					if (OPA.entity_var < 0
						|| OPA.entity_var >= pr->pr_edictareasize)
						PR_RunError (pr, "Progs attempted to read an out of "
									 "bounds edict number");
					if (OPB.uinteger_var + 3 >= pr->progs->entityfields)
						PR_RunError (pr, "Progs attempted to read an invalid "
									 "field in an edict");
				}
				ed = PROG_TO_EDICT (pr, OPA.entity_var);
				memcpy (&OPC, &ed->v[OPB.integer_var], 3 * sizeof (OPC));
				break;

			case OP_LOADB_F:
			case OP_LOADB_S:
			case OP_LOADB_ENT:
			case OP_LOADB_FLD:
			case OP_LOADB_FN:
			case OP_LOADB_I:
			case OP_LOADB_P:
				pointer = OPA.integer_var + OPB.integer_var;
				if (pr_boundscheck->int_val) {
					PR_BoundsCheck (pr, pointer, ev_integer);
				}
				ptr = pr->pr_globals + pointer;
				OPC.integer_var = ptr->integer_var;
				break;
			case OP_LOADB_V:
				pointer = OPA.integer_var + OPB.integer_var;
				if (pr_boundscheck->int_val) {
					PR_BoundsCheck (pr, pointer, ev_vector);
				}
				ptr = pr->pr_globals + pointer;
				VectorCopy (ptr->vector_var, OPC.vector_var);
				break;
			case OP_LOADB_Q:
				pointer = OPA.integer_var + OPB.integer_var;
				if (pr_boundscheck->int_val) {
					PR_BoundsCheck (pr, pointer, ev_quat);
				}
				ptr = pr->pr_globals + pointer;
				QuatCopy (ptr->quat_var, OPC.quat_var);
				break;

			case OP_LOADBI_F:
			case OP_LOADBI_S:
			case OP_LOADBI_ENT:
			case OP_LOADBI_FLD:
			case OP_LOADBI_FN:
			case OP_LOADBI_I:
			case OP_LOADBI_P:
				pointer = OPA.integer_var + (short) st->b;
				if (pr_boundscheck->int_val) {
					PR_BoundsCheck (pr, pointer, ev_integer);
				}
				ptr = pr->pr_globals + pointer;
				OPC.integer_var = ptr->integer_var;
				break;
			case OP_LOADBI_V:
				pointer = OPA.integer_var + (short) st->b;
				if (pr_boundscheck->int_val) {
					PR_BoundsCheck (pr, pointer, ev_vector);
				}
				ptr = pr->pr_globals + pointer;
				VectorCopy (ptr->vector_var, OPC.vector_var);
				break;
			case OP_LOADBI_Q:
				pointer = OPA.integer_var + (short) st->b;
				if (pr_boundscheck->int_val) {
					PR_BoundsCheck (pr, pointer, ev_quat);
				}
				ptr = pr->pr_globals + pointer;
				QuatCopy (ptr->quat_var, OPC.quat_var);
				break;

			case OP_LEA:
				pointer = OPA.integer_var + OPB.integer_var;
				OPC.integer_var = pointer;
				break;

			case OP_LEAI:
				pointer = OPA.integer_var + (short) st->b;
				OPC.integer_var = pointer;
				break;

			case OP_STOREB_F:
			case OP_STOREB_S:
			case OP_STOREB_ENT:
			case OP_STOREB_FLD:
			case OP_STOREB_FN:
			case OP_STOREB_I:
			case OP_STOREB_P:
				pointer = OPB.integer_var + OPC.integer_var;
				if (pr_boundscheck->int_val) {
					PR_BoundsCheck (pr, pointer, ev_integer);
				}
				ptr = pr->pr_globals + pointer;
				ptr->integer_var = OPA.integer_var;
				break;
			case OP_STOREB_V:
				pointer = OPB.integer_var + OPC.integer_var;
				if (pr_boundscheck->int_val) {
					PR_BoundsCheck (pr, pointer, ev_vector);
				}
				ptr = pr->pr_globals + pointer;
				VectorCopy (OPA.vector_var, ptr->vector_var);
				break;
			case OP_STOREB_Q:
				pointer = OPB.integer_var + OPC.integer_var;
				if (pr_boundscheck->int_val) {
					PR_BoundsCheck (pr, pointer, ev_quat);
				}
				ptr = pr->pr_globals + pointer;
				QuatCopy (OPA.quat_var, ptr->quat_var);
				break;

			case OP_STOREBI_F:
			case OP_STOREBI_S:
			case OP_STOREBI_ENT:
			case OP_STOREBI_FLD:
			case OP_STOREBI_FN:
			case OP_STOREBI_I:
			case OP_STOREBI_P:
				pointer = OPB.integer_var + (short) st->c;
				if (pr_boundscheck->int_val) {
					PR_BoundsCheck (pr, pointer, ev_integer);
				}
				ptr = pr->pr_globals + pointer;
				ptr->integer_var = OPA.integer_var;
				break;
			case OP_STOREBI_V:
				pointer = OPB.integer_var + (short) st->c;
				if (pr_boundscheck->int_val) {
					PR_BoundsCheck (pr, pointer, ev_vector);
				}
				ptr = pr->pr_globals + pointer;
				VectorCopy (OPA.vector_var, ptr->vector_var);
				break;
			case OP_STOREBI_Q:
				pointer = OPB.integer_var + (short) st->c;
				if (pr_boundscheck->int_val) {
					PR_BoundsCheck (pr, pointer, ev_quat);
				}
				ptr = pr->pr_globals + pointer;
				QuatCopy (OPA.quat_var, ptr->quat_var);
				break;

			// ==================
			case OP_IFNOT:
				if (!OPA.integer_var) {
					pr->pr_xstatement += (short)st->b - 1;	// offset the st++
					st = pr->pr_statements + pr->pr_xstatement;
				}
				break;
			case OP_IF:
				if (OPA.integer_var) {
					pr->pr_xstatement += (short)st->b - 1;	// offset the st++
					st = pr->pr_statements + pr->pr_xstatement;
				}
				break;
			case OP_IFBE:
				if (OPA.integer_var <= 0) {
					pr->pr_xstatement += (short)st->b - 1;	// offset the st++
					st = pr->pr_statements + pr->pr_xstatement;
				}
				break;
			case OP_IFB:
				if (OPA.integer_var < 0) {
					pr->pr_xstatement += (short)st->b - 1;	// offset the st++
					st = pr->pr_statements + pr->pr_xstatement;
				}
				break;
			case OP_IFAE:
				if (OPA.integer_var >= 0) {
					pr->pr_xstatement += (short)st->b - 1;	// offset the st++
					st = pr->pr_statements + pr->pr_xstatement;
				}
				break;
			case OP_IFA:
				if (OPA.integer_var > 0) {
					pr->pr_xstatement += (short)st->b - 1;	// offset the st++
					st = pr->pr_statements + pr->pr_xstatement;
				}
				break;
			case OP_GOTO:
				pr->pr_xstatement += (short)st->a - 1;		// offset the st++
				st = pr->pr_statements + pr->pr_xstatement;
				break;
			case OP_JUMP:
				if (pr_boundscheck->int_val
					&& (OPA.uinteger_var >= pr->progs->numstatements)) {
					PR_RunError (pr, "Invalid jump destination");
				}
				pr->pr_xstatement = OPA.uinteger_var - 1;	// offset the st++
				st = pr->pr_statements + pr->pr_xstatement;
				break;
			case OP_JUMPB:
				pointer = st->a + OPB.integer_var;
				if (pr_boundscheck->int_val) {
					PR_BoundsCheck (pr, pointer, ev_integer);
				}
				ptr = pr->pr_globals + pointer;
				pointer = ptr->integer_var;
				if (pr_boundscheck->int_val
					&& (pointer >= pr->progs->numstatements)) {
					PR_RunError (pr, "Invalid jump destination");
				}
				pr->pr_xstatement = pointer - 1;			// offset the st++
				st = pr->pr_statements + pr->pr_xstatement;
				break;

			case OP_RCALL2:
			case OP_RCALL3:
			case OP_RCALL4:
			case OP_RCALL5:
			case OP_RCALL6:
			case OP_RCALL7:
			case OP_RCALL8:
				pr->pr_params[1] = &OPC;
				goto op_rcall;
			case OP_RCALL1:
				pr->pr_params[1] = pr->pr_real_params[1];
op_rcall:
				pr->pr_params[0] = &OPB;
				pr->pr_argc = st->op - OP_RCALL1 + 1;
				goto op_call;
			case OP_CALL0:
			case OP_CALL1:
			case OP_CALL2:
			case OP_CALL3:
			case OP_CALL4:
			case OP_CALL5:
			case OP_CALL6:
			case OP_CALL7:
			case OP_CALL8:
				PR_RESET_PARAMS (pr);
				pr->pr_argc = st->op - OP_CALL0;
op_call:
				pr->pr_xfunction->profile += profile - startprofile;
				startprofile = profile;
				PR_CallFunction (pr, OPA.func_var);
				st = pr->pr_statements + pr->pr_xstatement;
				break;
			case OP_DONE:
			case OP_RETURN:
				if (!st->a)
					memset (&R_INT (pr), 0,
							pr->pr_param_size * sizeof (OPA));
				else if (&R_INT (pr) != &OPA.integer_var)
					memcpy (&R_INT (pr), &OPA,
							pr->pr_param_size * sizeof (OPA));
				// fallthrough
			case OP_RETURN_V:
				pr->pr_xfunction->profile += profile - startprofile;
				startprofile = profile;
				PR_LeaveFunction (pr);
				st = pr->pr_statements + pr->pr_xstatement;
				if (pr->pr_depth == exitdepth) {
					if (pr->pr_trace && pr->pr_depth <= pr->pr_trace_depth)
						pr->pr_trace = false;
					Sys_PopSignalHook ();
					return;					// all done
				}
				break;
			case OP_STATE:
				ed = PROG_TO_EDICT (pr, *pr->globals.self);
				ed->v[pr->fields.nextthink].float_var = *pr->globals.time +
					0.1;
				ed->v[pr->fields.frame].float_var = OPA.float_var;
				ed->v[pr->fields.think].func_var = OPB.func_var;
				break;
			case OP_STATE_F:
				ed = PROG_TO_EDICT (pr, *pr->globals.self);
				ed->v[pr->fields.nextthink].float_var = *pr->globals.time +
					OPC.float_var;
				ed->v[pr->fields.frame].float_var = OPA.float_var;
				ed->v[pr->fields.think].func_var = OPB.func_var;
				break;
			case OP_ADD_I:
				OPC.integer_var = OPA.integer_var + OPB.integer_var;
				break;
			case OP_SUB_I:
				OPC.integer_var = OPA.integer_var - OPB.integer_var;
				break;
			case OP_MUL_I:
				OPC.integer_var = OPA.integer_var * OPB.integer_var;
				break;
/*
			case OP_DIV_VF:
				{
					float       temp = 1.0f / OPB.float_var;
					VectorScale (OPA.vector_var, temp, OPC.vector_var);
				}
				break;
*/
			case OP_DIV_I:
				OPC.integer_var = OPA.integer_var / OPB.integer_var;
				break;
			case OP_MOD_I:
				OPC.integer_var = OPA.integer_var % OPB.integer_var;
				break;
			case OP_MOD_F:
				OPC.float_var = (int) OPA.float_var % (int) OPB.float_var;
				break;
			case OP_CONV_IF:
				OPC.float_var = OPA.integer_var;
				break;
			case OP_CONV_FI:
				OPC.integer_var = OPA.float_var;
				break;
			case OP_BITAND_I:
				OPC.integer_var = OPA.integer_var & OPB.integer_var;
				break;
			case OP_BITOR_I:
				OPC.integer_var = OPA.integer_var | OPB.integer_var;
				break;
			case OP_BITXOR_I:
				OPC.integer_var = OPA.integer_var ^ OPB.integer_var;
				break;
			case OP_BITNOT_I:
				OPC.integer_var = ~OPA.integer_var;
				break;

			case OP_GE_I:
			case OP_GE_P:
				OPC.integer_var = OPA.integer_var >= OPB.integer_var;
				break;
			case OP_GE_U:
				OPC.integer_var = OPA.uinteger_var >= OPB.uinteger_var;
				break;
			case OP_LE_I:
			case OP_LE_P:
				OPC.integer_var = OPA.integer_var <= OPB.integer_var;
				break;
			case OP_LE_U:
				OPC.integer_var = OPA.uinteger_var <= OPB.uinteger_var;
				break;
			case OP_GT_I:
			case OP_GT_P:
				OPC.integer_var = OPA.integer_var > OPB.integer_var;
				break;
			case OP_GT_U:
				OPC.integer_var = OPA.uinteger_var > OPB.uinteger_var;
				break;
			case OP_LT_I:
			case OP_LT_P:
				OPC.integer_var = OPA.integer_var < OPB.integer_var;
				break;
			case OP_LT_U:
				OPC.integer_var = OPA.uinteger_var < OPB.uinteger_var;
				break;

			case OP_AND_I:
				OPC.integer_var = OPA.integer_var && OPB.integer_var;
				break;
			case OP_OR_I:
				OPC.integer_var = OPA.integer_var || OPB.integer_var;
				break;
			case OP_NOT_I:
			case OP_NOT_P:
				OPC.integer_var = !OPA.integer_var;
				break;
			case OP_EQ_I:
			case OP_EQ_P:
				OPC.integer_var = OPA.integer_var == OPB.integer_var;
				break;
			case OP_NE_I:
			case OP_NE_P:
				OPC.integer_var = OPA.integer_var != OPB.integer_var;
				break;

			case OP_MOVEI:
				memmove (&OPC, &OPA, st->b * 4);
				break;
			case OP_MOVEP:
				if (pr_boundscheck->int_val) {
					PR_BoundsCheckSize (pr, OPC.integer_var, OPB.uinteger_var);
					PR_BoundsCheckSize (pr, OPA.integer_var, OPB.uinteger_var);
				}
				memmove (pr->pr_globals + OPC.integer_var,
						 pr->pr_globals + OPA.integer_var,
						 OPB.uinteger_var * 4);
				break;
			case OP_MOVEPI:
				if (pr_boundscheck->int_val) {
					PR_BoundsCheckSize (pr, OPC.integer_var, st->b);
					PR_BoundsCheckSize (pr, OPA.integer_var, st->b);
				}
				memmove (pr->pr_globals + OPC.integer_var,
						 pr->pr_globals + OPA.integer_var,
						 st->b * 4);
				break;

// LordHavoc: to be enabled when Progs version 7 (or whatever it will be numbered) is finalized
/*
			case OP_BOUNDCHECK:
				if (OPA.integer_var < 0 || OPA.integer_var >= st->b) {
					PR_RunError (pr, "Progs boundcheck failed at line number "
					"%d, value is < 0 or >= %d", st->b, st->c);
				}
				break;

*/
			default:
				PR_RunError (pr, "Bad opcode %i", st->op);
		}
		if (watch && watch->integer_var != old_val.integer_var
			&& (!pr->wp_conditional
				|| watch->integer_var == pr->wp_val.integer_var))
			PR_RunError (pr, "watchpoint hit: %d -> %d", old_val.integer_var,
						 watch->integer_var);
	}
}
	//
	// 更新
	//
	VOID CGfxBillboard::Update(const CEntityCamera *pCamera, CParticle *pParticleList, INT numParticles)
	{
		//
		// 1. 参数安全检查
		//
		if (pCamera == NULL || pParticleList == NULL || numParticles <= 0) {
			return;
		}

		//
		// 2. 更新相机矩阵
		//
		if (m_directionType == DIRECTION_CAMERA) {
			VEC3 direction;
			VEC3 position, up, target;

			Vec3Scale(&direction, pCamera->GetForwardDirection(), -1.0f);
			Vec3Set(&up, 0.0f, 1.0f, 0.0f);
			Vec3Set(&position, 0.0f, 0.0f, 0.0f);
			Vec3Ma(&target, &position, &direction, 1.0f);

			MtxDefLookAt(&m_mtxFaceToCamera, &position, &up, &target);
		}

		//
		// 3. 更新粒子数据
		//
		VERTEX *vertices = (VERTEX *)SAFE_MALLOC(4 * numParticles * sizeof(*vertices), MEMTYPE_STACK);
		{
			//    Billboard
			//    0 ___ 3
			//     |   |
			//     |___|
			//    1     2

			INT indexVertex = 0;
			CParticle *pParticle = pParticleList;

			while (pParticle) {
				const VEC3 *parentWorldScale = pParticle->pEmitter->GetWorldScale();
				const VEC3 *parentWorldPosition = pParticle->pEmitter->GetWorldPosition();
				const QUAT *parentWorldOrientation = pParticle->pEmitter->GetWorldOrientation();

				//
				// 1. 计算粒子位置与朝向
				//
				VEC3 scale;
				VEC3 position;
				QUAT orientation;

				if (pParticle->bKeepLocal && pParticle->pEmitter) {
					Vec3Mul(&scale, &pParticle->localScale, parentWorldScale);

					if (m_directionType == DIRECTION_FIXED) {
						QuatMul(&orientation, &pParticle->localOrientation, parentWorldOrientation);
					}

					VEC3 scalePosition;
					VEC3 scaleOrientationPosition;
					Vec3Mul(&scalePosition, &pParticle->localPosition, parentWorldScale);
					Vec3MulQuat(&scaleOrientationPosition, &scalePosition, parentWorldOrientation);
					Vec3Add(&position, &scaleOrientationPosition, parentWorldPosition);
				}
				else {
					Vec3Copy(&scale, &pParticle->localScale);

					if (m_directionType == DIRECTION_FIXED) {
						QuatCopy(&orientation, &pParticle->localOrientation);
					}

					Vec3Copy(&position, &pParticle->localPosition);
				}

				//
				// 2. 粒子位置偏移量
				//
				MATRIX4 mtxOrientation;

				if (m_directionType == DIRECTION_CAMERA) {
					MtxCopy(&mtxOrientation, &m_mtxFaceToCamera);

					if (m_offset < -EPSILON_E3 || m_offset > EPSILON_E3) {
						VEC3 offsetDirection;
						Vec3Sub(&offsetDirection, pCamera->GetPosition(), &position);
						Vec3Normalize(&offsetDirection);
						Vec3Ma(&position, &position, &offsetDirection, m_offset);
					}
				}
				else {
					QuatToMtxRotation(&mtxOrientation, &orientation);

					if (m_offset < -EPSILON_E3 || m_offset > EPSILON_E3) {
						VEC3 localDirection;
						VEC3 offsetDirection;
						Vec3Set(&localDirection, 0.0f, 0.0f, 1.0f);
						Vec3MulQuat(&offsetDirection, &localDirection, &orientation);
						Vec3Normalize(&offsetDirection);
						Vec3Ma(&position, &position, &offsetDirection, m_offset);
					}
				}

				//
				// 3. 计算粒子变换矩阵
				//
				MATRIX4 mtxScale;
				MATRIX4 mtxRotate;
				MATRIX4 mtxRotateSelf;
				MATRIX4 mtxTranslate;
				MtxDefScale(&mtxScale, scale[0], scale[1], scale[2]);
				MtxDefTranslate(&mtxTranslate, position[0], position[1], position[2]);
				MtxDefRotateAxisAngle(&mtxRotateSelf, &axisz, pParticle->radian);
				MtxMul(&mtxRotate, &mtxRotateSelf, &mtxOrientation);

				MATRIX4 mtxSR;
				MATRIX4 mtxTransform;
				MtxMul(&mtxSR, &mtxScale, &mtxRotate);
				MtxMul(&mtxTransform, &mtxSR, &mtxTranslate);

				//
				// 4. 计算粒子纹理矩阵
				//
				MATRIX4 mtxTexScale;
				MATRIX4 mtxTexTranslate;
				MATRIX4 mtxTexTransform;
				MtxDefScale(&mtxTexScale, pParticle->texSequenceScale[0], pParticle->texSequenceScale[1], 1.0f);
				MtxDefTranslate(&mtxTexTranslate, pParticle->texSequenceOffset[0] + pParticle->texScrollOffset[0], pParticle->texSequenceOffset[1] + pParticle->texScrollOffset[1], 0.0f);
				MtxMul(&mtxTexTransform, &mtxTexScale, &mtxTexTranslate);

				//
				// 5. 计算粒子顶点
				//
				VEC3 desVertices[4];
				VEC3 srcVertices[4] = {
					VEC3(-1.0f,  1.0f, 0.0f),
					VEC3(-1.0f, -1.0f, 0.0f),
					VEC3( 1.0f, -1.0f, 0.0f),
					VEC3( 1.0f,  1.0f, 0.0f),
				};

				VEC2 texCoords[4] = {
					VEC2(pParticle->uvOffset[0] + 0.0f, pParticle->uvOffset[1] + 0.0f),
					VEC2(pParticle->uvOffset[0] + 0.0f, pParticle->uvOffset[1] + 1.0f),
					VEC2(pParticle->uvOffset[0] + 1.0f, pParticle->uvOffset[1] + 1.0f),
					VEC2(pParticle->uvOffset[0] + 1.0f, pParticle->uvOffset[1] + 0.0f),
				};

				VEC3 localNormal, localBinormal;
				VEC3 worldNormal, worldBinormal;

				Vec3Set(&localNormal, 0.0f, 0.0f, 1.0f);
				Vec3Set(&localBinormal, 1.0f, 0.0f, 0.0f);
				Vec3MulMtx3x3(&worldNormal, &localNormal, &mtxTransform);
				Vec3MulMtx3x3(&worldBinormal, &localBinormal, &mtxTransform);

				Vec3MulMtx4x4(&desVertices[0], &srcVertices[0], &mtxTransform);
				Vec3MulMtx4x4(&desVertices[1], &srcVertices[1], &mtxTransform);
				Vec3MulMtx4x4(&desVertices[2], &srcVertices[2], &mtxTransform);
				Vec3MulMtx4x4(&desVertices[3], &srcVertices[3], &mtxTransform);

				Vec3Copy(&vertices[indexVertex].position, &desVertices[0]);
				Vec3Copy(&vertices[indexVertex].normal, &worldNormal);
				Vec3Copy(&vertices[indexVertex].binormal, &worldBinormal);
				Vec4Copy(&vertices[indexVertex].color, &pParticle->color);
				Vec2MulMtx4x4(&vertices[indexVertex].texCoordDiffuse, &texCoords[0], &mtxTexTransform);
				indexVertex++;

				Vec3Copy(&vertices[indexVertex].position, &desVertices[1]);
				Vec3Copy(&vertices[indexVertex].normal, &worldNormal);
				Vec3Copy(&vertices[indexVertex].binormal, &worldBinormal);
				Vec4Copy(&vertices[indexVertex].color, &pParticle->color);
				Vec2MulMtx4x4(&vertices[indexVertex].texCoordDiffuse, &texCoords[1], &mtxTexTransform);
				indexVertex++;

				Vec3Copy(&vertices[indexVertex].position, &desVertices[2]);
				Vec3Copy(&vertices[indexVertex].normal, &worldNormal);
				Vec3Copy(&vertices[indexVertex].binormal, &worldBinormal);
				Vec4Copy(&vertices[indexVertex].color, &pParticle->color);
				Vec2MulMtx4x4(&vertices[indexVertex].texCoordDiffuse, &texCoords[2], &mtxTexTransform);
				indexVertex++;

				Vec3Copy(&vertices[indexVertex].position, &desVertices[3]);
				Vec3Copy(&vertices[indexVertex].normal, &worldNormal);
				Vec3Copy(&vertices[indexVertex].binormal, &worldBinormal);
				Vec4Copy(&vertices[indexVertex].color, &pParticle->color);
				Vec2MulMtx4x4(&vertices[indexVertex].texCoordDiffuse, &texCoords[3], &mtxTexTransform);
				indexVertex++;

				pParticle = pParticle->pNext;
			}

			Renderer()->BindVBO(GL_ARRAY_BUFFER, m_vbo);
			Renderer()->UpdateVBO(GL_ARRAY_BUFFER, 0, 4 * numParticles * sizeof(*vertices), vertices);
		}
		SAFE_FREE(vertices);
	}
예제 #10
0
/*
==============
RE_BuildSkeleton
==============
*/
int RE_BuildSkeleton( refSkeleton_t *skel, qhandle_t hAnim, int startFrame, int endFrame, float frac, bool clearOrigin )
{
	skelAnimation_t *skelAnim;

	skelAnim = R_GetAnimationByHandle( hAnim );

	if ( skelAnim->type == animType_t::AT_IQM && skelAnim->iqm ) {
		return IQMBuildSkeleton( skel, skelAnim, startFrame, endFrame, frac );
	}
	else if ( skelAnim->type == animType_t::AT_MD5 && skelAnim->md5 )
	{
		int            i;
		md5Animation_t *anim;
		md5Channel_t   *channel;
		md5Frame_t     *newFrame, *oldFrame;
		vec3_t         newOrigin, oldOrigin, lerpedOrigin;
		quat_t         newQuat, oldQuat, lerpedQuat;
		int            componentsApplied;

		anim = skelAnim->md5;

		// Validate the frames so there is no chance of a crash.
		// This will write directly into the entity structure, so
		// when the surfaces are rendered, they don't need to be
		// range checked again.

		/*
		   if((startFrame >= anim->numFrames) || (startFrame < 0) || (endFrame >= anim->numFrames) || (endFrame < 0))
		   {
		   Log::Debug("RE_BuildSkeleton: no such frame %d to %d for '%s'\n", startFrame, endFrame, anim->name);
		   //startFrame = 0;
		   //endFrame = 0;
		   }
		 */

		startFrame = Math::Clamp( startFrame, 0, anim->numFrames - 1 );
		endFrame = Math::Clamp( endFrame, 0, anim->numFrames - 1 );

		// compute frame pointers
		oldFrame = &anim->frames[ startFrame ];
		newFrame = &anim->frames[ endFrame ];

		// calculate a bounding box in the current coordinate system
		for ( i = 0; i < 3; i++ )
		{
			skel->bounds[ 0 ][ i ] =
			  oldFrame->bounds[ 0 ][ i ] < newFrame->bounds[ 0 ][ i ] ? oldFrame->bounds[ 0 ][ i ] : newFrame->bounds[ 0 ][ i ];
			skel->bounds[ 1 ][ i ] =
			  oldFrame->bounds[ 1 ][ i ] > newFrame->bounds[ 1 ][ i ] ? oldFrame->bounds[ 1 ][ i ] : newFrame->bounds[ 1 ][ i ];
		}

		for ( i = 0, channel = anim->channels; i < anim->numChannels; i++, channel++ )
		{
			// set baseframe values
			VectorCopy( channel->baseOrigin, newOrigin );
			VectorCopy( channel->baseOrigin, oldOrigin );

			QuatCopy( channel->baseQuat, newQuat );
			QuatCopy( channel->baseQuat, oldQuat );

			componentsApplied = 0;

			// update tranlation bits
			if ( channel->componentsBits & COMPONENT_BIT_TX )
			{
				oldOrigin[ 0 ] = oldFrame->components[ channel->componentsOffset + componentsApplied ];
				newOrigin[ 0 ] = newFrame->components[ channel->componentsOffset + componentsApplied ];
				componentsApplied++;
			}

			if ( channel->componentsBits & COMPONENT_BIT_TY )
			{
				oldOrigin[ 1 ] = oldFrame->components[ channel->componentsOffset + componentsApplied ];
				newOrigin[ 1 ] = newFrame->components[ channel->componentsOffset + componentsApplied ];
				componentsApplied++;
			}

			if ( channel->componentsBits & COMPONENT_BIT_TZ )
			{
				oldOrigin[ 2 ] = oldFrame->components[ channel->componentsOffset + componentsApplied ];
				newOrigin[ 2 ] = newFrame->components[ channel->componentsOffset + componentsApplied ];
				componentsApplied++;
			}

			// update quaternion rotation bits
			if ( channel->componentsBits & COMPONENT_BIT_QX )
			{
				( ( vec_t * ) oldQuat ) [ 0 ] = oldFrame->components[ channel->componentsOffset + componentsApplied ];
				( ( vec_t * ) newQuat ) [ 0 ] = newFrame->components[ channel->componentsOffset + componentsApplied ];
				componentsApplied++;
			}

			if ( channel->componentsBits & COMPONENT_BIT_QY )
			{
				( ( vec_t * ) oldQuat ) [ 1 ] = oldFrame->components[ channel->componentsOffset + componentsApplied ];
				( ( vec_t * ) newQuat ) [ 1 ] = newFrame->components[ channel->componentsOffset + componentsApplied ];
				componentsApplied++;
			}

			if ( channel->componentsBits & COMPONENT_BIT_QZ )
			{
				( ( vec_t * ) oldQuat ) [ 2 ] = oldFrame->components[ channel->componentsOffset + componentsApplied ];
				( ( vec_t * ) newQuat ) [ 2 ] = newFrame->components[ channel->componentsOffset + componentsApplied ];
			}

			QuatCalcW( oldQuat );
			QuatNormalize( oldQuat );

			QuatCalcW( newQuat );
			QuatNormalize( newQuat );

#if 1
			VectorLerp( oldOrigin, newOrigin, frac, lerpedOrigin );
			QuatSlerp( oldQuat, newQuat, frac, lerpedQuat );
#else
			VectorCopy( newOrigin, lerpedOrigin );
			QuatCopy( newQuat, lerpedQuat );
#endif

			// copy lerped information to the bone + extra data
			skel->bones[ i ].parentIndex = channel->parentIndex;

			if ( channel->parentIndex < 0 && clearOrigin )
			{
				VectorClear( skel->bones[ i ].t.trans );
				QuatClear( skel->bones[ i ].t.rot );

				// move bounding box back
				VectorSubtract( skel->bounds[ 0 ], lerpedOrigin, skel->bounds[ 0 ] );
				VectorSubtract( skel->bounds[ 1 ], lerpedOrigin, skel->bounds[ 1 ] );
			}
			else
			{
				VectorCopy( lerpedOrigin, skel->bones[ i ].t.trans );
			}

			QuatCopy( lerpedQuat, skel->bones[ i ].t.rot );
			skel->bones[ i ].t.scale = 1.0f;

#if defined( REFBONE_NAMES )
			Q_strncpyz( skel->bones[ i ].name, channel->name, sizeof( skel->bones[ i ].name ) );
#endif
		}

		skel->numBones = anim->numChannels;
		skel->type = refSkeletonType_t::SK_RELATIVE;
		return true;
	}

	// FIXME: clear existing bones and bounds?
	return false;
}
예제 #11
0
/*
=================
R_LoadPSK
=================
*/
qboolean R_LoadPSK( model_t *mod, void *buffer, int bufferSize, const char *modName )
{
	int               i, j, k;
	memStream_t       *stream = NULL;

	axChunkHeader_t   chunkHeader;

	int               numPoints;
	axPoint_t         *point;
	axPoint_t         *points = NULL;

	int               numVertexes;
	axVertex_t        *vertex;
	axVertex_t        *vertexes = NULL;

	//int       numSmoothGroups;
	int               numTriangles;
	axTriangle_t      *triangle;
	axTriangle_t      *triangles = NULL;

	int               numMaterials;
	axMaterial_t      *material;
	axMaterial_t      *materials = NULL;

	int               numReferenceBones;
	axReferenceBone_t *refBone;
	axReferenceBone_t *refBones = NULL;

	int               numWeights;
	axBoneWeight_t    *axWeight;
	axBoneWeight_t    *axWeights = NULL;

	md5Model_t        *md5;
	md5Bone_t         *md5Bone;

	vec3_t            boneOrigin;
	quat_t            boneQuat;
	//matrix_t        boneMat;

	int               materialIndex, oldMaterialIndex;

	int               numRemaining;

	growList_t        sortedTriangles;
	growList_t        vboVertexes;
	growList_t        vboTriangles;
	growList_t        vboSurfaces;

	int               numBoneReferences;
	int               boneReferences[ MAX_BONES ];

	matrix_t          unrealToQuake;

#define DeallocAll() Com_Dealloc( materials ); \
	Com_Dealloc( points ); \
	Com_Dealloc( vertexes ); \
	Com_Dealloc( triangles ); \
	Com_Dealloc( refBones ); \
	Com_Dealloc( axWeights ); \
	FreeMemStream( stream );

	//MatrixSetupScale(unrealToQuake, 1, -1, 1);
	MatrixFromAngles( unrealToQuake, 0, 90, 0 );

	stream = AllocMemStream( (byte*) buffer, bufferSize );
	GetChunkHeader( stream, &chunkHeader );

	// check indent again
	if ( Q_strnicmp( chunkHeader.ident, "ACTRHEAD", 8 ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has wrong chunk indent ('%s' should be '%s')\n", modName, chunkHeader.ident, "ACTRHEAD" );
		DeallocAll();
		return qfalse;
	}

	PrintChunkHeader( &chunkHeader );

	mod->type = MOD_MD5;
	mod->dataSize += sizeof( md5Model_t );
	md5 = mod->md5 = (md5Model_t*) ri.Hunk_Alloc( sizeof( md5Model_t ), h_low );

	// read points
	GetChunkHeader( stream, &chunkHeader );

	if ( Q_strnicmp( chunkHeader.ident, "PNTS0000", 8 ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has wrong chunk indent ('%s' should be '%s')\n", modName, chunkHeader.ident, "PNTS0000" );
		DeallocAll();
		return qfalse;
	}

	if ( chunkHeader.dataSize != sizeof( axPoint_t ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has wrong chunk dataSize ('%i' should be '%i')\n", modName, chunkHeader.dataSize, ( int ) sizeof( axPoint_t ) );
		DeallocAll();
		return qfalse;
	}

	PrintChunkHeader( &chunkHeader );

	numPoints = chunkHeader.numData;
	points = (axPoint_t*) Com_Allocate( numPoints * sizeof( axPoint_t ) );

	for ( i = 0, point = points; i < numPoints; i++, point++ )
	{
		point->point[ 0 ] = MemStreamGetFloat( stream );
		point->point[ 1 ] = MemStreamGetFloat( stream );
		point->point[ 2 ] = MemStreamGetFloat( stream );

#if 0
		// Tr3B: HACK convert from Unreal coordinate system to the Quake one
		MatrixTransformPoint2( unrealToQuake, point->point );
#endif
	}

	// read vertices
	GetChunkHeader( stream, &chunkHeader );

	if ( Q_strnicmp( chunkHeader.ident, "VTXW0000", 8 ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has wrong chunk indent ('%s' should be '%s')\n", modName, chunkHeader.ident, "VTXW0000" );
		DeallocAll();
		return qfalse;
	}

	if ( chunkHeader.dataSize != sizeof( axVertex_t ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has wrong chunk dataSize ('%i' should be '%i')\n", modName, chunkHeader.dataSize, ( int ) sizeof( axVertex_t ) );
		DeallocAll();
		return qfalse;
	}

	PrintChunkHeader( &chunkHeader );

	numVertexes = chunkHeader.numData;
	vertexes = (axVertex_t*) Com_Allocate( numVertexes * sizeof( axVertex_t ) );

	for ( i = 0, vertex = vertexes; i < numVertexes; i++, vertex++ )
	{
		vertex->pointIndex = MemStreamGetShort( stream );

		if ( vertex->pointIndex < 0 || vertex->pointIndex >= numPoints )
		{
			ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has vertex with point index out of range (%i while max %i)\n", modName, vertex->pointIndex, numPoints );
			DeallocAll();
			return qfalse;
		}

		vertex->unknownA = MemStreamGetShort( stream );
		vertex->st[ 0 ] = MemStreamGetFloat( stream );
		vertex->st[ 1 ] = MemStreamGetFloat( stream );
		vertex->materialIndex = MemStreamGetC( stream );
		vertex->reserved = MemStreamGetC( stream );
		vertex->unknownB = MemStreamGetShort( stream );

#if 0
		ri.Printf( PRINT_ALL, "R_LoadPSK: axVertex_t(%i):\n"
		           "axVertex:pointIndex: %i\n"
		           "axVertex:unknownA: %i\n"
		           "axVertex::st: %f %f\n"
		           "axVertex:materialIndex: %i\n"
		           "axVertex:reserved: %d\n"
		           "axVertex:unknownB: %d\n",
		           i,
		           vertex->pointIndex,
		           vertex->unknownA,
		           vertex->st[ 0 ], vertex->st[ 1 ],
		           vertex->materialIndex,
		           vertex->reserved,
		           vertex->unknownB );
#endif
	}

	// read triangles
	GetChunkHeader( stream, &chunkHeader );

	if ( Q_strnicmp( chunkHeader.ident, "FACE0000", 8 ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has wrong chunk indent ('%s' should be '%s')\n", modName, chunkHeader.ident, "FACE0000" );
		DeallocAll();
		return qfalse;
	}

	if ( chunkHeader.dataSize != sizeof( axTriangle_t ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has wrong chunk dataSize ('%i' should be '%i')\n", modName, chunkHeader.dataSize, ( int ) sizeof( axTriangle_t ) );
		DeallocAll();
		return qfalse;
	}

	PrintChunkHeader( &chunkHeader );

	numTriangles = chunkHeader.numData;
	triangles = (axTriangle_t*) Com_Allocate( numTriangles * sizeof( axTriangle_t ) );

	for ( i = 0, triangle = triangles; i < numTriangles; i++, triangle++ )
	{
		for ( j = 0; j < 3; j++ )
			//for(j = 2; j >= 0; j--)
		{
			triangle->indexes[ j ] = MemStreamGetShort( stream );

			if ( triangle->indexes[ j ] >= numVertexes )
			{
				ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has triangle with vertex index out of range (%i while max %i)\n", modName, triangle->indexes[ j ], numVertexes );
				DeallocAll();
				return qfalse;
			}
		}

		triangle->materialIndex = MemStreamGetC( stream );
		triangle->materialIndex2 = MemStreamGetC( stream );
		triangle->smoothingGroups = MemStreamGetLong( stream );
	}

	// read materials
	GetChunkHeader( stream, &chunkHeader );

	if ( Q_strnicmp( chunkHeader.ident, "MATT0000", 8 ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has wrong chunk indent ('%s' should be '%s')\n", modName, chunkHeader.ident, "MATT0000" );
		DeallocAll();
		return qfalse;
	}

	if ( chunkHeader.dataSize != sizeof( axMaterial_t ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has wrong chunk dataSize ('%i' should be '%i')\n", modName, chunkHeader.dataSize, ( int ) sizeof( axMaterial_t ) );
		DeallocAll();
		return qfalse;
	}

	PrintChunkHeader( &chunkHeader );

	numMaterials = chunkHeader.numData;
	materials = (axMaterial_t*) Com_Allocate( numMaterials * sizeof( axMaterial_t ) );

	for ( i = 0, material = materials; i < numMaterials; i++, material++ )
	{
		MemStreamRead( stream, material->name, sizeof( material->name ) );

		ri.Printf( PRINT_ALL, "R_LoadPSK: material name: '%s'\n", material->name );

		material->shaderIndex = MemStreamGetLong( stream );
		material->polyFlags = MemStreamGetLong( stream );
		material->auxMaterial = MemStreamGetLong( stream );
		material->auxFlags = MemStreamGetLong( stream );
		material->lodBias = MemStreamGetLong( stream );
		material->lodStyle = MemStreamGetLong( stream );
	}

	for ( i = 0, vertex = vertexes; i < numVertexes; i++, vertex++ )
	{
		if ( vertex->materialIndex < 0 || vertex->materialIndex >= numMaterials )
		{
			ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has vertex with material index out of range (%i while max %i)\n", modName, vertex->materialIndex, numMaterials );
			DeallocAll();
			return qfalse;
		}
	}

	for ( i = 0, triangle = triangles; i < numTriangles; i++, triangle++ )
	{
		if ( triangle->materialIndex < 0 || triangle->materialIndex >= numMaterials )
		{
			ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has triangle with material index out of range (%i while max %i)\n", modName, triangle->materialIndex, numMaterials );
			DeallocAll();
			return qfalse;
		}
	}

	// read reference bones
	GetChunkHeader( stream, &chunkHeader );

	if ( Q_strnicmp( chunkHeader.ident, "REFSKELT", 8 ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has wrong chunk indent ('%s' should be '%s')\n", modName, chunkHeader.ident, "REFSKELT" );
		DeallocAll();
		return qfalse;
	}

	if ( chunkHeader.dataSize != sizeof( axReferenceBone_t ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has wrong chunk dataSize ('%i' should be '%i')\n", modName, chunkHeader.dataSize, ( int ) sizeof( axReferenceBone_t ) );
		DeallocAll();
		return qfalse;
	}

	PrintChunkHeader( &chunkHeader );

	numReferenceBones = chunkHeader.numData;
	refBones = (axReferenceBone_t*) Com_Allocate( numReferenceBones * sizeof( axReferenceBone_t ) );

	for ( i = 0, refBone = refBones; i < numReferenceBones; i++, refBone++ )
	{
		MemStreamRead( stream, refBone->name, sizeof( refBone->name ) );

		//ri.Printf(PRINT_ALL, "R_LoadPSK: reference bone name: '%s'\n", refBone->name);

		refBone->flags = MemStreamGetLong( stream );
		refBone->numChildren = MemStreamGetLong( stream );
		refBone->parentIndex = MemStreamGetLong( stream );

		GetBone( stream, &refBone->bone );

#if 0
		ri.Printf( PRINT_ALL, "R_LoadPSK: axReferenceBone_t(%i):\n"
		           "axReferenceBone_t::name: '%s'\n"
		           "axReferenceBone_t::flags: %i\n"
		           "axReferenceBone_t::numChildren %i\n"
		           "axReferenceBone_t::parentIndex: %i\n"
		           "axReferenceBone_t::quat: %f %f %f %f\n"
		           "axReferenceBone_t::position: %f %f %f\n"
		           "axReferenceBone_t::length: %f\n"
		           "axReferenceBone_t::xSize: %f\n"
		           "axReferenceBone_t::ySize: %f\n"
		           "axReferenceBone_t::zSize: %f\n",
		           i,
		           refBone->name,
		           refBone->flags,
		           refBone->numChildren,
		           refBone->parentIndex,
		           refBone->bone.quat[ 0 ], refBone->bone.quat[ 1 ], refBone->bone.quat[ 2 ], refBone->bone.quat[ 3 ],
		           refBone->bone.position[ 0 ], refBone->bone.position[ 1 ], refBone->bone.position[ 2 ],
		           refBone->bone.length,
		           refBone->bone.xSize,
		           refBone->bone.ySize,
		           refBone->bone.zSize );
#endif
	}

	// read  bone weights
	GetChunkHeader( stream, &chunkHeader );

	if ( Q_strnicmp( chunkHeader.ident, "RAWWEIGHTS", 10 ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has wrong chunk indent ('%s' should be '%s')\n", modName, chunkHeader.ident, "RAWWEIGHTS" );
		DeallocAll();
		return qfalse;
	}

	if ( chunkHeader.dataSize != sizeof( axBoneWeight_t ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has wrong chunk dataSize ('%i' should be '%i')\n", modName, chunkHeader.dataSize, ( int ) sizeof( axBoneWeight_t ) );
		DeallocAll();
		return qfalse;
	}

	PrintChunkHeader( &chunkHeader );

	numWeights = chunkHeader.numData;
	axWeights = (axBoneWeight_t*) Com_Allocate( numWeights * sizeof( axBoneWeight_t ) );

	for ( i = 0, axWeight = axWeights; i < numWeights; i++, axWeight++ )
	{
		axWeight->weight = MemStreamGetFloat( stream );
		axWeight->pointIndex = MemStreamGetLong( stream );
		axWeight->boneIndex = MemStreamGetLong( stream );

#if 0
		ri.Printf( PRINT_ALL, "R_LoadPSK: axBoneWeight_t(%i):\n"
		           "axBoneWeight_t::weight: %f\n"
		           "axBoneWeight_t::pointIndex %i\n"
		           "axBoneWeight_t::boneIndex: %i\n",
		           i,
		           axWeight->weight,
		           axWeight->pointIndex,
		           axWeight->boneIndex );
#endif
	}

	//
	// convert the model to an internal MD5 representation
	//
	md5->numBones = numReferenceBones;

	// calc numMeshes <number>

	/*
	numSmoothGroups = 0;
	for(i = 0, triangle = triangles; i < numTriangles; i++, triangle++)
	{
	        if(triangle->smoothingGroups)
	        {

	        }
	}
	*/

	if ( md5->numBones < 1 )
	{
		ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has no bones\n", modName );
		DeallocAll();
		return qfalse;
	}

	if ( md5->numBones > MAX_BONES )
	{
		ri.Printf( PRINT_WARNING, "R_LoadPSK: '%s' has more than %i bones (%i)\n", modName, MAX_BONES, md5->numBones );
		DeallocAll();
		return qfalse;
	}

	//ri.Printf(PRINT_ALL, "R_LoadPSK: '%s' has %i bones\n", modName, md5->numBones);

	// copy all reference bones
	md5->bones = (md5Bone_t*) ri.Hunk_Alloc( sizeof( *md5Bone ) * md5->numBones, h_low );

	for ( i = 0, md5Bone = md5->bones, refBone = refBones; i < md5->numBones; i++, md5Bone++, refBone++ )
	{
		Q_strncpyz( md5Bone->name, refBone->name, sizeof( md5Bone->name ) );

		if ( i == 0 )
		{
			md5Bone->parentIndex = refBone->parentIndex - 1;
		}
		else
		{
			md5Bone->parentIndex = refBone->parentIndex;
		}

		//ri.Printf(PRINT_ALL, "R_LoadPSK: '%s' has bone '%s' with parent index %i\n", modName, md5Bone->name, md5Bone->parentIndex);

		if ( md5Bone->parentIndex >= md5->numBones )
		{
			DeallocAll();
			ri.Error( ERR_DROP, "R_LoadPSK: '%s' has bone '%s' with bad parent index %i while numBones is %i", modName,
			          md5Bone->name, md5Bone->parentIndex, md5->numBones );
		}

		for ( j = 0; j < 3; j++ )
		{
			boneOrigin[ j ] = refBone->bone.position[ j ];
		}

		// Tr3B: I have really no idea why the .psk format stores the first quaternion with inverted quats.
		// Furthermore only the X and Z components of the first quat are inverted ?!?!
		if ( i == 0 )
		{
			boneQuat[ 0 ] = refBone->bone.quat[ 0 ];
			boneQuat[ 1 ] = -refBone->bone.quat[ 1 ];
			boneQuat[ 2 ] = refBone->bone.quat[ 2 ];
			boneQuat[ 3 ] = refBone->bone.quat[ 3 ];
		}
		else
		{
			boneQuat[ 0 ] = -refBone->bone.quat[ 0 ];
			boneQuat[ 1 ] = -refBone->bone.quat[ 1 ];
			boneQuat[ 2 ] = -refBone->bone.quat[ 2 ];
			boneQuat[ 3 ] = refBone->bone.quat[ 3 ];
		}

		VectorCopy( boneOrigin, md5Bone->origin );
		//MatrixTransformPoint(unrealToQuake, boneOrigin, md5Bone->origin);

		QuatCopy( boneQuat, md5Bone->rotation );

		//QuatClear(md5Bone->rotation);

#if 0
		ri.Printf( PRINT_ALL, "R_LoadPSK: md5Bone_t(%i):\n"
		           "md5Bone_t::name: '%s'\n"
		           "md5Bone_t::parentIndex: %i\n"
		           "md5Bone_t::quat: %f %f %f %f\n"
		           "md5bone_t::position: %f %f %f\n",
		           i,
		           md5Bone->name,
		           md5Bone->parentIndex,
		           md5Bone->rotation[ 0 ], md5Bone->rotation[ 1 ], md5Bone->rotation[ 2 ], md5Bone->rotation[ 3 ],
		           md5Bone->origin[ 0 ], md5Bone->origin[ 1 ], md5Bone->origin[ 2 ] );
#endif

		if ( md5Bone->parentIndex >= 0 )
		{
			vec3_t    rotated;
			quat_t    quat;

			md5Bone_t *parent;

			parent = &md5->bones[ md5Bone->parentIndex ];

			QuatTransformVector( parent->rotation, md5Bone->origin, rotated );
			//QuatTransformVector(md5Bone->rotation, md5Bone->origin, rotated);

			VectorAdd( parent->origin, rotated, md5Bone->origin );

			QuatMultiply1( parent->rotation, md5Bone->rotation, quat );
			QuatCopy( quat, md5Bone->rotation );
		}

#if 0
		ri.Printf( PRINT_ALL, "R_LoadPSK: md5Bone_t(%i):\n"
		           "md5Bone_t::name: '%s'\n"
		           "md5Bone_t::parentIndex: %i\n"
		           "md5Bone_t::quat: %f %f %f %f\n"
		           "md5bone_t::position: %f %f %f\n",
		           i,
		           md5Bone->name,
		           md5Bone->parentIndex,
		           md5Bone->rotation[ 0 ], md5Bone->rotation[ 1 ], md5Bone->rotation[ 2 ], md5Bone->rotation[ 3 ],
		           md5Bone->origin[ 0 ], md5Bone->origin[ 1 ], md5Bone->origin[ 2 ] );
#endif
	}

	Com_InitGrowList( &vboVertexes, 10000 );

	for ( i = 0, vertex = vertexes; i < numVertexes; i++, vertex++ )
	{
		md5Vertex_t *vboVert = (md5Vertex_t*) Com_Allocate( sizeof( *vboVert ) );

		for ( j = 0; j < 3; j++ )
		{
			vboVert->position[ j ] = points[ vertex->pointIndex ].point[ j ];
		}

		vboVert->texCoords[ 0 ] = vertex->st[ 0 ];
		vboVert->texCoords[ 1 ] = vertex->st[ 1 ];

		// find number of associated weights
		vboVert->numWeights = 0;

		for ( j = 0, axWeight = axWeights; j < numWeights; j++, axWeight++ )
		{
			if ( axWeight->pointIndex == vertex->pointIndex && axWeight->weight > 0.0f )
			{
				vboVert->numWeights++;
			}
		}

		if ( vboVert->numWeights > MAX_WEIGHTS )
		{
			DeallocAll();
			ri.Error( ERR_DROP, "R_LoadPSK: vertex %i requires more weights %i than the maximum of %i in model '%s'", i, vboVert->numWeights, MAX_WEIGHTS, modName );
			//ri.Printf(PRINT_WARNING, "R_LoadPSK: vertex %i requires more weights %i than the maximum of %i in model '%s'\n", i, vboVert->numWeights, MAX_WEIGHTS, modName);
		}

		for ( j = 0, axWeight = axWeights, k = 0; j < numWeights; j++, axWeight++ )
		{
			if ( axWeight->pointIndex == vertex->pointIndex && axWeight->weight > 0.0f )
			{
				vboVert->boneIndexes[ k ] = axWeight->boneIndex;
				vboVert->boneWeights[ k ] = axWeight->weight;
				k++;
			}
		}

		Com_AddToGrowList( &vboVertexes, vboVert );
	}

	ClearBounds( md5->bounds[ 0 ], md5->bounds[ 1 ] );

	for ( i = 0, vertex = vertexes; i < numVertexes; i++, vertex++ )
	{
		AddPointToBounds( points[ vertex->pointIndex ].point, md5->bounds[ 0 ], md5->bounds[ 1 ] );
	}

#if 0
	ri.Printf( PRINT_ALL, "R_LoadPSK: AABB (%i %i %i) (%i %i %i)\n",
	           ( int ) md5->bounds[ 0 ][ 0 ],
	           ( int ) md5->bounds[ 0 ][ 1 ],
	           ( int ) md5->bounds[ 0 ][ 2 ],
	           ( int ) md5->bounds[ 1 ][ 0 ],
	           ( int ) md5->bounds[ 1 ][ 1 ],
	           ( int ) md5->bounds[ 1 ][ 2 ] );
#endif

	// sort triangles
	qsort( triangles, numTriangles, sizeof( axTriangle_t ), CompareTrianglesByMaterialIndex );

	Com_InitGrowList( &sortedTriangles, 1000 );

	for ( i = 0, triangle = triangles; i < numTriangles; i++, triangle++ )
	{
		skelTriangle_t *sortTri = (skelTriangle_t*) Com_Allocate( sizeof( *sortTri ) );

		for ( j = 0; j < 3; j++ )
		{
			sortTri->indexes[ j ] = triangle->indexes[ j ];
			sortTri->vertexes[ j ] = (md5Vertex_t*) Com_GrowListElement( &vboVertexes, triangle->indexes[ j ] );
		}

		sortTri->referenced = qfalse;

		Com_AddToGrowList( &sortedTriangles, sortTri );
	}

	// calc tangent spaces
#if 1
	{
		md5Vertex_t *v0, *v1, *v2;
		const float *p0, *p1, *p2;
		const float *t0, *t1, *t2;
		vec3_t      tangent;
		vec3_t      binormal;
		vec3_t      normal;

		for ( j = 0; j < vboVertexes.currentElements; j++ )
		{
			v0 = (md5Vertex_t*) Com_GrowListElement( &vboVertexes, j );

			VectorClear( v0->tangent );
			VectorClear( v0->binormal );
			VectorClear( v0->normal );
		}

		for ( j = 0; j < sortedTriangles.currentElements; j++ )
		{
			skelTriangle_t *tri = (skelTriangle_t*) Com_GrowListElement( &sortedTriangles, j );

			v0 = (md5Vertex_t*) Com_GrowListElement( &vboVertexes, tri->indexes[ 0 ] );
			v1 = (md5Vertex_t*) Com_GrowListElement( &vboVertexes, tri->indexes[ 1 ] );
			v2 = (md5Vertex_t*) Com_GrowListElement( &vboVertexes, tri->indexes[ 2 ] );

			p0 = v0->position;
			p1 = v1->position;
			p2 = v2->position;

			t0 = v0->texCoords;
			t1 = v1->texCoords;
			t2 = v2->texCoords;

#if 1
			R_CalcTangentSpace( tangent, binormal, normal, p0, p1, p2, t0, t1, t2 );
#else
			R_CalcNormalForTriangle( normal, p0, p1, p2 );
			R_CalcTangentsForTriangle( tangent, binormal, p0, p1, p2, t0, t1, t2 );
#endif

			for ( k = 0; k < 3; k++ )
			{
				float *v;

				v0 = (md5Vertex_t*) Com_GrowListElement( &vboVertexes, tri->indexes[ k ] );

				v = v0->tangent;
				VectorAdd( v, tangent, v );

				v = v0->binormal;
				VectorAdd( v, binormal, v );

				v = v0->normal;
				VectorAdd( v, normal, v );
			}
		}

		for ( j = 0; j < vboVertexes.currentElements; j++ )
		{
			v0 = (md5Vertex_t*) Com_GrowListElement( &vboVertexes, j );

			VectorNormalize( v0->tangent );
			VectorNormalize( v0->binormal );
			VectorNormalize( v0->normal );
		}
	}
#else
	{
		float       bb, s, t;
		vec3_t      bary;
		vec3_t      faceNormal;
		md5Vertex_t *dv[ 3 ];

		for ( j = 0; j < sortedTriangles.currentElements; j++ )
		{
			skelTriangle_t *tri = Com_GrowListElement( &sortedTriangles, j );

			dv[ 0 ] = Com_GrowListElement( &vboVertexes, tri->indexes[ 0 ] );
			dv[ 1 ] = Com_GrowListElement( &vboVertexes, tri->indexes[ 1 ] );
			dv[ 2 ] = Com_GrowListElement( &vboVertexes, tri->indexes[ 2 ] );

			R_CalcNormalForTriangle( faceNormal, dv[ 0 ]->position, dv[ 1 ]->position, dv[ 2 ]->position );

			// calculate barycentric basis for the triangle
			bb = ( dv[ 1 ]->texCoords[ 0 ] - dv[ 0 ]->texCoords[ 0 ] ) * ( dv[ 2 ]->texCoords[ 1 ] - dv[ 0 ]->texCoords[ 1 ] ) - ( dv[ 2 ]->texCoords[ 0 ] - dv[ 0 ]->texCoords[ 0 ] ) * ( dv[ 1 ]->texCoords[ 1 ] -
			     dv[ 0 ]->texCoords[ 1 ] );

			if ( fabs( bb ) < 0.00000001f )
			{
				continue;
			}

			// do each vertex
			for ( k = 0; k < 3; k++ )
			{
				// calculate s tangent vector
				s = dv[ k ]->texCoords[ 0 ] + 10.0f;
				t = dv[ k ]->texCoords[ 1 ];
				bary[ 0 ] = ( ( dv[ 1 ]->texCoords[ 0 ] - s ) * ( dv[ 2 ]->texCoords[ 1 ] - t ) - ( dv[ 2 ]->texCoords[ 0 ] - s ) * ( dv[ 1 ]->texCoords[ 1 ] - t ) ) / bb;
				bary[ 1 ] = ( ( dv[ 2 ]->texCoords[ 0 ] - s ) * ( dv[ 0 ]->texCoords[ 1 ] - t ) - ( dv[ 0 ]->texCoords[ 0 ] - s ) * ( dv[ 2 ]->texCoords[ 1 ] - t ) ) / bb;
				bary[ 2 ] = ( ( dv[ 0 ]->texCoords[ 0 ] - s ) * ( dv[ 1 ]->texCoords[ 1 ] - t ) - ( dv[ 1 ]->texCoords[ 0 ] - s ) * ( dv[ 0 ]->texCoords[ 1 ] - t ) ) / bb;

				dv[ k ]->tangent[ 0 ] = bary[ 0 ] * dv[ 0 ]->position[ 0 ] + bary[ 1 ] * dv[ 1 ]->position[ 0 ] + bary[ 2 ] * dv[ 2 ]->position[ 0 ];
				dv[ k ]->tangent[ 1 ] = bary[ 0 ] * dv[ 0 ]->position[ 1 ] + bary[ 1 ] * dv[ 1 ]->position[ 1 ] + bary[ 2 ] * dv[ 2 ]->position[ 1 ];
				dv[ k ]->tangent[ 2 ] = bary[ 0 ] * dv[ 0 ]->position[ 2 ] + bary[ 1 ] * dv[ 1 ]->position[ 2 ] + bary[ 2 ] * dv[ 2 ]->position[ 2 ];

				VectorSubtract( dv[ k ]->tangent, dv[ k ]->position, dv[ k ]->tangent );
				VectorNormalize( dv[ k ]->tangent );

				// calculate t tangent vector (binormal)
				s = dv[ k ]->texCoords[ 0 ];
				t = dv[ k ]->texCoords[ 1 ] + 10.0f;
				bary[ 0 ] = ( ( dv[ 1 ]->texCoords[ 0 ] - s ) * ( dv[ 2 ]->texCoords[ 1 ] - t ) - ( dv[ 2 ]->texCoords[ 0 ] - s ) * ( dv[ 1 ]->texCoords[ 1 ] - t ) ) / bb;
				bary[ 1 ] = ( ( dv[ 2 ]->texCoords[ 0 ] - s ) * ( dv[ 0 ]->texCoords[ 1 ] - t ) - ( dv[ 0 ]->texCoords[ 0 ] - s ) * ( dv[ 2 ]->texCoords[ 1 ] - t ) ) / bb;
				bary[ 2 ] = ( ( dv[ 0 ]->texCoords[ 0 ] - s ) * ( dv[ 1 ]->texCoords[ 1 ] - t ) - ( dv[ 1 ]->texCoords[ 0 ] - s ) * ( dv[ 0 ]->texCoords[ 1 ] - t ) ) / bb;

				dv[ k ]->binormal[ 0 ] = bary[ 0 ] * dv[ 0 ]->position[ 0 ] + bary[ 1 ] * dv[ 1 ]->position[ 0 ] + bary[ 2 ] * dv[ 2 ]->position[ 0 ];
				dv[ k ]->binormal[ 1 ] = bary[ 0 ] * dv[ 0 ]->position[ 1 ] + bary[ 1 ] * dv[ 1 ]->position[ 1 ] + bary[ 2 ] * dv[ 2 ]->position[ 1 ];
				dv[ k ]->binormal[ 2 ] = bary[ 0 ] * dv[ 0 ]->position[ 2 ] + bary[ 1 ] * dv[ 1 ]->position[ 2 ] + bary[ 2 ] * dv[ 2 ]->position[ 2 ];

				VectorSubtract( dv[ k ]->binormal, dv[ k ]->position, dv[ k ]->binormal );
				VectorNormalize( dv[ k ]->binormal );

				// calculate the normal as cross product N=TxB
#if 0
				CrossProduct( dv[ k ]->tangent, dv[ k ]->binormal, dv[ k ]->normal );
				VectorNormalize( dv[ k ]->normal );

				// Gram-Schmidt orthogonalization process for B
				// compute the cross product B=NxT to obtain
				// an orthogonal basis
				CrossProduct( dv[ k ]->normal, dv[ k ]->tangent, dv[ k ]->binormal );

				if ( DotProduct( dv[ k ]->normal, faceNormal ) < 0 )
				{
					VectorInverse( dv[ k ]->normal );
					//VectorInverse(dv[k]->tangent);
					//VectorInverse(dv[k]->binormal);
				}

#else
				VectorAdd( dv[ k ]->normal, faceNormal, dv[ k ]->normal );
#endif
			}
		}

#if 1

		for ( j = 0; j < vboVertexes.currentElements; j++ )
		{
			dv[ 0 ] = Com_GrowListElement( &vboVertexes, j );
			//VectorNormalize(dv[0]->tangent);
			//VectorNormalize(dv[0]->binormal);
			VectorNormalize( dv[ 0 ]->normal );
		}

#endif
	}
#endif

#if 0
	{
		md5Vertex_t *v0, *v1;

		// do another extra smoothing for normals to avoid flat shading
		for ( j = 0; j < vboVertexes.currentElements; j++ )
		{
			v0 = Com_GrowListElement( &vboVertexes, j );

			for ( k = 0; k < vboVertexes.currentElements; k++ )
			{
				if ( j == k )
				{
					continue;
				}

				v1 = Com_GrowListElement( &vboVertexes, k );

				if ( VectorCompare( v0->position, v1->position ) )
				{
					VectorAdd( v0->position, v1->normal, v0->normal );
				}
			}

			VectorNormalize( v0->normal );
		}
	}
#endif

	// split the surfaces into VBO surfaces by the maximum number of GPU vertex skinning bones
	Com_InitGrowList( &vboSurfaces, 10 );

	materialIndex = oldMaterialIndex = -1;

	for ( i = 0; i < numTriangles; i++ )
	{
		triangle = &triangles[ i ];
		materialIndex = triangle->materialIndex;

		if ( materialIndex != oldMaterialIndex )
		{
			oldMaterialIndex = materialIndex;

			numRemaining = sortedTriangles.currentElements - i;

			while ( numRemaining )
			{
				numBoneReferences = 0;
				Com_Memset( boneReferences, 0, sizeof( boneReferences ) );

				Com_InitGrowList( &vboTriangles, 1000 );

				for ( j = i; j < sortedTriangles.currentElements; j++ )
				{
					skelTriangle_t *sortTri;

					triangle = &triangles[ j ];
					materialIndex = triangle->materialIndex;

					if ( materialIndex != oldMaterialIndex )
					{
						continue;
					}

					sortTri = (skelTriangle_t*) Com_GrowListElement( &sortedTriangles, j );

					if ( sortTri->referenced )
					{
						continue;
					}

					if ( AddTriangleToVBOTriangleList( &vboTriangles, sortTri, &numBoneReferences, boneReferences ) )
					{
						sortTri->referenced = qtrue;
					}
				}

				for ( j = 0; j < MAX_BONES; j++ )
				{
					if ( boneReferences[ j ] > 0 )
					{
						ri.Printf( PRINT_ALL, "R_LoadPSK: referenced bone: '%s'\n", ( j < numReferenceBones ) ? refBones[ j ].name : NULL );
					}
				}

				if ( !vboTriangles.currentElements )
				{
					ri.Printf( PRINT_WARNING, "R_LoadPSK: could not add triangles to a remaining VBO surface for model '%s'\n", modName );
					break;
				}

				// FIXME skinIndex
				AddSurfaceToVBOSurfacesList2( &vboSurfaces, &vboTriangles, &vboVertexes, md5, vboSurfaces.currentElements, materials[ oldMaterialIndex ].name, numBoneReferences, boneReferences );
				numRemaining -= vboTriangles.currentElements;

				Com_DestroyGrowList( &vboTriangles );
			}
		}
	}

	for ( j = 0; j < sortedTriangles.currentElements; j++ )
	{
		skelTriangle_t *sortTri = (skelTriangle_t*) Com_GrowListElement( &sortedTriangles, j );
		Com_Dealloc( sortTri );
	}

	Com_DestroyGrowList( &sortedTriangles );

	for ( j = 0; j < vboVertexes.currentElements; j++ )
	{
		md5Vertex_t *v = (md5Vertex_t*) Com_GrowListElement( &vboVertexes, j );
		Com_Dealloc( v );
	}

	Com_DestroyGrowList( &vboVertexes );

	// move VBO surfaces list to hunk
	md5->numVBOSurfaces = vboSurfaces.currentElements;
	md5->vboSurfaces = (srfVBOMD5Mesh_t**) ri.Hunk_Alloc( md5->numVBOSurfaces * sizeof( *md5->vboSurfaces ), h_low );

	for ( i = 0; i < md5->numVBOSurfaces; i++ )
	{
		md5->vboSurfaces[ i ] = ( srfVBOMD5Mesh_t * ) Com_GrowListElement( &vboSurfaces, i );
	}

	Com_DestroyGrowList( &vboSurfaces );

	FreeMemStream( stream );
	Com_Dealloc( points );
	Com_Dealloc( vertexes );
	Com_Dealloc( triangles );
	Com_Dealloc( materials );

	ri.Printf( PRINT_DEVELOPER, "%i VBO surfaces created for PSK model '%s'\n", md5->numVBOSurfaces, modName );

	return qtrue;
}
예제 #12
0
iqmblend_t *
Mod_IQMBuildBlendPalette (iqm_t *iqm, int *size)
{
	int         i, j;
	iqmvertexarray *bindices = 0;
	iqmvertexarray *bweights = 0;
	iqmblend_t *blend_list;
	int         num_blends;
	hashtab_t  *blend_hash;

	for (i = 0; i < iqm->num_arrays; i++) {
		if (iqm->vertexarrays[i].type == IQM_BLENDINDEXES)
			bindices = &iqm->vertexarrays[i];
		if (iqm->vertexarrays[i].type == IQM_BLENDWEIGHTS)
			bweights = &iqm->vertexarrays[i];
	}
	if (!bindices || !bweights) {
		// Not necessarily an error: might be a static model with no bones
		// Either way, no need to make a blend palette
		Sys_MaskPrintf (SYS_MODEL, "bone index or weight array missing\n");
		*size = 0;
		return 0;
	}

	blend_list = calloc (MAX_BLENDS, sizeof (iqmblend_t));
	for (i = 0; i < iqm->num_joints; i++) {
		blend_list[i].indices[0] = i;
		blend_list[i].weights[0] = 255;
	}
	num_blends = iqm->num_joints;

	blend_hash = Hash_NewTable (1023, 0, 0, 0);
	Hash_SetHashCompare (blend_hash, blend_get_hash, blend_compare);

	for (i = 0; i < iqm->num_verts; i++) {
		byte       *vert = iqm->vertices + i * iqm->stride;
		byte       *bi = vert + bindices->offset;
		byte       *bw = vert + bweights->offset;
		iqmblend_t  blend;
		iqmblend_t *bl;

		// First, canonicalize vextex bone data:
		//   bone indices are in increasing order
		//   bone weight of zero is never followed by a non-zero weight
		//   bone weight of zero has bone index of zero

		// if the weight is zero, ensure the index is also zero
		// also, ensure non-zero weights never follow zero weights
		for (j = 0; j < 4; j++) {
			if (!bw[j]) {
				bi[j] = 0;
			} else {
				if (j && !bw[j-1]) {
					swap_bones (bi, bw, j - 1, j);
					j = 0;					// force a rescan
				}
			}
		}
		// sort the bones such that the indeces are increasing (unless the
		// weight is zero)
		for (j = 0; j < 3; j++) {
			if (!bw[j+1])					// zero weight == end of list
				break;
			if (bi[j] > bi[j+1]) {
				swap_bones (bi, bw, j, j + 1);
				j = -1;						// force rescan
			}
		}

		// Now that the bone data is canonical, it can be hashed.
		// However, no need to check other combinations if the vertex has
		// only one influencing bone: the bone index will only change format.
		if (!bw[1]) {
			*(uint32_t *) bi = bi[0];
			continue;
		}
		QuatCopy (bi, blend.indices);
		QuatCopy (bw, blend.weights);
		if ((bl = Hash_FindElement (blend_hash, &blend))) {
			*(uint32_t *) bi = (bl - blend_list);
			continue;
		}
		if (num_blends >= MAX_BLENDS)
			Sys_Error ("Too many blends. Tell taniwha to stop being lazy.");
		blend_list[num_blends] = blend;
		Hash_AddElement (blend_hash, &blend_list[num_blends]);
		*(uint32_t *) bi = num_blends;
		num_blends++;
	}

	Hash_DelTable (blend_hash);
	*size = num_blends;
	return realloc (blend_list, num_blends * sizeof (iqmblend_t));
}
예제 #13
0
qboolean R_LoadMD5(model_t *mod, void *buffer, int bufferSize, const char *modName)
{
	int           i, j, k;
	md5Model_t    *md5;
	md5Bone_t     *bone;
	md5Surface_t  *surf;
	srfTriangle_t *tri;
	md5Vertex_t   *v;
	md5Weight_t   *weight;
	int           version;
	shader_t      *sh;
	char          *buf_p = ( char * ) buffer;
	char          *token;
	vec3_t        boneOrigin;
	quat_t        boneQuat;
	matrix_t      boneMat;
	int           numRemaining;
	growList_t    sortedTriangles;
	growList_t    vboTriangles;
	growList_t    vboSurfaces;
	int           numBoneReferences;
	int           boneReferences[MAX_BONES];

	// skip MD5Version indent string
	COM_ParseExt2(&buf_p, qfalse);

	// check version
	token   = COM_ParseExt2(&buf_p, qfalse);
	version = atoi(token);

	if (version != MD5_VERSION)
	{
		Ren_Warning("R_LoadMD5: %s has wrong version (%i should be %i)\n", modName, version, MD5_VERSION);
		return qfalse;
	}

	mod->type      = MOD_MD5;
	mod->dataSize += sizeof(md5Model_t);
	md5            = mod->md5 = ri.Hunk_Alloc(sizeof(md5Model_t), h_low);

	// skip commandline <arguments string>
	token = COM_ParseExt2(&buf_p, qtrue);
	token = COM_ParseExt2(&buf_p, qtrue);
	//  Ren_Print("%s\n", token);

	// parse numJoints <number>
	token = COM_ParseExt2(&buf_p, qtrue);

	if (Q_stricmp(token, "numJoints"))
	{
		Ren_Warning("R_LoadMD5: expected 'numJoints' found '%s' in model '%s'\n", token, modName);
		return qfalse;
	}

	token         = COM_ParseExt2(&buf_p, qfalse);
	md5->numBones = atoi(token);

	// parse numMeshes <number>
	token = COM_ParseExt2(&buf_p, qtrue);

	if (Q_stricmp(token, "numMeshes"))
	{
		Ren_Warning("R_LoadMD5: expected 'numMeshes' found '%s' in model '%s'\n", token, modName);
		return qfalse;
	}

	token            = COM_ParseExt2(&buf_p, qfalse);
	md5->numSurfaces = atoi(token);
	//Ren_Print("R_LoadMD5: '%s' has %i surfaces\n", modName, md5->numSurfaces);

	if (md5->numBones < 1)
	{
		Ren_Warning("R_LoadMD5: '%s' has no bones\n", modName);
		return qfalse;
	}

	if (md5->numBones > MAX_BONES)
	{
		Ren_Warning("R_LoadMD5: '%s' has more than %i bones (%i)\n", modName, MAX_BONES, md5->numBones);
		return qfalse;
	}

	//Ren_Print("R_LoadMD5: '%s' has %i bones\n", modName, md5->numBones);

	// parse all the bones
	md5->bones = ri.Hunk_Alloc(sizeof(*bone) * md5->numBones, h_low);

	// parse joints {
	token = COM_ParseExt2(&buf_p, qtrue);

	if (Q_stricmp(token, "joints"))
	{
		Ren_Warning("R_LoadMD5: expected 'joints' found '%s' in model '%s'\n", token, modName);
		return qfalse;
	}

	token = COM_ParseExt2(&buf_p, qfalse);

	if (Q_stricmp(token, "{"))
	{
		Ren_Warning("R_LoadMD5: expected '{' found '%s' in model '%s'\n", token, modName);
		return qfalse;
	}

	for (i = 0, bone = md5->bones; i < md5->numBones; i++, bone++)
	{
		token = COM_ParseExt2(&buf_p, qtrue);
		Q_strncpyz(bone->name, token, sizeof(bone->name));

		//Ren_Print("R_LoadMD5: '%s' has bone '%s'\n", modName, bone->name);

		token             = COM_ParseExt2(&buf_p, qfalse);
		bone->parentIndex = atoi(token);

		//Ren_Print("R_LoadMD5: '%s' has bone '%s' with parent index %i\n", modName, bone->name, bone->parentIndex);

		if (bone->parentIndex >= md5->numBones)
		{
			Ren_Drop("R_LoadMD5: '%s' has bone '%s' with bad parent index %i while numBones is %i", modName,
			         bone->name, bone->parentIndex, md5->numBones);
		}

		// skip (
		token = COM_ParseExt2(&buf_p, qfalse);

		if (Q_stricmp(token, "("))
		{
			Ren_Warning("R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName);
			return qfalse;
		}

		for (j = 0; j < 3; j++)
		{
			token         = COM_ParseExt2(&buf_p, qfalse);
			boneOrigin[j] = atof(token);
		}

		// skip )
		token = COM_ParseExt2(&buf_p, qfalse);

		if (Q_stricmp(token, ")"))
		{
			Ren_Warning("R_LoadMD5: expected ')' found '%s' in model '%s'\n", token, modName);
			return qfalse;
		}

		// skip (
		token = COM_ParseExt2(&buf_p, qfalse);

		if (Q_stricmp(token, "("))
		{
			Ren_Warning("R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName);
			return qfalse;
		}

		for (j = 0; j < 3; j++)
		{
			token       = COM_ParseExt2(&buf_p, qfalse);
			boneQuat[j] = atof(token);
		}

		QuatCalcW(boneQuat);
		MatrixFromQuat(boneMat, boneQuat);

		VectorCopy(boneOrigin, bone->origin);
		QuatCopy(boneQuat, bone->rotation);

		MatrixSetupTransformFromQuat(bone->inverseTransform, boneQuat, boneOrigin);
		MatrixInverse(bone->inverseTransform);

		// skip )
		token = COM_ParseExt2(&buf_p, qfalse);

		if (Q_stricmp(token, ")"))
		{
			Ren_Warning("R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName);
			return qfalse;
		}
	}

	// parse }
	token = COM_ParseExt2(&buf_p, qtrue);

	if (Q_stricmp(token, "}"))
	{
		Ren_Warning("R_LoadMD5: expected '}' found '%s' in model '%s'\n", token, modName);
		return qfalse;
	}

	// parse all the surfaces
	if (md5->numSurfaces < 1)
	{
		Ren_Warning("R_LoadMD5: '%s' has no surfaces\n", modName);
		return qfalse;
	}

	//Ren_Print("R_LoadMD5: '%s' has %i surfaces\n", modName, md5->numSurfaces);

	md5->surfaces = ri.Hunk_Alloc(sizeof(*surf) * md5->numSurfaces, h_low);

	for (i = 0, surf = md5->surfaces; i < md5->numSurfaces; i++, surf++)
	{
		// parse mesh {
		token = COM_ParseExt2(&buf_p, qtrue);

		if (Q_stricmp(token, "mesh"))
		{
			Ren_Warning("R_LoadMD5: expected 'mesh' found '%s' in model '%s'\n", token, modName);
			return qfalse;
		}

		token = COM_ParseExt2(&buf_p, qfalse);

		if (Q_stricmp(token, "{"))
		{
			Ren_Warning("R_LoadMD5: expected '{' found '%s' in model '%s'\n", token, modName);
			return qfalse;
		}

		// change to surface identifier
		surf->surfaceType = SF_MD5;

		// give pointer to model for Tess_SurfaceMD5
		surf->model = md5;

		// parse shader <name>
		token = COM_ParseExt2(&buf_p, qtrue);

		if (Q_stricmp(token, "shader"))
		{
			Ren_Warning("R_LoadMD5: expected 'shader' found '%s' in model '%s'\n", token, modName);
			return qfalse;
		}

		token = COM_ParseExt2(&buf_p, qfalse);
		Q_strncpyz(surf->shader, token, sizeof(surf->shader));

		//Ren_Print("R_LoadMD5: '%s' uses shader '%s'\n", modName, surf->shader);

		// FIXME .md5mesh meshes don't have surface names
		// lowercase the surface name so skin compares are faster
		//Q_strlwr(surf->name);
		//Ren_Print("R_LoadMD5: '%s' has surface '%s'\n", modName, surf->name);

		// register the shaders
		sh = R_FindShader(surf->shader, SHADER_3D_DYNAMIC, qtrue);

		if (sh->defaultShader)
		{
			surf->shaderIndex = 0;
		}
		else
		{
			surf->shaderIndex = sh->index;
		}

		// parse numVerts <number>
		token = COM_ParseExt2(&buf_p, qtrue);

		if (Q_stricmp(token, "numVerts"))
		{
			Ren_Warning("R_LoadMD5: expected 'numVerts' found '%s' in model '%s'\n", token, modName);
			return qfalse;
		}

		token          = COM_ParseExt2(&buf_p, qfalse);
		surf->numVerts = atoi(token);

		if (surf->numVerts > SHADER_MAX_VERTEXES)
		{
			Ren_Drop("R_LoadMD5: '%s' has more than %i verts on a surface (%i)",
			         modName, SHADER_MAX_VERTEXES, surf->numVerts);
		}

		surf->verts = ri.Hunk_Alloc(sizeof(*v) * surf->numVerts, h_low);

		for (j = 0, v = surf->verts; j < surf->numVerts; j++, v++)
		{
			// skip vert <number>
			token = COM_ParseExt2(&buf_p, qtrue);

			if (Q_stricmp(token, "vert"))
			{
				Ren_Warning("R_LoadMD5: expected 'vert' found '%s' in model '%s'\n", token, modName);
				return qfalse;
			}

			COM_ParseExt2(&buf_p, qfalse);

			// skip (
			token = COM_ParseExt2(&buf_p, qfalse);

			if (Q_stricmp(token, "("))
			{
				Ren_Warning("R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName);
				return qfalse;
			}

			for (k = 0; k < 2; k++)
			{
				token           = COM_ParseExt2(&buf_p, qfalse);
				v->texCoords[k] = atof(token);
			}

			// skip )
			token = COM_ParseExt2(&buf_p, qfalse);

			if (Q_stricmp(token, ")"))
			{
				Ren_Warning("R_LoadMD5: expected ')' found '%s' in model '%s'\n", token, modName);
				return qfalse;
			}

			token          = COM_ParseExt2(&buf_p, qfalse);
			v->firstWeight = atoi(token);

			token         = COM_ParseExt2(&buf_p, qfalse);
			v->numWeights = atoi(token);

			if (v->numWeights > MAX_WEIGHTS)
			{
				Ren_Drop("R_LoadMD5: vertex %i requires more than %i weights on surface (%i) in model '%s'",
				         j, MAX_WEIGHTS, i, modName);
			}
		}

		// parse numTris <number>
		token = COM_ParseExt2(&buf_p, qtrue);

		if (Q_stricmp(token, "numTris"))
		{
			Ren_Warning("R_LoadMD5: expected 'numTris' found '%s' in model '%s'\n", token, modName);
			return qfalse;
		}

		token              = COM_ParseExt2(&buf_p, qfalse);
		surf->numTriangles = atoi(token);

		if (surf->numTriangles > SHADER_MAX_TRIANGLES)
		{
			Ren_Drop("R_LoadMD5: '%s' has more than %i triangles on a surface (%i)",
			         modName, SHADER_MAX_TRIANGLES, surf->numTriangles);
		}

		surf->triangles = ri.Hunk_Alloc(sizeof(*tri) * surf->numTriangles, h_low);

		for (j = 0, tri = surf->triangles; j < surf->numTriangles; j++, tri++)
		{
			// skip tri <number>
			token = COM_ParseExt2(&buf_p, qtrue);

			if (Q_stricmp(token, "tri"))
			{
				Ren_Warning("R_LoadMD5: expected 'tri' found '%s' in model '%s'\n", token, modName);
				return qfalse;
			}

			COM_ParseExt2(&buf_p, qfalse);

			for (k = 0; k < 3; k++)
			{
				token           = COM_ParseExt2(&buf_p, qfalse);
				tri->indexes[k] = atoi(token);
			}
		}

		// parse numWeights <number>
		token = COM_ParseExt2(&buf_p, qtrue);

		if (Q_stricmp(token, "numWeights"))
		{
			Ren_Warning("R_LoadMD5: expected 'numWeights' found '%s' in model '%s'\n", token, modName);
			return qfalse;
		}

		token            = COM_ParseExt2(&buf_p, qfalse);
		surf->numWeights = atoi(token);

		surf->weights = ri.Hunk_Alloc(sizeof(*weight) * surf->numWeights, h_low);

		for (j = 0, weight = surf->weights; j < surf->numWeights; j++, weight++)
		{
			// skip weight <number>
			token = COM_ParseExt2(&buf_p, qtrue);

			if (Q_stricmp(token, "weight"))
			{
				Ren_Warning("R_LoadMD5: expected 'weight' found '%s' in model '%s'\n", token, modName);
				return qfalse;
			}

			COM_ParseExt2(&buf_p, qfalse);

			token             = COM_ParseExt2(&buf_p, qfalse);
			weight->boneIndex = atoi(token);

			token              = COM_ParseExt2(&buf_p, qfalse);
			weight->boneWeight = atof(token);

			// skip (
			token = COM_ParseExt2(&buf_p, qfalse);

			if (Q_stricmp(token, "("))
			{
				Ren_Warning("R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName);
				return qfalse;
			}

			for (k = 0; k < 3; k++)
			{
				token             = COM_ParseExt2(&buf_p, qfalse);
				weight->offset[k] = atof(token);
			}

			// skip )
			token = COM_ParseExt2(&buf_p, qfalse);

			if (Q_stricmp(token, ")"))
			{
				Ren_Warning("R_LoadMD5: expected ')' found '%s' in model '%s'\n", token, modName);
				return qfalse;
			}
		}

		// parse }
		token = COM_ParseExt2(&buf_p, qtrue);

		if (Q_stricmp(token, "}"))
		{
			Ren_Warning("R_LoadMD5: expected '}' found '%s' in model '%s'\n", token, modName);
			return qfalse;
		}

		// loop trough all vertices and set up the vertex weights
		for (j = 0, v = surf->verts; j < surf->numVerts; j++, v++)
		{
			v->weights = ri.Hunk_Alloc(sizeof(*v->weights) * v->numWeights, h_low);

			for (k = 0; k < v->numWeights; k++)
			{
				v->weights[k] = surf->weights + (v->firstWeight + k);
			}
		}
	}

	// loading is done now calculate the bounding box and tangent spaces
	ClearBounds(md5->bounds[0], md5->bounds[1]);

	for (i = 0, surf = md5->surfaces; i < md5->numSurfaces; i++, surf++)
	{
		for (j = 0, v = surf->verts; j < surf->numVerts; j++, v++)
		{
			vec3_t      tmpVert;
			md5Weight_t *w;

			VectorClear(tmpVert);

			for (k = 0, w = v->weights[0]; k < v->numWeights; k++, w++)
			{
				vec3_t offsetVec;

				bone = &md5->bones[w->boneIndex];

				QuatTransformVector(bone->rotation, w->offset, offsetVec);
				VectorAdd(bone->origin, offsetVec, offsetVec);

				VectorMA(tmpVert, w->boneWeight, offsetVec, tmpVert);
			}

			VectorCopy(tmpVert, v->position);
			AddPointToBounds(tmpVert, md5->bounds[0], md5->bounds[1]);
		}

		// calc tangent spaces
#if 1
		{
			const float *v0, *v1, *v2;
			const float *t0, *t1, *t2;
			vec3_t      tangent;
			vec3_t      binormal;
			vec3_t      normal;

			for (j = 0, v = surf->verts; j < surf->numVerts; j++, v++)
			{
				VectorClear(v->tangent);
				VectorClear(v->binormal);
				VectorClear(v->normal);
			}

			for (j = 0, tri = surf->triangles; j < surf->numTriangles; j++, tri++)
			{
				v0 = surf->verts[tri->indexes[0]].position;
				v1 = surf->verts[tri->indexes[1]].position;
				v2 = surf->verts[tri->indexes[2]].position;

				t0 = surf->verts[tri->indexes[0]].texCoords;
				t1 = surf->verts[tri->indexes[1]].texCoords;
				t2 = surf->verts[tri->indexes[2]].texCoords;

#if 1
				R_CalcTangentSpace(tangent, binormal, normal, v0, v1, v2, t0, t1, t2);
#else
				R_CalcNormalForTriangle(normal, v0, v1, v2);
				R_CalcTangentsForTriangle(tangent, binormal, v0, v1, v2, t0, t1, t2);
#endif

				for (k = 0; k < 3; k++)
				{
					float *v;

					v = surf->verts[tri->indexes[k]].tangent;
					VectorAdd(v, tangent, v);

					v = surf->verts[tri->indexes[k]].binormal;
					VectorAdd(v, binormal, v);

					v = surf->verts[tri->indexes[k]].normal;
					VectorAdd(v, normal, v);
				}
			}

			for (j = 0, v = surf->verts; j < surf->numVerts; j++, v++)
			{
				VectorNormalize(v->tangent);
				VectorNormalize(v->binormal);
				VectorNormalize(v->normal);
			}
		}
#else
		{
			int         k;
			float       bb, s, t;
			vec3_t      bary;
			vec3_t      faceNormal;
			md5Vertex_t *dv[3];

			for (j = 0, tri = surf->triangles; j < surf->numTriangles; j++, tri++)
			{
				dv[0] = &surf->verts[tri->indexes[0]];
				dv[1] = &surf->verts[tri->indexes[1]];
				dv[2] = &surf->verts[tri->indexes[2]];

				R_CalcNormalForTriangle(faceNormal, dv[0]->position, dv[1]->position, dv[2]->position);

				// calculate barycentric basis for the triangle
				bb = (dv[1]->texCoords[0] - dv[0]->texCoords[0]) * (dv[2]->texCoords[1] - dv[0]->texCoords[1]) - (dv[2]->texCoords[0] - dv[0]->texCoords[0]) * (dv[1]->texCoords[1] -
				                                                                                                                                                dv[0]->texCoords[1]);

				if (fabs(bb) < 0.00000001f)
				{
					continue;
				}

				// do each vertex
				for (k = 0; k < 3; k++)
				{
					// calculate s tangent vector
					s       = dv[k]->texCoords[0] + 10.0f;
					t       = dv[k]->texCoords[1];
					bary[0] = ((dv[1]->texCoords[0] - s) * (dv[2]->texCoords[1] - t) - (dv[2]->texCoords[0] - s) * (dv[1]->texCoords[1] - t)) / bb;
					bary[1] = ((dv[2]->texCoords[0] - s) * (dv[0]->texCoords[1] - t) - (dv[0]->texCoords[0] - s) * (dv[2]->texCoords[1] - t)) / bb;
					bary[2] = ((dv[0]->texCoords[0] - s) * (dv[1]->texCoords[1] - t) - (dv[1]->texCoords[0] - s) * (dv[0]->texCoords[1] - t)) / bb;

					dv[k]->tangent[0] = bary[0] * dv[0]->position[0] + bary[1] * dv[1]->position[0] + bary[2] * dv[2]->position[0];
					dv[k]->tangent[1] = bary[0] * dv[0]->position[1] + bary[1] * dv[1]->position[1] + bary[2] * dv[2]->position[1];
					dv[k]->tangent[2] = bary[0] * dv[0]->position[2] + bary[1] * dv[1]->position[2] + bary[2] * dv[2]->position[2];

					VectorSubtract(dv[k]->tangent, dv[k]->position, dv[k]->tangent);
					VectorNormalize(dv[k]->tangent);

					// calculate t tangent vector (binormal)
					s       = dv[k]->texCoords[0];
					t       = dv[k]->texCoords[1] + 10.0f;
					bary[0] = ((dv[1]->texCoords[0] - s) * (dv[2]->texCoords[1] - t) - (dv[2]->texCoords[0] - s) * (dv[1]->texCoords[1] - t)) / bb;
					bary[1] = ((dv[2]->texCoords[0] - s) * (dv[0]->texCoords[1] - t) - (dv[0]->texCoords[0] - s) * (dv[2]->texCoords[1] - t)) / bb;
					bary[2] = ((dv[0]->texCoords[0] - s) * (dv[1]->texCoords[1] - t) - (dv[1]->texCoords[0] - s) * (dv[0]->texCoords[1] - t)) / bb;

					dv[k]->binormal[0] = bary[0] * dv[0]->position[0] + bary[1] * dv[1]->position[0] + bary[2] * dv[2]->position[0];
					dv[k]->binormal[1] = bary[0] * dv[0]->position[1] + bary[1] * dv[1]->position[1] + bary[2] * dv[2]->position[1];
					dv[k]->binormal[2] = bary[0] * dv[0]->position[2] + bary[1] * dv[1]->position[2] + bary[2] * dv[2]->position[2];

					VectorSubtract(dv[k]->binormal, dv[k]->position, dv[k]->binormal);
					VectorNormalize(dv[k]->binormal);

					// calculate the normal as cross product N=TxB
#if 0
					CrossProduct(dv[k]->tangent, dv[k]->binormal, dv[k]->normal);
					VectorNormalize(dv[k]->normal);

					// Gram-Schmidt orthogonalization process for B
					// compute the cross product B=NxT to obtain
					// an orthogonal basis
					CrossProduct(dv[k]->normal, dv[k]->tangent, dv[k]->binormal);

					if (DotProduct(dv[k]->normal, faceNormal) < 0)
					{
						VectorInverse(dv[k]->normal);
						//VectorInverse(dv[k]->tangent);
						//VectorInverse(dv[k]->binormal);
					}

#else
					VectorAdd(dv[k]->normal, faceNormal, dv[k]->normal);
#endif
				}
			}

#if 1
			for (j = 0, v = surf->verts; j < surf->numVerts; j++, v++)
			{
				//VectorNormalize(v->tangent);
				//VectorNormalize(v->binormal);
				VectorNormalize(v->normal);
			}
#endif
		}
#endif

#if 0
		// do another extra smoothing for normals to avoid flat shading
		for (j = 0; j < surf->numVerts; j++)
		{
			for (k = 0; k < surf->numVerts; k++)
			{
				if (j == k)
				{
					continue;
				}

				if (VectorCompare(surf->verts[j].position, surf->verts[k].position))
				{
					VectorAdd(surf->verts[j].normal, surf->verts[k].normal, surf->verts[j].normal);
				}
			}

			VectorNormalize(surf->verts[j].normal);
		}
#endif
	}

	// split the surfaces into VBO surfaces by the maximum number of GPU vertex skinning bones
	Com_InitGrowList(&vboSurfaces, 10);

	for (i = 0, surf = md5->surfaces; i < md5->numSurfaces; i++, surf++)
	{
		// sort triangles
		Com_InitGrowList(&sortedTriangles, 1000);

		for (j = 0, tri = surf->triangles; j < surf->numTriangles; j++, tri++)
		{
			skelTriangle_t *sortTri = Com_Allocate(sizeof(*sortTri));

			for (k = 0; k < 3; k++)
			{
				sortTri->indexes[k]  = tri->indexes[k];
				sortTri->vertexes[k] = &surf->verts[tri->indexes[k]];
			}

			sortTri->referenced = qfalse;

			Com_AddToGrowList(&sortedTriangles, sortTri);
		}

		//qsort(sortedTriangles.elements, sortedTriangles.currentElements, sizeof(void *), CompareTrianglesByBoneReferences);

#if 0
		for (j = 0; j < sortedTriangles.currentElements; j++)
		{
			int b[MAX_WEIGHTS * 3];

			skelTriangle_t *sortTri = Com_GrowListElement(&sortedTriangles, j);

			for (k = 0; k < 3; k++)
			{
				v = sortTri->vertexes[k];

				for (l = 0; l < MAX_WEIGHTS; l++)
				{
					b[k * 3 + l] = (l < v->numWeights) ? v->weights[l]->boneIndex : 9999;
				}

				qsort(b, MAX_WEIGHTS * 3, sizeof(int), CompareBoneIndices);
				//Ren_Print("bone indices: %i %i %i %i\n", b[k * 3 + 0], b[k * 3 + 1], b[k * 3 + 2], b[k * 3 + 3]);
			}
		}
#endif

		numRemaining = sortedTriangles.currentElements;

		while (numRemaining)
		{
			numBoneReferences = 0;
			Com_Memset(boneReferences, 0, sizeof(boneReferences));

			Com_InitGrowList(&vboTriangles, 1000);

			for (j = 0; j < sortedTriangles.currentElements; j++)
			{
				skelTriangle_t *sortTri = Com_GrowListElement(&sortedTriangles, j);

				if (sortTri->referenced)
				{
					continue;
				}

				if (AddTriangleToVBOTriangleList(&vboTriangles, sortTri, &numBoneReferences, boneReferences))
				{
					sortTri->referenced = qtrue;
				}
			}

			if (!vboTriangles.currentElements)
			{
				Ren_Warning("R_LoadMD5: could not add triangles to a remaining VBO surfaces for model '%s'\n", modName);
				Com_DestroyGrowList(&vboTriangles);
				break;
			}

			AddSurfaceToVBOSurfacesList(&vboSurfaces, &vboTriangles, md5, surf, i, numBoneReferences, boneReferences);
			numRemaining -= vboTriangles.currentElements;

			Com_DestroyGrowList(&vboTriangles);
		}

		for (j = 0; j < sortedTriangles.currentElements; j++)
		{
			skelTriangle_t *sortTri = Com_GrowListElement(&sortedTriangles, j);

			Com_Dealloc(sortTri);
		}

		Com_DestroyGrowList(&sortedTriangles);
	}

	// move VBO surfaces list to hunk
	md5->numVBOSurfaces = vboSurfaces.currentElements;
	md5->vboSurfaces    = ri.Hunk_Alloc(md5->numVBOSurfaces * sizeof(*md5->vboSurfaces), h_low);

	for (i = 0; i < md5->numVBOSurfaces; i++)
	{
		md5->vboSurfaces[i] = ( srfVBOMD5Mesh_t * ) Com_GrowListElement(&vboSurfaces, i);
	}

	Com_DestroyGrowList(&vboSurfaces);

	return qtrue;
}
예제 #14
0
/*
=================
R_LoadMD5
=================
*/
qboolean R_LoadMD5( model_t *mod, void *buffer, int bufferSize, const char *modName )
{
	int           i, j, k;
	md5Model_t    *md5;
	md5Bone_t     *bone;
	md5Surface_t  *surf;
	md5Triangle_t *tri;
	md5Vertex_t   *v;
	md5Weight_t   *weight;
	int           version;
	shader_t      *sh;
	char          *buf_p;
	char          *token;
	vec3_t        boneOrigin;
	quat_t        boneQuat;
	matrix_t      boneMat;

	buf_p = ( char * ) buffer;

	// skip MD5Version indent string
	COM_ParseExt2( &buf_p, qfalse );

	// check version
	token = COM_ParseExt2( &buf_p, qfalse );
	version = atoi( token );

	if ( version != MD5_VERSION )
	{
		ri.Printf( PRINT_WARNING, "R_LoadMD5: %s has wrong version (%i should be %i)\n", modName, version, MD5_VERSION );
		return qfalse;
	}

	mod->type = MOD_MD5;
	mod->dataSize += sizeof( md5Model_t );
	md5 = mod->model.md5 = ri.Hunk_Alloc( sizeof( md5Model_t ), h_low );

	// skip commandline <arguments string>
	token = COM_ParseExt2( &buf_p, qtrue );
	token = COM_ParseExt2( &buf_p, qtrue );
//  ri.Printf(PRINT_ALL, "%s\n", token);

	// parse numJoints <number>
	token = COM_ParseExt2( &buf_p, qtrue );

	if ( Q_stricmp( token, "numJoints" ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'numJoints' found '%s' in model '%s'\n", token, modName );
		return qfalse;
	}

	token = COM_ParseExt2( &buf_p, qfalse );
	md5->numBones = atoi( token );

	// parse numMeshes <number>
	token = COM_ParseExt2( &buf_p, qtrue );

	if ( Q_stricmp( token, "numMeshes" ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'numMeshes' found '%s' in model '%s'\n", token, modName );
		return qfalse;
	}

	token = COM_ParseExt2( &buf_p, qfalse );
	md5->numSurfaces = atoi( token );
	//ri.Printf(PRINT_ALL, "R_LoadMD5: '%s' has %i surfaces\n", modName, md5->numSurfaces);

	if ( md5->numBones < 1 )
	{
		ri.Printf( PRINT_WARNING, "R_LoadMD5: '%s' has no bones\n", modName );
		return qfalse;
	}

	if ( md5->numBones > MAX_BONES )
	{
		ri.Printf( PRINT_WARNING, "R_LoadMD5: '%s' has more than %i bones (%i)\n", modName, MAX_BONES, md5->numBones );
		return qfalse;
	}

	//ri.Printf(PRINT_ALL, "R_LoadMD5: '%s' has %i bones\n", modName, md5->numBones);

	// parse all the bones
	md5->bones = ri.Hunk_Alloc( sizeof( *bone ) * md5->numBones, h_low );

	// parse joints {
	token = COM_ParseExt2( &buf_p, qtrue );

	if ( Q_stricmp( token, "joints" ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'joints' found '%s' in model '%s'\n", token, modName );
		return qfalse;
	}

	token = COM_ParseExt2( &buf_p, qfalse );

	if ( Q_stricmp( token, "{" ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '{' found '%s' in model '%s'\n", token, modName );
		return qfalse;
	}

	for ( i = 0, bone = md5->bones; i < md5->numBones; i++, bone++ )
	{
		token = COM_ParseExt2( &buf_p, qtrue );
		Q_strncpyz( bone->name, token, sizeof( bone->name ) );

		//ri.Printf(PRINT_ALL, "R_LoadMD5: '%s' has bone '%s'\n", modName, bone->name);

		token = COM_ParseExt2( &buf_p, qfalse );
		bone->parentIndex = atoi( token );

		//ri.Printf(PRINT_ALL, "R_LoadMD5: '%s' has bone '%s' with parent index %i\n", modName, bone->name, bone->parentIndex);

		if ( bone->parentIndex >= md5->numBones )
		{
			ri.Error( ERR_DROP, "R_LoadMD5: '%s' has bone '%s' with bad parent index %i while numBones is %i\n", modName,
			          bone->name, bone->parentIndex, md5->numBones );
		}

		// skip (
		token = COM_ParseExt2( &buf_p, qfalse );

		if ( Q_stricmp( token, "(" ) )
		{
			ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName );
			return qfalse;
		}

		for ( j = 0; j < 3; j++ )
		{
			token = COM_ParseExt2( &buf_p, qfalse );
			boneOrigin[ j ] = atof( token );
		}

		// skip )
		token = COM_ParseExt2( &buf_p, qfalse );

		if ( Q_stricmp( token, ")" ) )
		{
			ri.Printf( PRINT_WARNING, "R_LoadMD5: expected ')' found '%s' in model '%s'\n", token, modName );
			return qfalse;
		}

		// skip (
		token = COM_ParseExt2( &buf_p, qfalse );

		if ( Q_stricmp( token, "(" ) )
		{
			ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName );
			return qfalse;
		}

		for ( j = 0; j < 3; j++ )
		{
			token = COM_ParseExt2( &buf_p, qfalse );
			boneQuat[ j ] = atof( token );
		}

		QuatCalcW( boneQuat );
		MatrixFromQuat( boneMat, boneQuat );

		VectorCopy( boneOrigin, bone->origin );
		QuatCopy( boneQuat, bone->rotation );

		MatrixSetupTransformFromQuat( bone->inverseTransform, boneQuat, boneOrigin );
		MatrixInverse( bone->inverseTransform );

		// skip )
		token = COM_ParseExt2( &buf_p, qfalse );

		if ( Q_stricmp( token, ")" ) )
		{
			ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName );
			return qfalse;
		}
	}

	// parse }
	token = COM_ParseExt2( &buf_p, qtrue );

	if ( Q_stricmp( token, "}" ) )
	{
		ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '}' found '%s' in model '%s'\n", token, modName );
		return qfalse;
	}

	// parse all the surfaces
	if ( md5->numSurfaces < 1 )
	{
		ri.Printf( PRINT_WARNING, "R_LoadMD5: '%s' has no surfaces\n", modName );
		return qfalse;
	}

	//ri.Printf(PRINT_ALL, "R_LoadMD5: '%s' has %i surfaces\n", modName, md5->numSurfaces);

	md5->surfaces = ri.Hunk_Alloc( sizeof( *surf ) * md5->numSurfaces, h_low );

	for ( i = 0, surf = md5->surfaces; i < md5->numSurfaces; i++, surf++ )
	{
		// parse mesh {
		token = COM_ParseExt2( &buf_p, qtrue );

		if ( Q_stricmp( token, "mesh" ) )
		{
			ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'mesh' found '%s' in model '%s'\n", token, modName );
			return qfalse;
		}

		token = COM_ParseExt2( &buf_p, qfalse );

		if ( Q_stricmp( token, "{" ) )
		{
			ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '{' found '%s' in model '%s'\n", token, modName );
			return qfalse;
		}

		// change to surface identifier
		surf->surfaceType = SF_MD5;

		// give pointer to model for Tess_SurfaceMD5
		surf->model = md5;

		// parse shader <name>
		token = COM_ParseExt2( &buf_p, qtrue );

		if ( Q_stricmp( token, "shader" ) )
		{
			Q_strncpyz( surf->shader, "<default>", sizeof( surf->shader ) );
			surf->shaderIndex = 0;
		}
		else
		{
			token = COM_ParseExt2( &buf_p, qfalse );
			Q_strncpyz( surf->shader, token, sizeof( surf->shader ) );

			//ri.Printf(PRINT_ALL, "R_LoadMD5: '%s' uses shader '%s'\n", modName, surf->shader);

			// FIXME .md5mesh meshes don't have surface names
			// lowercase the surface name so skin compares are faster
			//Q_strlwr(surf->name);
			//ri.Printf(PRINT_ALL, "R_LoadMD5: '%s' has surface '%s'\n", modName, surf->name);

			// register the shaders
			sh = R_FindShader( surf->shader, LIGHTMAP_NONE, qtrue );

			if ( sh->defaultShader )
			{
				surf->shaderIndex = 0;
			}
			else
			{
				surf->shaderIndex = sh->index;
			}

			token = COM_ParseExt2( &buf_p, qtrue );
		}

		// parse numVerts <number>
		if ( Q_stricmp( token, "numVerts" ) )
		{
			ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'numVerts' found '%s' in model '%s'\n", token, modName );
			return qfalse;
		}

		token = COM_ParseExt2( &buf_p, qfalse );
		surf->numVerts = atoi( token );

		if ( surf->numVerts > SHADER_MAX_VERTEXES )
		{
			ri.Error( ERR_DROP, "R_LoadMD5: '%s' has more than %i verts on a surface (%i)",
			          modName, SHADER_MAX_VERTEXES, surf->numVerts );
		}

		surf->verts = ri.Hunk_Alloc( sizeof( *v ) * surf->numVerts, h_low );

		for ( j = 0, v = surf->verts; j < surf->numVerts; j++, v++ )
		{
			// skip vert <number>
			token = COM_ParseExt2( &buf_p, qtrue );

			if ( Q_stricmp( token, "vert" ) )
			{
				ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'vert' found '%s' in model '%s'\n", token, modName );
				return qfalse;
			}

			COM_ParseExt2( &buf_p, qfalse );

			// skip (
			token = COM_ParseExt2( &buf_p, qfalse );

			if ( Q_stricmp( token, "(" ) )
			{
				ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName );
				return qfalse;
			}

			for ( k = 0; k < 2; k++ )
			{
				token = COM_ParseExt2( &buf_p, qfalse );
				v->texCoords[ k ] = atof( token );
			}

			// skip )
			token = COM_ParseExt2( &buf_p, qfalse );

			if ( Q_stricmp( token, ")" ) )
			{
				ri.Printf( PRINT_WARNING, "R_LoadMD5: expected ')' found '%s' in model '%s'\n", token, modName );
				return qfalse;
			}

			token = COM_ParseExt2( &buf_p, qfalse );
			v->firstWeight = atoi( token );

			token = COM_ParseExt2( &buf_p, qfalse );
			v->numWeights = atoi( token );

			if ( v->numWeights > MAX_WEIGHTS )
			{
				ri.Error( ERR_DROP, "R_LoadMD5: vertex %i requires more than %i weights on surface (%i) in model '%s'",
				          j, MAX_WEIGHTS, i, modName );
			}
		}

		// parse numTris <number>
		token = COM_ParseExt2( &buf_p, qtrue );

		if ( Q_stricmp( token, "numTris" ) )
		{
			ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'numTris' found '%s' in model '%s'\n", token, modName );
			return qfalse;
		}

		token = COM_ParseExt2( &buf_p, qfalse );
		surf->numTriangles = atoi( token );

		if ( surf->numTriangles > SHADER_MAX_TRIANGLES )
		{
			ri.Error( ERR_DROP, "R_LoadMD5: '%s' has more than %i triangles on a surface (%i)",
			          modName, SHADER_MAX_TRIANGLES, surf->numTriangles );
		}

		surf->triangles = ri.Hunk_Alloc( sizeof( *tri ) * surf->numTriangles, h_low );

		for ( j = 0, tri = surf->triangles; j < surf->numTriangles; j++, tri++ )
		{
			// skip tri <number>
			token = COM_ParseExt2( &buf_p, qtrue );

			if ( Q_stricmp( token, "tri" ) )
			{
				ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'tri' found '%s' in model '%s'\n", token, modName );
				return qfalse;
			}

			COM_ParseExt2( &buf_p, qfalse );

			for ( k = 0; k < 3; k++ )
			{
				token = COM_ParseExt2( &buf_p, qfalse );
				tri->indexes[ k ] = atoi( token );
			}
		}

		// parse numWeights <number>
		token = COM_ParseExt2( &buf_p, qtrue );

		if ( Q_stricmp( token, "numWeights" ) )
		{
			ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'numWeights' found '%s' in model '%s'\n", token, modName );
			return qfalse;
		}

		token = COM_ParseExt2( &buf_p, qfalse );
		surf->numWeights = atoi( token );

		surf->weights = ri.Hunk_Alloc( sizeof( *weight ) * surf->numWeights, h_low );

		for ( j = 0, weight = surf->weights; j < surf->numWeights; j++, weight++ )
		{
			// skip weight <number>
			token = COM_ParseExt2( &buf_p, qtrue );

			if ( Q_stricmp( token, "weight" ) )
			{
				ri.Printf( PRINT_WARNING, "R_LoadMD5: expected 'weight' found '%s' in model '%s'\n", token, modName );
				return qfalse;
			}

			COM_ParseExt2( &buf_p, qfalse );

			token = COM_ParseExt2( &buf_p, qfalse );
			weight->boneIndex = atoi( token );

			token = COM_ParseExt2( &buf_p, qfalse );
			weight->boneWeight = atof( token );

			// skip (
			token = COM_ParseExt2( &buf_p, qfalse );

			if ( Q_stricmp( token, "(" ) )
			{
				ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '(' found '%s' in model '%s'\n", token, modName );
				return qfalse;
			}

			for ( k = 0; k < 3; k++ )
			{
				token = COM_ParseExt2( &buf_p, qfalse );
				weight->offset[ k ] = atof( token );
			}

			// skip )
			token = COM_ParseExt2( &buf_p, qfalse );

			if ( Q_stricmp( token, ")" ) )
			{
				ri.Printf( PRINT_WARNING, "R_LoadMD5: expected ')' found '%s' in model '%s'\n", token, modName );
				return qfalse;
			}
		}

		// parse }
		token = COM_ParseExt2( &buf_p, qtrue );

		if ( Q_stricmp( token, "}" ) )
		{
			ri.Printf( PRINT_WARNING, "R_LoadMD5: expected '}' found '%s' in model '%s'\n", token, modName );
			return qfalse;
		}

		// loop trough all vertices and set up the vertex weights
		for ( j = 0, v = surf->verts; j < surf->numVerts; j++, v++ )
		{
			v->weights = ri.Hunk_Alloc( sizeof( *v->weights ) * v->numWeights, h_low );

			for ( k = 0; k < v->numWeights; k++ )
			{
				v->weights[ k ] = surf->weights + ( v->firstWeight + k );
			}
		}
	}

	// loading is done now calculate the bounding box and tangent spaces
	ClearBounds( md5->bounds[ 0 ], md5->bounds[ 1 ] );

	for ( i = 0, surf = md5->surfaces; i < md5->numSurfaces; i++, surf++ )
	{
		for ( j = 0, v = surf->verts; j < surf->numVerts; j++, v++ )
		{
			vec3_t      tmpVert;
			md5Weight_t *w;

			VectorClear( tmpVert );

			for ( k = 0, w = v->weights[ 0 ]; k < v->numWeights; k++, w++ )
			{
				vec3_t offsetVec;

				bone = &md5->bones[ w->boneIndex ];

				QuatTransformVector( bone->rotation, w->offset, offsetVec );
				VectorAdd( bone->origin, offsetVec, offsetVec );

				VectorMA( tmpVert, w->boneWeight, offsetVec, tmpVert );
			}

			VectorCopy( tmpVert, v->position );
			AddPointToBounds( tmpVert, md5->bounds[ 0 ], md5->bounds[ 1 ] );
		}

		// calc normals
		{
			const float *v0, *v1, *v2;
			const float *t0, *t1, *t2;
			vec3_t      normal;

			for ( j = 0, v = surf->verts; j < surf->numVerts; j++, v++ )
			{
				VectorClear( v->tangent );
				VectorClear( v->binormal );
				VectorClear( v->normal );
			}

			for ( j = 0, tri = surf->triangles; j < surf->numTriangles; j++, tri++ )
			{
				v0 = surf->verts[ tri->indexes[ 0 ] ].position;
				v1 = surf->verts[ tri->indexes[ 1 ] ].position;
				v2 = surf->verts[ tri->indexes[ 2 ] ].position;

				t0 = surf->verts[ tri->indexes[ 0 ] ].texCoords;
				t1 = surf->verts[ tri->indexes[ 1 ] ].texCoords;
				t2 = surf->verts[ tri->indexes[ 2 ] ].texCoords;

				R_CalcNormalForTriangle( normal, v0, v1, v2 );

				for ( k = 0; k < 3; k++ )
				{
					float *v;

					v = surf->verts[ tri->indexes[ k ] ].normal;
					VectorAdd( v, normal, v );
				}
			}

			for ( j = 0, v = surf->verts; j < surf->numVerts; j++, v++ )
			{
				VectorNormalize( v->normal );
			}
		}

#if 0

		// do another extra smoothing for normals to avoid flat shading
		for ( j = 0; j < surf->numVerts; j++ )
		{
			for ( k = 0; k < surf->numVerts; k++ )
			{
				if ( j == k )
				{
					continue;
				}

				if ( VectorCompare( surf->verts[ j ].position, surf->verts[ k ].position ) )
				{
					VectorAdd( surf->verts[ j ].normal, surf->verts[ k ].normal, surf->verts[ j ].normal );
				}
			}

			VectorNormalize( surf->verts[ j ].normal );
		}

#endif
	}

	return qtrue;
}