Wednesday, April 22, 2009

Air Energy vs. Ocean Energy

With all this talk about climate change and such, I got curious about how much total energy is stored in the heat in the atmosphere vs. the oceans. It seems that we talk about climate change, but because we are humans, we are highly biased toward the air being the climate and somewhat ignore the oceans. If we were all dolphins, we would see the oceans and ignore the air.

SO lets get some numbers, all gleaned from Google searches, mostly with hits on Wikipedia...

Weight of the atmosphere: 5.1x10^18 kg.
Weight of the oceans: 1.5x10^21 kg.

So the water out weighs the atmosphere by 294 times.

But, water holds more heat per kg than air...

The specific heat of water: 2.015 kJ/kg-K.

This means that it takes 2015 Joules of energy to raise one killogram of water by one degree Kelvin or Centigrade.

the specific heat of air: 1.0 kJ/kg-K

So water holds about twice the heat per pound than air, so

The total heat needed to change the temperature of the ocean vs. the air is
588 times greater
.

My point?

the global temperature that we all measure and quote as 'global warming' is irrelevant.

The oceans are the real climate and fluctuations in the atmosphere are trivial.

TF

Saturday, April 11, 2009

XNA, XACT, and Sound

Our project had a lot of difficulty in getting sound to work in XNA and in communication between the programmers and the sound artists.
This tutorial is intended as instructions for the audio folks at Sandswept Studios, but feel free to use it too.

Key Concepts

XACT lets the audio engineers tweak all sorts of sound parameters without the programmers having to worry about it.
The programmer just plays the cue.
The audio engineer can tweak volume, sound fall off distance, etc.

First and most important, you must have the correct version of XACT to work with XNA!
This means that you must use the XACT that comes with XNA Studio.
On my machine XACT is at C:\Program Files\Microsoft XNA\XNA Game Studio\v3.0\Tools\Xact.exe

When you are editing sounds in XACT it is nice to be able to hear them, so you have to run AudConsole.exe
that is in the same directory that Xact.exe is in.
You would think that XACT could just play sounds, but no, you have to run AudConsole.exe

Create An XACT Project

So lets get started. Run XACT and create a new project.
File ==> New project
I put the new project at C:\Users\rkeene\Documents\AudioTest\ and called it AudioTest

XACT Uses wave banks which are the 'raw sound' usualy from a .wav or .aif file.
We will use the regular old windows chimes sound. So create a wave bank
Wave Banks ==> New Wave Bank
You should get an empty wave bank window.
Now go find the .wav file we want. On my Vista machine it is in
C:\WINDOWS\Media\chimes.wav
So drag-n-drop that file onto the WaveBank area.

Now we want a sound bank, which ties a raw wave bank entry to all sorts of settings like volume and pitch.
Sound Banks ==> New Sound Bank
Then drag and drop the chimes.wav wave bank entry to the upper part of the Sound Banks window.
Sounds let you add event like volume changes, pitch changes, etc. creating nifty effects.

We also need an actual cue.
This is the name the programmer will use to find the cue and play it in the C# code in XNA.
So, drag-n-drop the sound to the lower part of the cues area.

Now we have a wave bank, a sound bank, and a project. So click on the save icon to save the project.

Sound Fade With Distance

In most games it is nifty to have sounds fade as your view point gets furthur away from the sound source.
Like in DETOUR the factory makes machine noises and we want them to fade out as the player's viewpoitn gets furthur from the factory.
But there are 'big' sound like nukes going off, and 'small' sounds like a dog barking. Big sounds fade out less with distance, and small sounds fade quickly.
In XACT there are RPC Presets that let you setup this fade out.

You need to determine what units distance is in in your game. Is it meters? Inches? Pixels?
On the DETOUR board tiles are 254x254 game units. These are also called World Coordinates.
So while the view point may be on tile 12,5 the world coordinates will be 12*254, 200, 5*254
which is X,Y,Z coordinates. The board is in the XZ plane and Y is 'up'.
Thus for our factory sound we want to be at full volume if you are looking at the factory and fade out
to silent in about 2000 units of distance.

First lets look at Variables. On the left side there is Variables ==> Cue Instance ==> Distance
This is the variable that we will relate to volume. Since our largest board is about 40x40 and thus
10,000 world units max, we want to limit distance ranges to 0 to 10000.
So click on Distance and down below set the range to 0 and 10000.

Next drag and drop your cue on to the RPC Presets item on the left of the screen.
This attaches the cue to the Presets.
Click on RPC Presets and a window comes up with a black window and lines on it.
The lines are (or will be) the sound level related to distance.

So in the RPC Presets window you first select the Variable whic is Distance.
Then select the Object whic is Sound, and then the Parameter which is Volume.
Thus "The distance effects sounds by changing colume."

Now you can drage the line around and get whatever sound vs. distance function you like.
You can double click on the line to make a new drag point. And Ctrl-Z undoes mistakes.

Testing 1 2 3 Testing

Make sure AudConsole is running, and up at the center top of the Xact tools there is a play button.
Click on the Cue, and then click play. Do you hear it?
Also, in the RPC Presets tool you can set the distance, click on the line, and then click the play button
in the RPC window and hear the sounds volume at a given distance.

Exporting

Next we need to export from XACT for XNA.
At the top of the XACT windows is a View menu and the first two items are View XBox36 and View Windows.
This is how you switch between compiling and exporting for the XBox and the PC.

You want to be able to tell the XBox vs. Windows files appart so click on the Wave Bank in the left tree,
and on the lower left there should be fields for
XBox 360 Build Path and Windows Build Path.

We use the convention that the file names end with _w or _x. So change these to be differnet.
Do the same for the sound banks.
Do the same for the top most level project too.

In my test project the top level is AudioTest
so the build path is AudioTest_w.xgs and SudioTest_x.xgs
The sound banks are SB1_w.xsb and SB1_x.xsb
and wave banks are WB1_w.xwb and WB1_x.xwb

Now you build, so make sure View is on View Windows Properties, and click the Builds The Current Project
icon in the top center of the XACT windows.
It will ask about the report you might want. I check Concise and enter rpt_w.txt

The reason for the report is that as your game gets bigger and has zillions of cues, the report helps you keep thing straight.
You can also give the report to the programmer so they know what the cue names are.
This is where all those 'notes' fields come in handy.
It is wise to add a note entry to every cue describing what it is intended for in the game.

Phew! We have exported the windows version.

You can set the View to View XBox Properties and export again to get the XBox version of the files.
Windows version files will not work on the XBox. Period.

Note: sound effects (sfx) are usually loaded into memory in advance so there is no delay to play them.
Music is HUGE so it is streamed from disk to the audio hardware on-the-fly with buffers.
If you click on the Wave Bank, the properties below will include Streaming or InMemory.
Sound effect should be InMemory, and Music should be Streaming.


C# and XNA and Sounds

Ok, now you have all the sounds build for XNA.

Now we move to the XNA C# world.
In XNA you have to load the project (.xgs) the sound bank (.xsb) and the wave bank (.xwb)
Then you find the cue, give it 3D position (optional), and play it.
If your sound person did things correctly, then that is all.

First we need to get the files into the XNA project.
So copy the three files into the Content area. (Ok, get organized and use a subdirectory, etc.)
Now in the Content part of the Solution Explorer, Add ==> Existing Item
You will need to set the pop up file chooser to All Files (*.*). It initially filters for non-sound files.
Select the three files and hit Ok.

They are now in the project, but you need to tell XNA and Visual Studio how to use them.
Select all three under the Content area and in the properties area set...
Build Action: Content
Content Importer: XACT Project - XNA FrameWork
Content ProcessorXACT Project - XNA FrameWork
Copy To Output Directory: Copy if Newer


Ok, now build the project to see if everything is Ok.

Now we need to load the sounds into our game so
add three new class instance level variables to your Game.cs file

public AudioEngine TheAudioEngineTest;
public WaveBank TheWaveBankTest;
public SoundBank TheSoundBankTest;

And in the LoadContent method put something like...

TheAudioEngineTest = new AudioEngine("Content\\AudioTest_w.xgs");
TheWaveBankTest = new WaveBank(TheAudioEngineTest, "Content\\WB1_w.xwb");
TheSoundBankTest = new SoundBank(TheAudioEngineTest, "Content\\SB1_w.xsb");
TheAudioEngineTest.Update();

The Update there is to 'fail fast' if there is a problem, and is not required.

Note: If you play a cue and never call Update on the audio engine, you not hear anything.
Note: The above is for in-memory sounds like sound effects. Music is streamed and the code might
then look like...

TheAudioEngine = new AudioEngine("Content\\music_w.xgs");
TheWaveBank = new WaveBank(TheAudioEngine, "Content\\music_w_wb.xwb", 0, 4);
TheSoundBank = new SoundBank(TheAudioEngine, "Content\\music_w_sb.xsb");
TheAudioEngine.Update();
Note the 0,4 on the end of the WaveBank entry. This is critical.It is the offset and packet size
and effects how smoothly the music plays back.

Oops - We need to differentiate between XBOX and not XBOX code so we need a
#if XBOX
// Load the XBox versions of the files.
#else
// Load the Windows versions of the files.
#endif
around our loads where the _w becomes _x

Now lets play our sound.
The simplest way is...

TheSoundBank.PlayCue("mycue");

Also in the main Update in your game you need to Update TheSoundEngine

You can also do...

Cue c = TheSoundBank.GetCue("mycue");
c.Play();

But of course, we want possitional sound so we do...
Object o = Gm.TheAudioEngineTest.RendererDetails;
AudioListener aListen = new AudioListener();
aListen.Position = FocusTile.LocationWorld;
AudioEmitter aEmit = new AudioEmitter();
aEmit.Position = FactoryTile.LocationWorld;
Cue c = Gm.TheSoundBankTest.GetCue("TestCue1");
float d = (aEmit.Position - aListen.Position).Length();
c.SetVariable("Distance", d);
c.Apply3D(aListen, aEmit);
c.Play();

Ok, the FocusTile.LocationWorld gives me the X,Y,Z coordinates of the tile in world units.
The FactoryTile is where my factory is.
The d is the distance between the two.
We have to set the Distance variable on the cue, and the Apply3D does the stereo
left-right speaker shift.
(You left and right speakers are in the correct place on your desk top?
The one with the volume knob is always the right speaker.)

Wow, that is how sound is done.

XBox Note: When you make the XNA project for the XBox you must remove the wave bank, project, and sound bank files in Content
and re-add the XBox versions.

Tips and Tricks

Randomize Cue Sound Choice
You can make a single cue play several different sound randomly. The programmer does not need to even know which one will play.
Cues have a weight or probability. You can give each sound in the cue a different weight and that is how often they will get picked.
This is cool if you have a repetitive sound, like gun fire, and you want to randomly choose between several different gun sounds to make it not-so-boring.

C# Code Structure
You can have several Audio Engines at the same time. We break out one music engine, and 3 different Sfx engines. Then we have a PlaySfx call that takes an enum flag as to which Sfx set the cue is in. This makes our code control checkins much faster since we don't have one giant Sfx Sound Bank and Wave Bank.

It is also handy to have a currently playing music track, and a NextMusicCue that will get played when the current one finishes.

We also have an SfxItem class that hold info about a currently playing item and each of the Sfx engines. So our overall update method loos like this...

private void UpdateAudioEngines()
{
if (TheAudioEngineTest != null)
{
TheAudioEngineTest.Update();
}
if (TheAudioEngine != null)
{
TheAudioEngine.Update();
if (CurrentMusicCue != null && CurrentMusicCue.IsStopped && NextMusicCue != null)
{
CurrentMusicCue = NextMusicCue;
NextMusicCue = null;
CurrentMusicCue.Play();
}
}
if (SfxActions != null && SfxActions.TheAudioEngine != null)
{
SfxActions.TheAudioEngine.Update();
}
SfxItem.UpdateAll();
}

Enjoy

T.F.

Tuesday, March 3, 2009

How to Dig for Truth

I read a recent post here there is some talk about the lack of information on the internet.

There in fact is no lack of very deep scientific information. The major problem is finding it.
General news articles are mostly wrong and usually the articles do not even match the conclusions of the underlying scientific papers.

There are two issues. First, finding real scientific papers, and second, understanding them.

So I'll guide you through an example...

The Initial News Article
Tuesday March 3, 2009 there is an article stating...
This article in on Google's front page news, and the title is

Older people face greater HIV infection risks: study

and it is on the Reuters UK page.

* Doctors failing to screen for HIV in older patients

* HIV progresses faster to AIDS in those aged 50-plus

* Older people more likely to risk unprotected sex

By Laura MacInnis GENEVA (Reuters) - Doctors are failing to diagnose HIV in older patients, who are exposed to greater risk of infection as erectile dysfunction drugs extend their sex lives, a study published by the World Health Organization said on Tuesday.

One Level Down, The source news article.

Ok, lets go find the original study...
Lucky, this one gives us a clue, it is WHO, the World Health Organization. So we google the site.
It's the top hit on Google.
An it is the top blurb on the WHO web site.
3 March 2009 -- HIV prevalence and incidence in people 50 years of age and over seem surprisingly high and the risk factors are largely unexplored. Understanding the epidemiology of HIV infection in older individuals can lead to interventions to make these years safer and more enjoyable, according to articles in the March edition of the WHO Bulletin.
Ah, there is a link to the actual bulletin.

The 'Real' Story and What was the Source of Funding

This has many authors, so now our trust level can go up some. These look like actual scientists.
See those little reference letters next to their names? those tell where they are from .
a. Department of HIV/AIDS, World Health Organization, 20 avenue Appia, 1211 Geneva 27, Switzerland.
b. Independent Consultant, Geneva, Switzerland.
c. St Olaf College, Northfield, MN, United States of America.
Now a. is the HIV/AIDS department. You can guess that they will have an agenda. Given that it is WHO, they will have scientific training, and are funded by, hmmm. lets see who pays their bills...
A google of WHO funding gets about the third hit down as Wikipedia, and the very first line says ...
The World Health Organization (WHO) is a specialized agency of the United Nations (UN) that acts as a coordinating authority on international public health.
Ok, so they are part of the UN, which is a ultra political organization. Just keep that in mind.

b. above is an independent consultant.
That could mean just about anything. So, who is Brian G Williams, back to google.
Wow, lots of hits. Lets try Brian G. Williams Geneva. The sixth hit down is Science magazine with a list of some papers he has co-authored. This guy is definitely for real in the HIV research community. And he has worked a lot with the UN.

Then there are several references with c.
I could search the first one, Chris Miller but that name is so common, I'll search a more unique name, Emily Segar and add in Olaf to get the university hits....
Hmm, she shows up as a student graduating this year, and very big on the golf team.
so I add HIV as a search word, and now I see college news about the research.
In here is the 23rd National Conference on Undergraduate Research where the same authors are showing off their study.

This is the abstract...
Over 22 million individuals in sub-Saharan Africa are infected with Human Immunodeficiency Virus (HIV). Recent epidemiological research suggests that an unexpectedly high prevalence of HIV exists among the elderly in South Africa, particularly among females (Schmid et al., 2004). This trend deviates from the norm in most countries, where the prevalence of HIV infection steadily decreases after the age of 25. In the current study, we sought to (1) describe the age specific prevalence of HIV for men and women in Sub-Saharan Africa, (2) identify the risk factors associated with HIV prevalence in elderly populations and (3) compare and contrast risk factors between sub-Saharan African countries. We utilized individual-level data from 16 sub-Saharan African countries in the Demographic Health Surveys (DHS). The DHS surveys were conducted between 2002 and 2006 using a cross-sectional stratified clustered sampling methodology, which, in theory, allows findings to be generalized to each country’s population. The surveys collected data on household and respondent characteristics, socioeconomic status, education, knowledge and beliefs about sexually transmitted disease, health status, nutrition, and HIV status. Due to the nature of the sampling method, we incorporated weighted estimates for each individual (N=100,000+) into the prevalence estimates. Multiple logistic regression analyses were performed to identify potential socioeconomic, demographic, and cultural factors that were associated with HIV prevalence in elderly populations. We hope to identify whether an increased prevalence of HIV in the elderly exists in sub-Saharan African countries, and which factors may mitigate the risk for this population. Implications for future research of the epidemiology of HIV in sub-Saharan Africa are discussed.
Oh, it is a study they did in Africa.
I wonder who the professor at the university doing the study is. Maybe the first name in the list? David Tonyan Olaf gets a hit on the same article.
Ok, so I go to the top level web site for St. Olaf college, then faculty, then departments.
First I went back and read the names of the c. people.
Ok, I got sick of digging out the names. I suppose I could email one of the students and ask who the acedemic advisor is. But...

A Nugget of Facts Not in the News Articles
Did you notice the line in the abstract above...
This trend deviates from the norm in most countries, where the prevalence of HIV infection steadily decreases after the age of 25.
What the heck. When I read the news article I was thinking along the lines of good old USA retirement homes. Why they must be hives of lust! skyrocketing HIV rates for the elderly!
What about dear old Mom!

Nope. Not even the core idea. The core idea of the paper is that African HIV rates are high for the elderly, in contrast to the rest of the world.

What we Didn't Do
We got off lucky on this one and did not have to read the actual scientific paper. Aand no, I did not plan it ahead of time. I wrote this blog as a stream of thought, as I did the research.

The Point
The whole point of this article is that YOU can do the research on any topic. Follow the rabiit down the hole. You never know where it will lead.

TF

P.S. I am NOT going to go back and edit or correct this article like I usualy do. It is a stram of thought, as it is. Typos and everything. :-)

P.P.S. I have to give credit to the WHO thought. They point out that in the developed countries HIV in the elderly is slowly going up. So it is not entirely a wrong news story.
But this begs the question; Is the WHO just trying to get more AIDS funding, and is it the best use of the money for world health issues? Yes, again, the UN is highly political.

P.P.P.S. Not that the WHO article title has the word Unexplored in it. In other words they have not yet explored the issue of the aging and HIV. So the news article is still way off.

P.P.P.P.S. After digging for the truth, wait a day or two and then rethink and reread the information. And dig a little more. On a technical subject it helps to read at least 6 technical papers (not news articles) on the subject to get more balance. And if the subject is climate, try reading some older papers from BAIT (Before An Inconvenient Truth).

P.P.P.P.P.S. the next day, a very good article on the same topic.

Thursday, September 25, 2008

FBX and XNA - Part 6 - 40 Million Triangles Per Second

Now that we have pretty 3D models on the screen, the first impression is "Golly thats slow".
One test model I have is about 18000 polygons or 35000 triangles. It displays at about 21 frames per second (FPS) on my PC. In a game that would mean that 2 models on the screen would be but-ugly slow. So lets move lots of the processing onto the Graphics Processing Unit (GPU).

The GPU uses a language call HLSL (High Level Shader Language). What you do is write a somthing.fx file, add it to your project and then load the Effect with the Content loader. Sounds easy. The trick is what to put in the .fx file and how to drive it. Fortunately Microsoft has already provided a SkinnedModelProcessor and it has a file called SkinnedModel.fx We just is that as-is.

HLSL defines method names in a .fx file for doing two things. First is converting a model X,Y,Z point to world space so it can be used as one of the points in a triangle or line drawing. Second is to determine the color of a point on a triangle as it is filled in on the screen (A Shader).

SkinnedModel.fx has a section like this...

// Vertex shader input structure.
struct VS_INPUT
{
float4 Position : POSITION0;
float3 Normal : NORMAL0;
float2 TexCoord : TEXCOORD0;
float4 BoneIndices : BLENDINDICES0;
float4 BoneWeights : BLENDWEIGHT0;
};
It indicates that each triangle corner is supposed to have a Position, a normal to the surface, a texture coordinate. That is just like our previous code. The BoneIndices is the index of the Deformer (Bone) that influences the point location with a weight of BoneWeights. The trick here is that a float4 has a X,Y,Z,W values meaning that there can be up to 4 deformers and their associated matrices that can influence the point.

Note: The Position has a vector4 which means it has a X,Y,Z and the mysterious W. Just set all the W's to 1.0f and it will work fine.

So we modify our code to generate this new structure in place of the previous DrawData point list. We use the structure...

    [Serializable]
public struct SkinnedModelRec
{
public Vector4 Position;
public Vector3 Normal;
///
/// The UV texture coord.
///

public Vector2 TexCoord;
///
/// We can have up to 4 bones influencing one vertex point and normal.
///

public Vector4 BoneIndices;
///
/// We can have up to 4 bones per vertex. These are the weights. Leave them 0 of no bone.
///

public Vector4 BoneWeights;

///
/// Description of the elements of this structure.
///

public static readonly VertexElement[] VertexElements =
new VertexElement[] {
new VertexElement(0,0,VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.Position, 0),
new VertexElement(0, sizeof(float) * 4, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0),
new VertexElement(0, sizeof(float) * 7, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0),
new VertexElement(0, sizeof(float) * 9, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.BlendIndices, 0),
new VertexElement(0, sizeof(float) * 13, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.BlendWeight, 0)
};

... some methods ...
}
Notice the vertex element definition. It tells XNA where the elements in the structure go in relation to the defeinitions in SkinnedModel.fx

Next we load all the points in the model, and the draw indices into the GPU


            TheVertexBuffer = new VertexBuffer(gd, typeof(SkinnedModelRec), DrawData.Length, BufferUsage.WriteOnly);
TheVertexDeclaration = new VertexDeclaration(gd, SkinnedModelRec.VertexElements);
TheIndexBuffer = new IndexBuffer(gd, typeof(int), DrawIndices.Length, BufferUsage.WriteOnly);
TheVertexBuffer.SetData(DrawData);
TheIndexBuffer.SetData(DrawIndices);
And then we can draw them. I realize that all the code is not here. This is just to get the idea of the style.



private void DrawSingleMesh(GraphicsDevice gd, PTake CurrentTake, Effect effect, PMesh mesh, PModelTracker tracker)
{
//mesh.CalcCurrentState(CurrentTake, tracker);
mesh.CalcAllDeformerMatrices(CurrentTake, tracker);

mesh.SetupGPU(gd);

gd.RenderState.CullMode = CullMode.None;
gd.VertexDeclaration = mesh.TheVertexDeclaration;
gd.Vertices[0].SetSource(mesh.TheVertexBuffer, 0, SkinnedModelRec.SizeInBytes);
gd.Indices = mesh.TheIndexBuffer;

// We draw all the triangles in a single texture at once. Then switch to next texture.
// Sometimes textures repeat later in the list.
foreach (PTextureRun tr in mesh.DrawDataTextureSegments)
{
if (tr.TextureId (lessthan messes up HTML) 0)
{
// Some polygons have no texture and are just white with illumination.
// We should probably not allow untextured polygons.
//effect.TextureEnabled = false;
effect.Parameters["Texture"].SetValue(DefaultTexture);
}
else
{
//effect.TextureEnabled = true;
effect.Parameters["Texture"].SetValue(Textures[tr.TextureId].Tex);
}

// Max bones in the SkinnedModel.fx is 59
Matrix[] bonesMatricies = new Matrix[59];
for (int i = 0; i lessthan mesh.DrawDeformers.Length; i++)
{
bonesMatricies[i] = mesh.DrawDeformers[i].Transform;
}
effect.Parameters["Bones"].SetValue(bonesMatricies);

effect.Begin();
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Begin();

gd.DrawIndexedPrimitives(PrimitiveType.TriangleList,
0,
tr.MinVertexIdx,
tr.MaxVertexIdx,
tr.StartIdx,
tr.PrimCount);

pass.End();
}
effect.End();
}
}
Easy as that. Then I tested it with a loop. We can now put up lots of our model on the screen and hold 60 FPS. The result is 40 million triangles per second. Now that's where we need to be to play a game.

TF

Thursday, September 18, 2008

FBX and XNA Part 5 - Animation Interpolation

One thing I glossed over was how you arrive at the 'current animation rotation' or translation.

The Key: entries represent instants in time that the LimbNode is at a particular translation and rotation. (And scale and color and transparency, but we ignore theses.) What we do is have a ModelTracker class that keeps track of the current values for the animation for every LimbNode.
We simply interpolate between the instants in time. But remember, the first entry is the beginning of the time line, and the last entry is the end so it is a tricky interpolation.

If there is only one entry in the Key: section we just use that value. If there are no Key: entries at all we use the default. If there is not even a take: section for that LimbNode in the given Take, we create a new key frame and initialize it's default values to the LimbNode Lcl Rotation and Translation.

This all happens in the Games Update section. Many ModelTrackers can share the same Model.

We also detect when the animation hits the end and provide a callback so the game can take action when the animation ends.

Not yet implemented bu soon to come, tracking where a given LimbNode is in world coordinates so we can detect when a blade strikes an enemy. Update: This is done. Turns out that on a LimbNode in Maya you can set arbitrary attributes, that then show up in the Properties60 section of the LimbNode entry. We just set a property "Attack" and look for those and track them during attack moves. Same for "Step" for tracking foot prints and putting prints in the snow.

Also, KeyFrame sequences can be specified on any property, so we can put an animation on the value of a Attack Attribute and vary the attack deadliness through the attack swing.

I have not yet accounted for the Initial Velocity Tangent and Final Velocity Tangent. This allows for smoother blending between key frame segments.

TF

FBX and XNA Part 4 - Drawing the model.

Now that we know what all the parts of the model are in the FBX, lets combine them.

well,

for each Mesh in the Model
make a copy of the Vertices
make an array of Matrices, one for each point in the Vertices.
They all get initialized to all zeros (not Identity)
for each SubDeformer
M = SubDeformer.Transform
if it has any Indexes (some do not)
find the animation in the current Take
for this Deformer.
It is the one in Connections that has the
same name as the SubDeformer
M = M * the animation current rotation * the PreRotation *
the animation current translation
if there is a parent LimbNode recursively do the
M = M * rot * prerot * trans
remember m for this deformer.

Now for all the remembered Ms influence the array of Matrices by
doing weighted sums of the elements in the matrices. Note that
this is not a matrix multiply, it is an element
by element weighted sum.
vMats[Indexes[i]] += Matrix.Multiply(Transform, Weights[i])

Now for every element in the copy of Vertices, do a matrix multiply by
the weighted sum matrices
to arrive at the position. Also do a TransformNormal to get the normals right.

Phew, that is how its done.

Now take the copy of Vertices that is now in world coordinates, and draw it on the screen.

foreach Polygon in the list of polygon indices, call
GraphicsDevice.DrawUserPrimitives. Like this
gd.DrawUserPrimitives(PrimitiveType.TriangleFan, drawVerticesXformed, polys[i].StartIdx, polys[i].PrimCount);

It's easy as that.

TF

FBX and XNA Part 3 - Models cont.

Now we look at the LimbNodes.

A LimbNode is a bone in the model. Just like your bones they have a length (translation) and an angle (rotation). There is a section in LimbNodes called Properties60: and there are some important porperties.
Lcl Translation and Lcl Rotation are the default values to use for the limb if there is no animation information. There is also a PreRotation which defines a transform on the limb before translation is added in. This is necessary because limbs may have rotation in the joint.

The Deformer and SubDeformer are what connect the LimbNode to the mesh so the points in the mesh can actually be moved in world coordinates. A Deformer is just the parent of one or more SubDeformers and is most important because it links (with the Connections: section) to the LimbNode.
A SubDeformer simply has a series of indexes in the Vertices, and a series of weights. In all our models if there are multiple deformers for a given Vertex, the sum of weights will be 1.0
there also is a very important Transform. The Transform is where the Deformer is relative to the LimbNode.

I think we have all the pieces now. Next we actually combine them to get a model that moves in world coordinates.

TF