/* * Util.cs * Copyright (c) 2006 David Astle * * 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. */ using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Collections.ObjectModel; namespace Xclna.Xna.Animation { /// /// Info on how a model is skinned. /// public enum SkinningType { /// /// No skinning. /// None, /// /// A max of four influences per vertex. /// FourBonesPerVertex, /// /// A max of eight influences per vertex. /// EightBonesPerVertex, /// /// A max of twelve influences per vertex. /// TwelveBonesPerVertex } /// /// Provides various animation utilities. /// public sealed class Util { /// /// Ticks per frame at 60 frames per second. /// public const long TICKS_PER_60FPS = TimeSpan.TicksPerSecond / 60; /// /// Gets info on what skinning info a vertex element array contains. /// /// The vertex elements. /// Info on what type of skinning the elements contain. public static SkinningType GetSkinningType(VertexElement[] elements) { int numIndexChannels = 0; int numWeightChannels = 0; foreach (VertexElement e in elements) { if (e.VertexElementUsage == VertexElementUsage.BlendIndices) numIndexChannels++; else if (e.VertexElementUsage == VertexElementUsage.BlendWeight) numWeightChannels++; } if (numIndexChannels == 3 || numWeightChannels == 3) return SkinningType.TwelveBonesPerVertex; else if (numIndexChannels == 2 || numWeightChannels == 2) return SkinningType.EightBonesPerVertex; else if (numIndexChannels == 1 || numWeightChannels == 1) return SkinningType.FourBonesPerVertex; return SkinningType.None; } /// /// Reflects a matrix across the Z axis by multiplying both the Z /// column and the Z row by -1 such that the Z,Z element stays intact. /// /// The matrix to be reflected across the Z axis public static void ReflectMatrix(ref Matrix m) { m.M13 *= -1; m.M23 *= -1; m.M33 *= -1; m.M43 *= -1; m.M31 *= -1; m.M32 *= -1; m.M33 *= -1; m.M34 *= -1; } private static T Max(params T[] items) where T : IComparable { IComparable max = null; foreach (IComparable c in items) { if (max == null) max = c; else { if (c.CompareTo(max) > 0) max = c; } } return (T)max; } /// /// Converts from an array of bytes to any vertex type. /// /// The type of vertex to which we are converting the bytes /// The bytes that will be converted to the vertices /// The size of one vertex /// Any working device; required to use our conversion hack /// An array of the converted vertices public static T[] Convert(byte[] data, int vertexSize, GraphicsDevice device) where T : struct { T[] verts = new T[data.Length / vertexSize]; using (VertexBuffer vb = new VertexBuffer(device, data.Length, ResourceUsage.None)) { vb.SetData(data); vb.GetData(verts); } return verts; } private static Quaternion qStart, qEnd, qResult; private static Vector3 curTrans, nextTrans, lerpedTrans; private static Vector3 curScale, nextScale, lerpedScale; private static Matrix startRotation, endRotation; private static Matrix returnMatrix; /// /// Roughly decomposes two matrices and performs spherical linear interpolation /// /// Source matrix for interpolation /// Destination matrix for interpolation /// Ratio of interpolation /// The interpolated matrix public static Matrix SlerpMatrix(Matrix start, Matrix end, float slerpAmount) { if (start == end) return start; // Get rotation components and interpolate (not completely accurate but I don't want // to get into polar decomposition and this seems smooth enough) Quaternion.CreateFromRotationMatrix(ref start, out qStart); Quaternion.CreateFromRotationMatrix(ref end, out qEnd); Quaternion.Lerp(ref qStart, ref qEnd, slerpAmount, out qResult); // Get final translation components curTrans.X = start.M41; curTrans.Y = start.M42; curTrans.Z = start.M43; nextTrans.X = end.M41; nextTrans.Y = end.M42; nextTrans.Z = end.M43; Vector3.Lerp(ref curTrans, ref nextTrans, slerpAmount, out lerpedTrans); // Get final scale component Matrix.CreateFromQuaternion(ref qStart, out startRotation); Matrix.CreateFromQuaternion(ref qEnd, out endRotation); curScale.X = start.M11 - startRotation.M11; curScale.Y = start.M22 - startRotation.M22; curScale.Z = start.M33 - startRotation.M33; nextScale.X = end.M11 - endRotation.M11; nextScale.Y = end.M22 - endRotation.M22; nextScale.Z = end.M33 - endRotation.M33; Vector3.Lerp(ref curScale, ref nextScale, slerpAmount, out lerpedScale); // Create the rotation matrix from the slerped quaternions Matrix.CreateFromQuaternion(ref qResult, out returnMatrix); // Set the translation returnMatrix.M41 = lerpedTrans.X; returnMatrix.M42 = lerpedTrans.Y; returnMatrix.M43 = lerpedTrans.Z; // And the lerped scale component returnMatrix.M11 += lerpedScale.X; returnMatrix.M22 += lerpedScale.Y; returnMatrix.M33 += lerpedScale.Z; return returnMatrix; } /// /// Roughly decomposes two matrices and performs spherical linear interpolation /// /// Source matrix for interpolation /// Destination matrix for interpolation /// Ratio of interpolation /// Stores the result of hte interpolation. public static void SlerpMatrix( ref Matrix start, ref Matrix end, float slerpAmount, out Matrix result) { if (start == end) { result = start; return; } // Get rotation components and interpolate (not completely accurate but I don't want // to get into polar decomposition and this seems smooth enough) Quaternion.CreateFromRotationMatrix(ref start, out qStart); Quaternion.CreateFromRotationMatrix(ref end, out qEnd); Quaternion.Lerp(ref qStart, ref qEnd, slerpAmount, out qResult); // Get final translation components curTrans.X = start.M41; curTrans.Y = start.M42; curTrans.Z = start.M43; nextTrans.X = end.M41; nextTrans.Y = end.M42; nextTrans.Z = end.M43; Vector3.Lerp(ref curTrans, ref nextTrans, slerpAmount, out lerpedTrans); // Get final scale component Matrix.CreateFromQuaternion(ref qStart, out startRotation); Matrix.CreateFromQuaternion(ref qEnd, out endRotation); curScale.X = start.M11 - startRotation.M11; curScale.Y = start.M22 - startRotation.M22; curScale.Z = start.M33 - startRotation.M33; nextScale.X = end.M11 - endRotation.M11; nextScale.Y = end.M22 - endRotation.M22; nextScale.Z = end.M33 - endRotation.M33; Vector3.Lerp(ref curScale, ref nextScale, slerpAmount, out lerpedScale); // Create the rotation matrix from the slerped quaternions Matrix.CreateFromQuaternion(ref qResult, out result); // Set the translation result.M41 = lerpedTrans.X; result.M42 = lerpedTrans.Y; result.M43 = lerpedTrans.Z; // Add the lerped scale component result.M11 += lerpedScale.X; result.M22 += lerpedScale.Y; result.M33 += lerpedScale.Z; } /// /// Determines whether or not a ModelMeshPart is skinned. /// /// The part to check. /// True if the part is skinned. public static bool IsSkinned(ModelMeshPart meshPart) { VertexElement[] ves = meshPart.VertexDeclaration.GetVertexElements(); foreach (VertexElement ve in ves) { //(BlendIndices with UsageIndex = 0) specifies matrix indices for fixed-function vertex // processing using indexed paletted skinning. if (ve.VertexElementUsage == VertexElementUsage.BlendIndices && ve.UsageIndex == 0) { return true; } } return false; } /// /// Determines whether or not a ModelMesh is skinned. /// /// The mesh to check. /// True if the mesh is skinned. public static bool IsSkinned(ModelMesh mesh) { foreach (ModelMeshPart mmp in mesh.MeshParts) { if (IsSkinned(mmp)) return true; } return false; } /// /// Determines whether or not a Model is skinned. /// /// The model to check. /// True if the model is skinned. public static bool IsSkinned(Model model) { foreach (ModelMesh mm in model.Meshes) { if (IsSkinned(mm)) return true; } return false; } } }