HomeNewsFeaturesLicensingDownloadsScreenshotsFAQRoadmap Contact Us
Search:
9 Online

Community

Discussion Topics Recent Postings User Contributions General Articles Example Documentation Credits

Licensed Developer

Programmer's Manual Artists Manual Tutorials and Articles

Programming: Game Code

Game Code Overview Server Side Game Code Client Side Game Code

Programming: System

Alphabetical Function List Renderer File System Collision & Ray Casting Low Level Audio Game Audio The Console Console Variable List Multiplayer Localisation Maths Library Memory Manager Model File Formats Texture Formats

Art: Overviews

Specifications Shaders Particle Systems Lens Flares Cipher console Cipher file types Tutorials Reference

Art: Tools

Shader Designer Particle Designer 3dsmax tools Model Conversion Font Generator

Cipher Engine
Game Development Search Engine
GameDev.net
You are not signed in - [sign in] [register]

ANIMTYPE_SELECTIVE_BLEND to replace animation in specific area

Overview

Author: LostSoul
Date Submitted: 2004-07-29 17:13:06

Source Code

/*
==========================
r_GenerateBlendedPose

Creates a single pose from a blended skeletal animation
==========================
*/
int r_GenerateBlendedPose(skinmodel_t* skinmodel, iteminfo_t* item, anim_t* anim, int numblends)
{
	float lerp;
	int from, to, numbones;
	int i, n, s, b0, b1, bRef;
	skeleton_t* skel;
	bone_t bone, blendedbone, selectiveBlendBone;
	float totalweight;
	int *selectiveBlends, numOfSelectiveBlends = 0, blendnum;


	// If there is no skeletal animation data, do nothing
	skel = skinmodel->skeleton;
	if (!skel)
		return GEOM_STATIC;

	// if there are no bones, treat as a static model
	if (skel->numbones==0)
		return GEOM_STATIC;

	// Check one of the blends is valid
	if (!r_ValidateAnimBlends(anim, numblends, skel))
		return GEOM_STATIC;

	// Allocate space in the pose list for the final blended pose of our model
	numbones = skel->numbonesperframe;
	r_GrowPoseList(numbones);

	// Change item->from to point at the pose, as this is how the skinner finds out
	item->from = r_model.pose_count;
	item->to = 0;
	item->lerp = 0;

	// allocate enough space to hold indices of selective blends (max = numblends)
	selectiveBlends = (int *) ri.com_Malloc(sizeof(int) * numblends);


	// walk over all the bones, blend and convert them to mat4's
	for (i=0; i<numbones; i++)
	{
		// Starting a new bone, so reset the weighting
		totalweight = 0;

		// for all the blends being applied, add them up
		for (n=0; n<numblends; n++)
		{

			// check for selective blend and save blend num for later (only on first bone pass)
			if (i == 0)
			{
				if (anim[n].type==ANIMTYPE_SELECTIVE_BLEND)
				{
					selectiveBlends[numOfSelectiveBlends] = n;
					numOfSelectiveBlends++;
				}
			}

			
			// skip blends of the wrong type (we are only processing ANIMTYPE_BLEND here
			if (anim[n].type!=ANIMTYPE_BLEND )
				continue;

			// skip items that have a weight of zero
			if (anim[n].blend.weight <= 0)
				continue;

			// I guess this is a good one, so work out the bone for this part
			from = anim[n].blend.from;
			to = anim[n].blend.to;
			lerp = anim[n].blend.lerp;

			// Find the "from" and "to" versions of the bone
			b0 = skel->boneindex[numbones*from + i];
			b1 = skel->boneindex[numbones*to + i];
			

			// blend the two bones and build a matrix for it
			r_LerpBone(&skel->bonepool[b0], &skel->bonepool[b1], lerp, &bone);

			// mix this bone into the final result
			if (totalweight==0)
			{
				// This is the first one, so just copy it over
				blendedbone = bone;
				totalweight = anim[n].blend.weight;
			}
			else
			{
				// blend this one using the weight provided
				totalweight += anim[n].blend.weight;
				lerp = anim[n].blend.weight / totalweight;
				r_LerpBone(&blendedbone, &bone, lerp, &blendedbone);
			}
		}

		// if there was no blend parts, copy the reference frame over
		if (totalweight == 0)
		{
			b0 = skel->boneindex[i];
			blendedbone = skel->bonepool[b0];
		}


		// reset the weighting
		totalweight = 0;

		// now handle all Selective Blends
		for (s=0; s<numOfSelectiveBlends; s++)
		{
			blendnum = selectiveBlends[s];

			// work out the bone for this part
			from = anim[blendnum].blend.from;
			to = anim[blendnum].blend.to;
			lerp = anim[blendnum].blend.lerp;

			// Find the "from" and "to" versions of the bone
			b0 = skel->boneindex[numbones*from + i];
			b1 = skel->boneindex[numbones*to + i];
			

			// Find reference bone index
			bRef = skel->boneindex[i];

			// compare positions of from, to and ref bone(no from yet). Skip if all the same
			if (skel->bonepool[b1].translate[0] == skel->bonepool[bRef].translate[0] &&
				skel->bonepool[b1].translate[1] == skel->bonepool[bRef].translate[1] &&
				skel->bonepool[b1].translate[2] == skel->bonepool[bRef].translate[2])
			{
				continue;
			}

			// blend the two bones and build a matrix for it
			r_LerpBone(&skel->bonepool[b0], &skel->bonepool[b1], lerp, &bone);

			// mix this bone into the final result
			if (totalweight==0)
			{
				// This is the first one, so just copy it over
				selectiveBlendBone = bone;
				totalweight = anim[blendnum].blend.weight;
			}
			else
			{
				// blend this one using the weight provided
				totalweight += anim[blendnum].blend.weight;
				lerp = anim[blendnum].blend.weight / totalweight;
				r_LerpBone(&selectiveBlendBone, &bone, lerp, &selectiveBlendBone);
			}
		}
		

		// If there was no selective blend parts, just use blendedbone for pose
		if (totalweight==0)
		{
			r_BoneToMat4(&blendedbone, r_model.pose_list[r_model.pose_count++]);
		}
		else
		{
			if (totalweight > 1)
				totalweight = 1;

			// combine the selective and blended bones using the total selective weighting
			lerp = totalweight;
			r_LerpBone(&blendedbone, &selectiveBlendBone, lerp, &selectiveBlendBone);
			

			// Convert the final bone to a matrix
			r_BoneToMat4(&selectiveBlendBone, r_model.pose_list[r_model.pose_count++]);
		}
	}	


	// Now apply all the bone overrides
	for (n=0; n<numblends; n++)
	{
		// skip blends of the wrong type
		if (anim[n].type!=ANIMTYPE_FORCE)
			continue;

		// This is a good one, so replace the blended bone with the one provided
		b0 = anim[n].force.boneid;
		mat4_Copy(anim[n].force.transform, r_model.pose_list[b0]);
	}

	
	// Free up selectiveBlends array
	ri.com_Free(selectiveBlends);

	// Use skinning please
	return GEOM_SKINFRAME;
}


Description

This code will allow you to blend skeletal animation in a specific area of the model. It comes in handy when you have a running cycle and you want the model to move an arm while still running. You can add as many selective animations as you want.

[bold]File Changes:[/bold]
In file r_types.c?
extend the "animation blending constants" by adding ANIMTYPE_SELECTIVE_BLEND to the enum.

Line 69 should look like?

enum
{
ANIMTYPE_NONE,
ANIMTYPE_BLEND,
ANIMTYPE_SELECTIVE_BLEND,
ANIMTYPE_FORCE
};


In r_model.c?
change line 951 in the r_ValidateAnimBlends function
from?
 if (anim[i].type==ANIMTYPE_BLEND)

to?
 if (anim[i].type==ANIMTYPE_BLEND || anim[i].type==ANIMTYPE_SELECTIVE_BLEND)


and replace the r_GenerateBlendedPose at line 992 with the code above.

That's all you need to do.


[bold]How it works:[/bold]
When a selective blend is added to the scene, the renderer will check each bone against the reference frame of the skeleton. If the animation bone is in the same position as the reference bone, it doesn't get added to the final calculation. For this to work, when your building your animations in MAX, you must make sure every bone in the selective animation that's not used, is in the same position as the reference frame. The easiest way to do this is to copy your reference frame to the first frame of your animation, then animate it.

[bold]How to control the blend:[/bold]
All regular blends are calculated first, so there weights will still affect the final pose. After that pose is calculated, all selective blends are calculated with their weights. Then the total weight of all selective blends is used to combine them with the regular blends. This means that if you have 2 regular blends, they will blend like they did before. Then if you add a selective blend, it will replace the regular ones by it's weight factor. So a selective blend with a weight of 1 will cancel out all other blends where the bone is moving. I know it sound confusing, but play around with it and you'll understand. The main thing to remember is that regular blend weights are separate from selective blend weights.

[bold]Example:[/bold]
If a model is running and you want him to wave while still running.

The running animation will be a regular blend of type ANIMTYPE_BLEND with a weight of 1. The wave animation is the selective blend of type ANIMTYPE_SELECTIVE_BLEND whose weight starts at 0 and goes to 1 over a second or two (this is so the transition is smooth), then fades back to 0 when the animation is done. If you handle the weight properly, you'll end up with a nice smooth animation.

I haven't fully tested this code out with more then 2 blends going on. Let me know if something isn't working.

[Recent Contributions] [Recent Source Code]

User Contributed Comments

rikh 29th July, 2004 18:27
Awesome!


JTilo 29th July, 2004 19:08
LostSoul, Very nice addition!


Serapth 29th July, 2004 19:10
Very very cool, thanks.


LostSoul 29th July, 2004 19:19
Thanks Guys,

I've also almost finished a maya exporter that handles static, vertex and skeletal models. I've been using this exporter for testing this new blending technique, so someone who uses MAX will need to give it a wirl. Once I add support for multiple materials and meshes, I'll post the exporter. It might be a week or two.


seryu 29th July, 2004 19:36
great code, and maya exporter sounds a lot cool!

It will be a sourcecode or only binary release?
its a mdl/skl or ase exporter?
Edited 29th July, 2004 19:37


LostSoul 29th July, 2004 20:32
Sorry guys, but it looks like I was alittle premature in releasing this code. First of all I was only checking positions of bones, so the lines that say...
			// compare positions of from, to and ref bone(no from yet). Skip if all the same
if (skel->bonepool[b1].translate[0] == skel->bonepool[bRef].translate[0] &&
skel->bonepool[b1].translate[1] == skel->bonepool[bRef].translate[1] &&
skel->bonepool[b1].translate[2] == skel->bonepool[bRef].translate[2])
{


Should be a very messy...
			// compare positions of from, to and ref bone(no from yet). Skip if all the same
if (skel->bonepool[b1].translate[0] == skel->bonepool[bRef].translate[0] &&
skel->bonepool[b1].translate[1] == skel->bonepool[bRef].translate[1] &&
skel->bonepool[b1].translate[2] == skel->bonepool[bRef].translate[2] &&
skel->bonepool[b1].rotate[0] == skel->bonepool[bRef].rotate[0] &&
skel->bonepool[b1].rotate[1] == skel->bonepool[bRef].rotate[1] &&
skel->bonepool[b1].rotate[2] == skel->bonepool[bRef].rotate[2] &&
skel->bonepool[b1].scale[0] == skel->bonepool[bRef].scale[0] &&
skel->bonepool[b1].scale[1] == skel->bonepool[bRef].scale[1] &&
skel->bonepool[b1].scale[2] == skel->bonepool[bRef].scale[2] )
{


That will handle all transforms that a bone can go through.

Now the only problem left (and I could use your help) is best explained by an example. If the first regular blended animation is a twist at the waist, the shoulder will rotate with this animation as expected. Now if I apply a selective blend wave animation to the arm, the ball joint of the arm stays at its static place and doesn't move with the waist animation. What this ends up looking like is a model with a stretched out shoulder. What I need to do is hook this joint back to the shoulder. I was thinking of using the parents of bones to check for this and move the bone manually to the right place. What do you think? Any help would be great.

PS The exporter will generate ase, skl and skin files. I'll give everyone the code too.


Mattxl 30th July, 2004 00:58
Great job! This saves me a lot of time and hassle. Hey Rik, how about putting this into the next release of Cipher? It'd be extremely useful.

Matt


ebrownlee 30th July, 2004 01:56
Nice work, indeed. I can't wait to test it out first hand.

- Ernest


LostSoul 30th July, 2004 20:47
Like I said, this code still needs work. It would be great to get some help. Read two posts up and let me know if you have a solution.


swazeeJim 5th September, 2004 15:25
Hmm, I dont have ANIMTYPE_FORCE in my engine, im sure its the latest download (2791). Where do I get the version with ANIMTYPE_FORCE?


ruzark 10th March, 2005 21:18
Is this contrib already in Cipher?


crusherfred 6th September, 2005 15:38
Yo LostSoul. Did you ever solve the waist animation then wave selective blend on top of the new waist position? I believe the solution may lie in comparing to the bone parents, figuring out the quaternion difference between where the parent bone is in the animation and where it is after the blends have been applied, then moving to bone to where it would be via the quaternion (so yes, very similar to what you propose above).


crusherfred 7th September, 2005 07:02
I'd also like to add that ciphertool already does the bone comparison for you. The bone pool that gets exported to the .mdl is a pool of unique bones, with the reference frame bones starting off the pool. This explains why the number of bones in the bone pool can be smaller than numbonesperframe * numframes. Bones that don't move are identical to the bones in the reference frame, so to check if a bone is a reference bone, all you need to do is check if your bRef < numbones.




Register and Sign In to discuss this article