/*
* ModelAnimator.cs
* Copyright (c) 2007 David Astle, Michael Nikonov
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#define GI
#region Using Statements
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using System;
using System.Collections.ObjectModel;
#endregion
namespace Xclna.Xna.Animation
{
///
/// Animates and draws a model that was processed with AnimatedModelProcessor
///
public class ModelAnimator
{
#region Member Variables
/// Stores the world transform for the animation controller.
public Matrix world = Matrix.Identity;
///Model to be animated
public readonly Model model;
/// This stores all of the "World" matrix parameters for an unskinned model
public readonly EffectParameter[] worldParams, matrixPaletteParams;
/// A flattened array of effects, one for each ModelMeshPart
public Effect[] modelEffects;
public ReadOnlyCollection effectCollection;
/// Skeletal structure containg transforms
public BonePoseCollection bonePoses;
public AnimationInfoCollection animations;
/// List of attached objects
public IList attachedObjects = new List();
/// Store the number of meshes in the model
public readonly int numMeshes;
///
/// Stores the number of effects/ModelMeshParts
///
public readonly int numEffects;
// Used to avoid reallocation
public static Matrix skinTransform;
// Buffer for storing absolute bone transforms
public Matrix[] pose;
// Array used for the matrix palette
public Matrix[][] palette;
// Inverse reference pose transforms
public SkinInfoCollection[] skinInfo;
#endregion
#region General Properties
///
/// Gets or sets the world matrix for the animation scene.
///
public Matrix World
{
get
{
return world;
}
set
{
world = value;
}
}
///
/// Returns the number of effects used by the model, one for each ModelMeshPart
///
public int EffectCount
{
get { return numEffects; }
}
///
/// Gets the model associated with this controller.
///
public Model Model
{ get { return model; } }
///
/// Gets the animations that were loaded in from the content pipeline
/// for this model.
///
public AnimationInfoCollection Animations
{ get { return animations; } }
#endregion
#region Constructors
///
/// Creates a new instance of ModelAnimator.
///
/// The game to which this component will belong.
/// The model to be animated.
public ModelAnimator(Model model) : base()
{
this.model = model;
animations = AnimationInfoCollection.FromModel(model);
bonePoses = BonePoseCollection.FromModelBoneCollection(
model.Bones);
numMeshes = model.Meshes.Count;
// Find total number of effects used by the model
numEffects = 0;
foreach (ModelMesh mesh in model.Meshes)
foreach (Effect effect in mesh.Effects)
numEffects++;
// Initialize the arrays that store effect parameters
modelEffects = new Effect[numEffects];
worldParams = new EffectParameter[numEffects];
matrixPaletteParams = new EffectParameter[numEffects];
InitializeEffectParams();
pose = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(pose);
// Get all the skinning info for the model
Dictionary modelTagInfo = (Dictionary)model.Tag;
if (modelTagInfo == null)
throw new Exception("Model Processor must subclass AnimatedModelProcessor.");
skinInfo = (SkinInfoCollection[])modelTagInfo["SkinInfo"];
if (skinInfo == null)
throw new Exception("Model processor must pass skinning info through the tag.");
palette = new Matrix[model.Meshes.Count][];
for (int i = 0; i < skinInfo.Length; i++)
{
if (Util.IsSkinned(model.Meshes[i]))
palette[i] = new Matrix[skinInfo[i].Count];
else
palette[i] = null;
}
// Update after AnimationController by default
//base.UpdateOrder = 1;
//game.Components.Add(this);
// Test to see if model has too many bones
for (int i = 0; i < model.Meshes.Count; i++ )
{
if (palette[i] != null && matrixPaletteParams[i] != null)
{
Matrix[] meshPalette = palette[i];
try
{
matrixPaletteParams[i].SetValue(meshPalette);
}
catch
{
throw new Exception("Model has too many skinned bones for the matrix palette.");
}
}
}
}
#endregion
///
/// Returns skinning information for a mesh.
///
/// The index of the mesh.
/// Skinning information for the mesh.
public SkinInfoCollection GetMeshSkinInfo(int index)
{
return skinInfo[index];
}
///
/// Called during creation and calls to InitializeEffectParams. Returns the list of
/// effects used during rendering.
///
/// A flattened list of effects used during rendering, one for each ModelMeshPart
protected virtual IList CreateEffectList()
{
List effects = new List();
foreach (ModelMesh mesh in model.Meshes)
{
foreach (ModelMeshPart part in mesh.MeshParts)
{
effects.Add(part.Effect);
}
}
return effects;
}
///
/// Initializes the effect parameters. Should be called after the effects
/// on the model are changed.
///
public void InitializeEffectParams()
{
IList effects = CreateEffectList();
if (effects.Count != numEffects)
throw new Exception("The number of effects in the list returned by CreateEffectList "
+ "must be equal to the number of ModelMeshParts.");
effects.CopyTo(modelEffects, 0);
effectCollection = new ReadOnlyCollection(modelEffects);
// store the parameters in the arrays so the values they refer to can quickly be set
for (int i = 0; i < numEffects; i++)
{
worldParams[i] = modelEffects[i].Parameters["World"];
matrixPaletteParams[i] = modelEffects[i].Parameters["MatrixPalette"];
}
}
///
/// Gets a collection of effects, one per ModelMeshPart, that are used by
/// the ModelAnimator. The first index of the collection corresponds to the
/// effect used to draw the first ModelMeshPart of the first Mesh, and the
/// last index corresponds to the effect used to drwa the last ModelMeshPart
/// of the last Mesh.
///
public ReadOnlyCollection Effects
{
get { return effectCollection; }
}
#region Animation and Update Routines
///
/// Updates the animator by finding the current absolute transforms.
///
/// The GameTime.
public void Update()
{
bonePoses.CopyAbsoluteTransformsTo(pose);
for (int i = 0; i < skinInfo.Length; i ++)
{
if (palette[i] == null)
continue;
SkinInfoCollection infoCollection = skinInfo[i];
foreach (SkinInfo info in infoCollection)
{
skinTransform = info.InverseBindPoseTransform;
Matrix.Multiply(ref skinTransform, ref pose[info.BoneIndex],
out palette[i][info.PaletteIndex]);
}
}
foreach (IAttachable attached in attachedObjects)
{
attached.CombinedTransform = attached.LocalTransform *
Matrix.Invert(pose[model.Meshes[0].ParentBone.Index]) *
pose[attached.AttachedBone.Index] * world;
}
}
///
/// Copies the current absolute transforms to the specified array.
///
/// The array to which the transforms will be copied.
public void CopyAbsoluteTransformsTo(Matrix[] transforms)
{
pose.CopyTo(transforms, 0);
}
///
/// Gets the current absolute transform for the given bone index.
///
///
/// The current absolute transform for the bone index.
public Matrix GetAbsoluteTransform(int boneIndex)
{
return pose[boneIndex];
}
///
/// Gets a list of objects that are attached to a bone in the model.
///
public IList AttachedObjects
{
get { return attachedObjects; }
}
///
/// Gets the BonePoses associated with this ModelAnimator.
///
public BonePoseCollection BonePoses
{
get { return bonePoses; }
}
///
/// Draws the current frame
///
/// The game time
public void Draw()
{
try
{
int index = 0;
// Update all the effects with the palette and world and draw the meshes
for (int i = 0; i < numMeshes; i++)
{
ModelMesh mesh = model.Meshes[i];
// The starting index for the modelEffects array
int effectStartIndex = index;
if (palette[i] != null && matrixPaletteParams[index] != null)
{
foreach (Effect effect in mesh.Effects)
{
worldParams[index].SetValue(
world);
matrixPaletteParams[index].SetValue(palette[i]);
index++;
}
}
else
{
foreach (Effect effect in mesh.Effects)
{
worldParams[index].SetValue(pose[mesh.ParentBone.Index] * world);
index++;
}
}
int numParts = mesh.MeshParts.Count;
GraphicsDevice device = mesh.VertexBuffer.GraphicsDevice;
device.Indices = mesh.IndexBuffer;
for (int j = 0; j < numParts; j++ )
{
ModelMeshPart currentPart = mesh.MeshParts[j];
if (currentPart.NumVertices == 0 || currentPart.PrimitiveCount == 0)
continue;
Effect currentEffect = modelEffects[effectStartIndex+j];
currentEffect.CurrentTechnique = currentEffect.Techniques["BasicLightingShaderMain"];
device.VertexDeclaration = currentPart.VertexDeclaration;
device.Vertices[0].SetSource(mesh.VertexBuffer, currentPart.StreamOffset,
currentPart.VertexStride);
currentEffect.Begin();
EffectPassCollection passes = currentEffect.CurrentTechnique.Passes;
int numPasses = passes.Count;
for (int k = 0; k < numPasses; k++)
{
EffectPass pass = passes[k];
pass.Begin();
device.DrawIndexedPrimitives(PrimitiveType.TriangleList, currentPart.BaseVertex,
0, currentPart.NumVertices, currentPart.StartIndex, currentPart.PrimitiveCount);
pass.End();
}
currentEffect.End();
}
}
}
catch (NullReferenceException)
{
throw new InvalidOperationException("The effects on the model for a " +
"ModelAnimator were changed without calling ModelAnimator.InitializeEffectParams().");
}
catch (InvalidCastException)
{
throw new InvalidCastException("ModelAnimator has thrown an InvalidCastException. This is " +
"likely because the model uses too many bones for the matrix palette. The default palette size "
+ "is 56 for windows and 40 for Xbox.");
}
}
#endregion
}
}