/* * Copyright (c) 2006-2014, openmetaverse.org * All rights reserved. * * - Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Neither the name of the openmetaverse.org nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.IO; namespace OpenMetaverse.Rendering { /// /// Load and handle Linden Lab binary meshes. /// /// /// The exact definition of this file is a bit sketchy, especially concerning skin weights. /// A good starting point is on the /// second life wiki /// public class LindenMesh { const string MeshHeader = "Linden Binary Mesh 1.0"; const string MorphFooter = "End Morphs"; public LindenSkeleton Skeleton { get; private set; } //!< The skeleton used to animate this mesh #region Mesh Structs /// /// Defines a polygon /// public struct Face { public short[] Indices; //!< Indices into the vertex array } /// /// Structure of a vertex, No surprises there, except for the Detail tex coord /// /// /// The skinweights are a tad unconventional. The best explanation found is: /// >Each weight actually contains two pieces of information. The number to the /// >left of the decimal point is the index of the joint and also implicitly /// >indexes to the following joint. The actual weight is to the right of the /// >decimal point and interpolates between these two joints. The index is into /// >an "expanded" list of joints, not just a linear array of the joints as /// >defined in the skeleton file. In particular, any joint that has more than /// >one child will be repeated in the list for each of its children. /// /// Maybe I'm dense, but that description seems to be a bit hard to build an /// algorithm on. /// /// Esentially the weights are compressed into one floating point value. /// 1. The whole number part is an index into an array of joints /// 2. The fractional part is the weight that joint has /// 3. If the fractional part is 0 (x.0000) then the vertex is 100% influenced by the specified joint /// public struct Vertex { public Vector3 Coord; //!< 3d co-ordinate of the vertex public Vector3 Normal; //!< Normal of the vertex public Vector3 BiNormal; //!< Bi normal of the vertex public Vector2 TexCoord; //!< UV maping of the vertex public Vector2 DetailTexCoord; //!< Detailed? UV mapping public float Weight; //!< Used to calculate the skin weights /// /// Provide a nice format for debugging /// /// Vertex definition as a string public override string ToString() { return String.Format("Coord: {0} Norm: {1} BiNorm: {2} TexCoord: {3} DetailTexCoord: {4}", Coord, Normal, BiNormal, TexCoord, DetailTexCoord); } } /// /// Describes deltas to apply to a vertex in order to morph a vertex /// public struct MorphVertex { public uint VertexIndex; //!< Index into the vertex list of the vertex to change public Vector3 Coord; //!< Delta position public Vector3 Normal; //!< Delta normal public Vector3 BiNormal; //!< Delta BiNormal public Vector2 TexCoord; //!< Delta UV mapping /// /// Provide a nice format for debugging /// /// MorphVertex definition as a string public override string ToString() { return String.Format("Index: {0} Coord: {1} Norm: {2} BiNorm: {3} TexCoord: {4}", VertexIndex, Coord, Normal, BiNormal, TexCoord); } } /// /// Describes a named mesh morph, essentially a named list of MorphVertices /// public struct Morph { public string Name; //!< Name of the morph public int NumVertices; //!< Number of vertices to distort public MorphVertex[] Vertices; //!< The actual list of morph vertices /// /// Provide a nice format for debugging /// /// The name of the morph public override string ToString() { return Name; } } /// /// Don't really know what this does /// public struct VertexRemap { public int RemapSource; //!< Source index public int RemapDestination; //!< Destination index /// /// Provide a nice format for debugging /// /// Human friendly format public override string ToString() { return String.Format("{0} -> {1}", RemapSource, RemapDestination); } } #endregion Mesh Structs #region reference mesh /// /// A reference mesh is one way to implement level of detail /// /// /// Reference meshes are supplemental meshes to full meshes. For all practical /// purposes almost all lod meshes are implemented as reference meshes, except for /// 'avatar_eye_1.llm' which for some reason is implemented as a full mesh. /// public class ReferenceMesh { public float MinPixelWidth; //!< Pixel width on screen before switching to coarser lod public string Header; //!< Header - marking the file as a Linden Lab Mesh (llm) public bool HasWeights; //!< Do the vertices carry any defintions about skin weights public bool HasDetailTexCoords; //!< Do the vertices carry any defintions about detailed UV mappings public Vector3 Position; //!< Origin of this mesh public Vector3 RotationAngles; //!< Used to reconstruct a normalized quarternion (These are *NOT* Euler rotations) public byte RotationOrder; //!< Not used public Vector3 Scale; //!< Scaling information public ushort NumFaces; //!< # of polygons in the mesh public Face[] Faces; //!< Polygons making up the mesh, the indices are into the full mesh /// /// Load a mesh from a stream /// /// Filename and path of the file containing the reference mesh public virtual void LoadMesh(string filename) { using(FileStream meshStream = new FileStream(filename, FileMode.Open, FileAccess.Read)) using (EndianAwareBinaryReader reader = new EndianAwareBinaryReader(meshStream)) { Header = TrimAt0(reader.ReadString(24)); if (!String.Equals(Header, MeshHeader)) throw new FileLoadException("Unrecognized mesh format"); // Populate base mesh parameters HasWeights = (reader.ReadByte() != 0); HasDetailTexCoords = (reader.ReadByte() != 0); Position = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); RotationAngles = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); RotationOrder = reader.ReadByte(); Scale = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); NumFaces = reader.ReadUInt16(); Faces = new Face[NumFaces]; for (int i = 0; i < NumFaces; i++) Faces[i].Indices = new[] { reader.ReadInt16(), reader.ReadInt16(), reader.ReadInt16() }; } } } /// /// Level of Detail mesh /// [Obsolete("Renamed to: ReferenceMesh")] public class LODMesh { public float MinPixelWidth; protected string _header; protected bool _hasWeights; protected bool _hasDetailTexCoords; protected Vector3 _position; protected Vector3 _rotationAngles; protected byte _rotationOrder; protected Vector3 _scale; protected ushort _numFaces; protected Face[] _faces; public virtual void LoadMesh(string filename) { byte[] buffer = File.ReadAllBytes(filename); BitPack input = new BitPack(buffer, 0); _header = TrimAt0(input.UnpackString(24)); if (!String.Equals(_header, MeshHeader)) return; // Populate base mesh variables _hasWeights = (input.UnpackByte() != 0); _hasDetailTexCoords = (input.UnpackByte() != 0); _position = new Vector3(input.UnpackFloat(), input.UnpackFloat(), input.UnpackFloat()); _rotationAngles = new Vector3(input.UnpackFloat(), input.UnpackFloat(), input.UnpackFloat()); _rotationOrder = input.UnpackByte(); _scale = new Vector3(input.UnpackFloat(), input.UnpackFloat(), input.UnpackFloat()); _numFaces = input.UnpackUShort(); _faces = new Face[_numFaces]; for (int i = 0; i < _numFaces; i++) _faces[i].Indices = new short[] { input.UnpackShort(), input.UnpackShort(), input.UnpackShort() }; } } #endregion lod mesh public float MinPixelWidth; //!< Width of redered avatar, before moving to a coarser LOD public string Name { get; protected set; } //!< The name of this mesh public string Header { get; protected set; } //!< The header marker contained in the .llm file public bool HasWeights { get; protected set; } //!< Does the file contain skin weights? public bool HasDetailTexCoords { get; protected set; } //!< Does the file contain detailed UV mapings public Vector3 Position { get; protected set; } //!< Origin of this mesh public Vector3 RotationAngles { get; protected set; } //!< Rotation - This is a compressed quaternion //public byte RotationOrder public Vector3 Scale { get; protected set; } //!< Scale of this mesh public ushort NumVertices { get; protected set; } //!< # of vertices in the file public Vertex[] Vertices { get; protected set; } //!< The actual vertices defining the 3d shape public ushort NumFaces { get; protected set; } //!< # of polygons in the file public Face[] Faces { get; protected set; } //!< The polgon defintion public ushort NumSkinJoints { get; protected set; } //!< # of joints influencing the mesh public string[] SkinJoints { get; protected set; } //!< Named list of joints public int NumRemaps { get; protected set; } //!< # of vertex remaps public VertexRemap[] VertexRemaps { get; protected set; } //!< The actual vertex remapping list // lods can either be Reference meshes or full LindenMeshes // so we cannot use a collection of specialized classes public SortedList LodMeshes { get; protected set; } //!< The LOD meshes, available for this mesh public Morph[] Morphs; //!< The morphs this file contains /// /// Construct a linden mesh with the given name /// /// the name of the mesh public LindenMesh(string name) : this(name, null) { } /// /// Construct a linden mesh with the given name /// /// the name of the mesh /// The skeleton governing mesh deformation public LindenMesh(string name, LindenSkeleton skeleton) { Name = name; Skeleton = skeleton; LodMeshes = new SortedList(); if (Skeleton == null) Skeleton = LindenSkeleton.Load(); } /// /// Load the mesh from a stream /// /// The filename and path of the file containing the mesh data public virtual void LoadMesh(string filename) { using(FileStream meshData = new FileStream(filename, FileMode.Open, FileAccess.Read)) using (EndianAwareBinaryReader reader = new EndianAwareBinaryReader(meshData)) { Header = TrimAt0(reader.ReadString(24)); if (!String.Equals(Header, MeshHeader)) throw new FileLoadException("Unrecognized mesh format"); // Populate base mesh parameters HasWeights = (reader.ReadByte() != 0); HasDetailTexCoords = (reader.ReadByte() != 0); Position = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); RotationAngles = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); /* RotationOrder = */ reader.ReadByte(); Scale = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); // Populate the vertex array NumVertices = reader.ReadUInt16(); Vertices = new Vertex[NumVertices]; for (int i = 0; i < NumVertices; i++) Vertices[i].Coord = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); for (int i = 0; i < NumVertices; i++) Vertices[i].Normal = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); for (int i = 0; i < NumVertices; i++) Vertices[i].BiNormal = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); for (int i = 0; i < NumVertices; i++) Vertices[i].TexCoord = new Vector2(reader.ReadSingle(), reader.ReadSingle()); if (HasDetailTexCoords) { for (int i = 0; i < NumVertices; i++) Vertices[i].DetailTexCoord = new Vector2(reader.ReadSingle(), reader.ReadSingle()); } if (HasWeights) { for (int i = 0; i < NumVertices; i++) Vertices[i].Weight = reader.ReadSingle(); } NumFaces = reader.ReadUInt16(); Faces = new Face[NumFaces]; for (int i = 0; i < NumFaces; i++) Faces[i].Indices = new[] { reader.ReadInt16(), reader.ReadInt16(), reader.ReadInt16() }; if (HasWeights) { NumSkinJoints = reader.ReadUInt16(); SkinJoints = new string[NumSkinJoints]; for (int i = 0; i < NumSkinJoints; i++) { SkinJoints[i] = TrimAt0(reader.ReadString(64)); } } else { NumSkinJoints = 0; SkinJoints = new string[0]; } // Grab morphs List morphs = new List(); string morphName = TrimAt0(reader.ReadString(64)); while (morphName != MorphFooter) { if (reader.BaseStream.Position + 48 >= reader.BaseStream.Length) throw new FileLoadException("Encountered end of file while parsing morphs"); Morph morph = new Morph(); morph.Name = morphName; morph.NumVertices = reader.ReadInt32(); morph.Vertices = new MorphVertex[morph.NumVertices]; for (int i = 0; i < morph.NumVertices; i++) { morph.Vertices[i].VertexIndex = reader.ReadUInt32(); morph.Vertices[i].Coord = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); morph.Vertices[i].Normal = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); morph.Vertices[i].BiNormal = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); morph.Vertices[i].TexCoord = new Vector2(reader.ReadSingle(), reader.ReadSingle()); } morphs.Add(morph); // Grab the next name morphName = TrimAt0(reader.ReadString(64)); } Morphs = morphs.ToArray(); // Check if there are remaps or if we're at the end of the file if (reader.BaseStream.Position < reader.BaseStream.Length - 1) { NumRemaps = reader.ReadInt32(); VertexRemaps = new VertexRemap[NumRemaps]; for (int i = 0; i < NumRemaps; i++) { VertexRemaps[i].RemapSource = reader.ReadInt32(); VertexRemaps[i].RemapDestination = reader.ReadInt32(); } } else { NumRemaps = 0; VertexRemaps = new VertexRemap[0]; } } // uncompress the skin weights if (Skeleton != null) { // some meshes aren't weighted, which doesn't make much sense. // we check for left and right eyeballs, and assign them a 100% // to their respective bone List expandedJointList = Skeleton.BuildExpandedJointList(SkinJoints); if (expandedJointList.Count == 0) { if (Name == "eyeBallLeftMesh") { expandedJointList.AddRange(new[] { "mEyeLeft", "mSkull" }); } else if (Name == "eyeBallRightMesh") { expandedJointList.AddRange(new[] { "mEyeRight", "mSkull" }); } } if (expandedJointList.Count > 0) ExpandCompressedSkinWeights(expandedJointList); } } #region Skin weight /// /// Layout of one skinweight element /// public struct SkinWeightElement { public string Bone1; // Name of the first bone that influences the vertex public string Bone2; // Name of the second bone that influences the vertex public float Weight1; // Weight with whitch the first bone influences the vertex public float Weight2; // Weight with whitch the second bone influences the vertex } /// List of skinweights, in the same order as the mesh vertices public List SkinWeights = new List(); /// /// Decompress the skinweights /// /// the expanded joint list, used to index which bones should influece the vertex void ExpandCompressedSkinWeights(List expandedJointList) { for (int i = 0; i < NumVertices; i++) { int boneIndex = (int)Math.Floor(Vertices[i].Weight); // Whole number part is the index float boneWeight = (Vertices[i].Weight - boneIndex); // fractional part it the weight if (boneIndex == 0) // Special case for dealing with eye meshes, which doesn't have any weights { SkinWeights.Add(new SkinWeightElement { Bone1 = expandedJointList[0], Weight1 = 1, Bone2 = expandedJointList[1], Weight2 = 0 }); } else if (boneIndex < expandedJointList.Count) { string bone1 = expandedJointList[boneIndex - 1]; string bone2 = expandedJointList[boneIndex]; SkinWeights.Add(new SkinWeightElement { Bone1 = bone1, Weight1 = 1 - boneWeight, Bone2 = bone2, Weight2 = boneWeight }); } else { // this should add a weight where the "invalid" Joint has a weight of zero SkinWeights.Add(new SkinWeightElement { Bone1 = expandedJointList[boneIndex - 1], Weight1 = 1 - boneWeight, Bone2 = "mPelvis", Weight2 = boneWeight }); } } } #endregion Skin weight [Obsolete("Use LoadLodMesh")] public virtual void LoadLODMesh(int level, string filename) { if (filename == "avatar_eye_1.llm") throw new ArgumentException("Eyballs are not LOD Meshes", "filename"); LODMesh lod = new LODMesh(); lod.LoadMesh(filename); LodMeshes[level] = lod; } public virtual object LoadLodMesh(int level, string filename) { if (filename != "avatar_eye_1.llm") { ReferenceMesh refMesh = new ReferenceMesh(); refMesh.LoadMesh(filename); LodMeshes[level] = refMesh; return refMesh; } LindenMesh fullMesh = new LindenMesh(""); fullMesh.LoadMesh(filename); LodMeshes[level] = fullMesh; return fullMesh; } /// /// Load a reference mesh from a given stream /// /// The lod level of this reference mesh /// the name and path of the file containing the mesh data /// the loaded reference mesh public virtual ReferenceMesh LoadReferenceMesh(int lodLevel, string filename) { if (filename == "avatar_eye_1.llm") throw new ArgumentException("Eyballs are not LOD Meshes", "filename"); ReferenceMesh reference = new ReferenceMesh(); reference.LoadMesh(filename); LodMeshes[lodLevel] = lodLevel; return reference; } /// /// Trim a string at the first occurence of NUL /// /// /// The llm file uses null terminated strings (C/C++ style), this is where /// the conversion is made. /// /// The string to trim /// A standard .Net string public static string TrimAt0(string s) { int pos = s.IndexOf("\0"); if (pos >= 0) return s.Substring(0, pos); return s; } } }