/* * 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 } }