using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Xml; using OpenTK.Graphics.OpenGL; using System.Runtime.InteropServices; using OpenMetaverse; using OpenMetaverse.Rendering; namespace Radegast.Rendering { public class FaceData { public float[] Vertices; public ushort[] Indices; public float[] TexCoords; public float[] Normals; public int PickingID = -1; public int VertexVBO = -1; public int IndexVBO = -1; public TextureInfo TextureInfo = new TextureInfo(); public BoundingSphere BoundingSphere = new BoundingSphere(); public static int VertexSize = 32; // sizeof (vertex), 2 x vector3 + 1 x vector2 = 8 floats x 4 bytes = 32 bytes public TextureAnimationInfo AnimInfo; public void CheckVBO(Face face) { if (VertexVBO == -1) { Vertex[] vArray = face.Vertices.ToArray(); GL.GenBuffers(1, out VertexVBO); GL.BindBuffer(BufferTarget.ArrayBuffer, VertexVBO); GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(vArray.Length * VertexSize), vArray, BufferUsageHint.StreamDraw); } if (IndexVBO == -1) { ushort[] iArray = face.Indices.ToArray(); GL.GenBuffers(1, out IndexVBO); GL.BindBuffer(BufferTarget.ElementArrayBuffer, IndexVBO); GL.BufferData(BufferTarget.ElementArrayBuffer, (IntPtr)(iArray.Length * sizeof(ushort)), iArray, BufferUsageHint.StreamDraw); } } } public class TextureAnimationInfo { public Primitive.TextureAnimation PrimAnimInfo; public float CurrentFrame; public double CurrentTime; public bool PingPong; float LastTime = 0f; float TotalTime = 0f; public void Step(double lastFrameTime) { float numFrames = 1f; float fullLength = 1f; if (PrimAnimInfo.Length > 0) { numFrames = PrimAnimInfo.Length; } else { numFrames = Math.Max(1f, (float)(PrimAnimInfo.SizeX * PrimAnimInfo.SizeY)); } if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.PING_PONG) != 0) { if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.SMOOTH) != 0) { fullLength = 2f * numFrames; } else if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.LOOP) != 0) { fullLength = 2f * numFrames - 2f; fullLength = Math.Max(1f, fullLength); } else { fullLength = 2f * numFrames - 1f; fullLength = Math.Max(1f, fullLength); } } else { fullLength = numFrames; } float frameCounter; if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.SMOOTH) != 0) { frameCounter = (float)lastFrameTime * PrimAnimInfo.Rate + LastTime; } else { TotalTime += (float)lastFrameTime; frameCounter = TotalTime * PrimAnimInfo.Rate; } LastTime = frameCounter; if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.LOOP) != 0) { frameCounter %= fullLength; } else { frameCounter = Math.Min(fullLength - 1f, frameCounter); } if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.SMOOTH) == 0) { frameCounter = (float)Math.Floor(frameCounter + 0.01f); } if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.PING_PONG) != 0) { if (frameCounter > numFrames) { if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.SMOOTH) != 0) { frameCounter = numFrames - (frameCounter - numFrames); } else { frameCounter = (numFrames - 1.99f) - (frameCounter - numFrames); } } } if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.REVERSE) != 0) { if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.SMOOTH) != 0) { frameCounter = numFrames - frameCounter; } else { frameCounter = (numFrames - 0.99f) - frameCounter; } } frameCounter += PrimAnimInfo.Start; if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.SMOOTH) == 0) { frameCounter = (float)Math.Round(frameCounter); } GL.MatrixMode(MatrixMode.Texture); GL.LoadIdentity(); if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.ROTATE) != 0) { GL.Translate(0.5f, 0.5f, 0f); GL.Rotate(Utils.RAD_TO_DEG * frameCounter, OpenTK.Vector3d.UnitZ); GL.Translate(-0.5f, -0.5f, 0f); } else if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.SCALE) != 0) { GL.Scale(frameCounter, frameCounter, 0); } else // Translate { float sizeX = Math.Max(1f, (float)PrimAnimInfo.SizeX); float sizeY = Math.Max(1f, (float)PrimAnimInfo.SizeY); GL.Scale(1f / sizeX, 1f / sizeY, 0); GL.Translate(frameCounter % sizeX, Math.Floor(frameCounter / sizeY), 0); } GL.MatrixMode(MatrixMode.Modelview); } [Obsolete("Use Step() instead")] public void ExperimentalStep(double time) { int reverseFactor = 1; float rate = PrimAnimInfo.Rate; if (rate < 0) { rate = -rate; reverseFactor = -reverseFactor; } if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.REVERSE) != 0) { reverseFactor = -reverseFactor; } CurrentTime += time; double totalTime = 1 / rate; uint x = Math.Max(1, PrimAnimInfo.SizeX); uint y = Math.Max(1, PrimAnimInfo.SizeY); uint nrFrames = x * y; if (PrimAnimInfo.Length > 0 && PrimAnimInfo.Length < nrFrames) { nrFrames = (uint)PrimAnimInfo.Length; } GL.MatrixMode(MatrixMode.Texture); GL.LoadIdentity(); if (CurrentTime >= totalTime) { CurrentTime = 0; CurrentFrame++; if (CurrentFrame > nrFrames) CurrentFrame = (uint)PrimAnimInfo.Start; if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.PING_PONG) != 0) { PingPong = !PingPong; } } float smoothOffset = 0f; if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.SMOOTH) != 0) { smoothOffset = (float)(CurrentTime / totalTime) * reverseFactor; } float f = CurrentFrame; if (reverseFactor < 0) { f = nrFrames - CurrentFrame; } if ((PrimAnimInfo.Flags & Primitive.TextureAnimMode.ROTATE) == 0) // not rotaating { GL.Scale(1f / x, 1f / y, 0f); GL.Translate((f % x) + smoothOffset, f / y, 0); } else { smoothOffset = (float)(CurrentTime * PrimAnimInfo.Rate); double startAngle = PrimAnimInfo.Start; double endAngle = PrimAnimInfo.Length; double angle = startAngle + (endAngle - startAngle) * smoothOffset; GL.Translate(0.5f, 0.5f, 0f); GL.Rotate(Utils.RAD_TO_DEG * angle, OpenTK.Vector3d.UnitZ); GL.Translate(-0.5f, -0.5f, 0f); } GL.MatrixMode(MatrixMode.Modelview); } } public class TextureInfo { public System.Drawing.Image Texture; public int TexturePointer; public bool HasAlpha; public UUID TextureID; } public class TextureLoadItem { public FaceData Data; public Primitive Prim; public Primitive.TextureEntryFace TeFace; } public enum RenderPass { Picking, Simple, Alpha } public static class Render { public static IRendering Plugin; } public static class RHelp { public static OpenTK.Vector2 TKVector3(Vector2 v) { return new OpenTK.Vector2(v.X, v.Y); } public static OpenTK.Vector3 TKVector3(Vector3 v) { return new OpenTK.Vector3(v.X, v.Y, v.Z); } public static OpenTK.Vector4 TKVector3(Vector4 v) { return new OpenTK.Vector4(v.X, v.Y, v.Z, v.W); } } /// /// Represents camera object /// public class Camera { Vector3 mPosition; Vector3 mFocalPoint; bool mModified; /// Camera position public Vector3 Position { get { return mPosition; } set { mPosition = value; Modify(); } } /// Camera target public Vector3 FocalPoint { get { return mFocalPoint; } set { mFocalPoint = value; Modify(); } } /// Zoom level public float Zoom; /// Draw distance public float Far; /// Has camera been modified public bool Modified { get { return mModified; } set { mModified = value; } } public double TimeToTarget = 0d; public Vector3 RenderPosition; public Vector3 RenderFocalPoint; void Modify() { mModified = true; if (TimeToTarget <= 0) { RenderPosition = Position; RenderFocalPoint = FocalPoint; } } public void Step(double time) { TimeToTarget -= time; if (TimeToTarget <= time) { EndMove(); return; } mModified = true; float pctElapsed = (float)(time / TimeToTarget); if (RenderPosition != Position) { float distance = Vector3.Distance(RenderPosition, Position); RenderPosition = Vector3.Lerp(RenderPosition, Position, (float)(distance * pctElapsed)); } if (RenderFocalPoint != FocalPoint) { RenderFocalPoint = Interpolate(RenderFocalPoint, FocalPoint, pctElapsed); } } Vector3 Interpolate(Vector3 start, Vector3 end, float fraction) { float distance = Vector3.Distance(start, end); Vector3 direction = end - start; return start + direction * fraction; } public void EndMove() { mModified = true; TimeToTarget = 0; RenderPosition = Position; RenderFocalPoint = FocalPoint; } } public static class MeshToOBJ { public static bool MeshesToOBJ(Dictionary meshes, string filename) { StringBuilder obj = new StringBuilder(); StringBuilder mtl = new StringBuilder(); FileInfo objFileInfo = new FileInfo(filename); string mtlFilename = objFileInfo.FullName.Substring(objFileInfo.DirectoryName.Length + 1, objFileInfo.FullName.Length - (objFileInfo.DirectoryName.Length + 1) - 4) + ".mtl"; obj.AppendLine("# Created by libprimrender"); obj.AppendLine("mtllib ./" + mtlFilename); obj.AppendLine(); mtl.AppendLine("# Created by libprimrender"); mtl.AppendLine(); int primNr = 0; foreach (FacetedMesh mesh in meshes.Values) { for (int j = 0; j < mesh.Faces.Count; j++) { Face face = mesh.Faces[j]; if (face.Vertices.Count > 2) { string mtlName = String.Format("material{0}-{1}", primNr, face.ID); Primitive.TextureEntryFace tex = face.TextureFace; string texName = tex.TextureID.ToString() + ".tga"; // FIXME: Convert the source to TGA (if needed) and copy to the destination float shiny = 0.00f; switch (tex.Shiny) { case Shininess.High: shiny = 1.00f; break; case Shininess.Medium: shiny = 0.66f; break; case Shininess.Low: shiny = 0.33f; break; } obj.AppendFormat("g face{0}-{1}{2}", primNr, face.ID, Environment.NewLine); mtl.AppendLine("newmtl " + mtlName); mtl.AppendFormat("Ka {0} {1} {2}{3}", tex.RGBA.R, tex.RGBA.G, tex.RGBA.B, Environment.NewLine); mtl.AppendFormat("Kd {0} {1} {2}{3}", tex.RGBA.R, tex.RGBA.G, tex.RGBA.B, Environment.NewLine); //mtl.AppendFormat("Ks {0} {1} {2}{3}"); mtl.AppendLine("Tr " + tex.RGBA.A); mtl.AppendLine("Ns " + shiny); mtl.AppendLine("illum 1"); if (tex.TextureID != UUID.Zero && tex.TextureID != Primitive.TextureEntry.WHITE_TEXTURE) mtl.AppendLine("map_Kd ./" + texName); mtl.AppendLine(); // Write the vertices, texture coordinates, and vertex normals for this side for (int k = 0; k < face.Vertices.Count; k++) { Vertex vertex = face.Vertices[k]; #region Vertex Vector3 pos = vertex.Position; // Apply scaling pos *= mesh.Prim.Scale; // Apply rotation pos *= mesh.Prim.Rotation; // The root prim position is sim-relative, while child prim positions are // parent-relative. We want to apply parent-relative translations but not // sim-relative ones if (mesh.Prim.ParentID != 0) pos += mesh.Prim.Position; obj.AppendFormat("v {0} {1} {2}{3}", pos.X, pos.Y, pos.Z, Environment.NewLine); #endregion Vertex #region Texture Coord obj.AppendFormat("vt {0} {1}{2}", vertex.TexCoord.X, vertex.TexCoord.Y, Environment.NewLine); #endregion Texture Coord #region Vertex Normal // HACK: Sometimes normals are getting set to if (!Single.IsNaN(vertex.Normal.X) && !Single.IsNaN(vertex.Normal.Y) && !Single.IsNaN(vertex.Normal.Z)) obj.AppendFormat("vn {0} {1} {2}{3}", vertex.Normal.X, vertex.Normal.Y, vertex.Normal.Z, Environment.NewLine); else obj.AppendLine("vn 0.0 1.0 0.0"); #endregion Vertex Normal } obj.AppendFormat("# {0} vertices{1}", face.Vertices.Count, Environment.NewLine); obj.AppendLine(); obj.AppendLine("usemtl " + mtlName); #region Elements // Write all of the faces (triangles) for this side for (int k = 0; k < face.Indices.Count / 3; k++) { obj.AppendFormat("f -{0}/-{0}/-{0} -{1}/-{1}/-{1} -{2}/-{2}/-{2}{3}", face.Vertices.Count - face.Indices[k * 3 + 0], face.Vertices.Count - face.Indices[k * 3 + 1], face.Vertices.Count - face.Indices[k * 3 + 2], Environment.NewLine); } obj.AppendFormat("# {0} elements{1}", face.Indices.Count / 3, Environment.NewLine); obj.AppendLine(); #endregion Elements } } primNr++; } try { File.WriteAllText(filename, obj.ToString()); File.WriteAllText(mtlFilename, mtl.ToString()); } catch (Exception) { return false; } return true; } } public static class Math3D { // Column-major: // | 0 4 8 12 | // | 1 5 9 13 | // | 2 6 10 14 | // | 3 7 11 15 | public static float[] CreateTranslationMatrix(Vector3 v) { float[] mat = new float[16]; mat[12] = v.X; mat[13] = v.Y; mat[14] = v.Z; mat[0] = mat[5] = mat[10] = mat[15] = 1; return mat; } public static float[] CreateRotationMatrix(Quaternion q) { float[] mat = new float[16]; // Transpose the quaternion (don't ask me why) q.X = q.X * -1f; q.Y = q.Y * -1f; q.Z = q.Z * -1f; float x2 = q.X + q.X; float y2 = q.Y + q.Y; float z2 = q.Z + q.Z; float xx = q.X * x2; float xy = q.X * y2; float xz = q.X * z2; float yy = q.Y * y2; float yz = q.Y * z2; float zz = q.Z * z2; float wx = q.W * x2; float wy = q.W * y2; float wz = q.W * z2; mat[0] = 1.0f - (yy + zz); mat[1] = xy - wz; mat[2] = xz + wy; mat[3] = 0.0f; mat[4] = xy + wz; mat[5] = 1.0f - (xx + zz); mat[6] = yz - wx; mat[7] = 0.0f; mat[8] = xz - wy; mat[9] = yz + wx; mat[10] = 1.0f - (xx + yy); mat[11] = 0.0f; mat[12] = 0.0f; mat[13] = 0.0f; mat[14] = 0.0f; mat[15] = 1.0f; return mat; } public static float[] CreateScaleMatrix(Vector3 v) { float[] mat = new float[16]; mat[0] = v.X; mat[5] = v.Y; mat[10] = v.Z; mat[15] = 1; return mat; } public static bool GluProject(OpenTK.Vector3 objPos, OpenTK.Matrix4 modelMatrix, OpenTK.Matrix4 projMatrix, int[] viewport, out OpenTK.Vector3 screenPos) { OpenTK.Vector4 _in; OpenTK.Vector4 _out; _in.X = objPos.X; _in.Y = objPos.Y; _in.Z = objPos.Z; _in.W = 1.0f; _out = OpenTK.Vector4.Transform(_in, modelMatrix); _in = OpenTK.Vector4.Transform(_out, projMatrix); if (_in.W <= 0.0) { screenPos = OpenTK.Vector3.Zero; return false; } _in.X /= _in.W; _in.Y /= _in.W; _in.Z /= _in.W; /* Map x, y and z to range 0-1 */ _in.X = _in.X * 0.5f + 0.5f; _in.Y = _in.Y * 0.5f + 0.5f; _in.Z = _in.Z * 0.5f + 0.5f; /* Map x,y to viewport */ _in.X = _in.X * viewport[2] + viewport[0]; _in.Y = _in.Y * viewport[3] + viewport[1]; screenPos.X = _in.X; screenPos.Y = _in.Y; screenPos.Z = _in.Z; return true; } } public class attachment_point { public string name; public string joint; public Vector3 position; public Quaternion rotation; public int id; public int group; public GLMesh jointmesh; public int jointmeshindex; } /// /// Subclass of LindenMesh that adds vertex, index, and texture coordinate /// arrays suitable for pushing direct to OpenGL /// public class GLMesh : LindenMesh { /// /// Subclass of LODMesh that adds an index array suitable for pushing /// direct to OpenGL /// /// public int teFaceID; new public class LODMesh : LindenMesh.LODMesh { public ushort[] Indices; public override void LoadMesh(string filename) { base.LoadMesh(filename); // Generate the index array Indices = new ushort[_numFaces * 3]; int current = 0; for (int i = 0; i < _numFaces; i++) { Indices[current++] = (ushort)_faces[i].Indices[0]; Indices[current++] = (ushort)_faces[i].Indices[1]; Indices[current++] = (ushort)_faces[i].Indices[2]; } } } /// /// /// public struct GLData { public float[] Vertices; public ushort[] Indices; public float[] TexCoords; public Vector3 Center; } public static GLData baseRenderData; public GLData RenderData; public GLMesh(string name) : base(name) { } public void setMeshPos(Vector3 pos) { _position = pos; } public void setMeshRot(Vector3 rot) { _rotationAngles = rot; } public override void LoadMesh(string filename) { base.LoadMesh(filename); float minX, minY, minZ; minX = minY = minZ = Single.MaxValue; float maxX, maxY, maxZ; maxX = maxY = maxZ = Single.MinValue; // Generate the vertex array RenderData.Vertices = new float[_numVertices * 3]; int current = 0; for (int i = 0; i < _numVertices; i++) { RenderData.Vertices[current++] = _vertices[i].Coord.X; RenderData.Vertices[current++] = _vertices[i].Coord.Y; RenderData.Vertices[current++] = _vertices[i].Coord.Z; if (_vertices[i].Coord.X < minX) minX = _vertices[i].Coord.X; else if (_vertices[i].Coord.X > maxX) maxX = _vertices[i].Coord.X; if (_vertices[i].Coord.Y < minY) minY = _vertices[i].Coord.Y; else if (_vertices[i].Coord.Y > maxY) maxY = _vertices[i].Coord.Y; if (_vertices[i].Coord.Z < minZ) minZ = _vertices[i].Coord.Z; else if (_vertices[i].Coord.Z > maxZ) maxZ = _vertices[i].Coord.Z; } // Calculate the center-point from the bounding box edges RenderData.Center = new Vector3((minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2); // Generate the index array RenderData.Indices = new ushort[_numFaces * 3]; current = 0; for (int i = 0; i < _numFaces; i++) { RenderData.Indices[current++] = (ushort)_faces[i].Indices[0]; RenderData.Indices[current++] = (ushort)_faces[i].Indices[1]; RenderData.Indices[current++] = (ushort)_faces[i].Indices[2]; } // Generate the texcoord array RenderData.TexCoords = new float[_numVertices * 2]; current = 0; for (int i = 0; i < _numVertices; i++) { RenderData.TexCoords[current++] = _vertices[i].TexCoord.X; RenderData.TexCoords[current++] = _vertices[i].TexCoord.Y; } } public override void LoadLODMesh(int level, string filename) { LODMesh lod = new LODMesh(); lod.LoadMesh(filename); _lodMeshes[level] = lod; } } public class GLAvatar { public static Dictionary _meshes = new Dictionary(); public static bool _wireframe = true; public static bool _showSkirt = false; public static Dictionary attachment_points = new Dictionary(); public static void loadlindenmeshes(string LODfilename) { Bone.loadbones("avatar_skeleton.xml"); string basedir = Directory.GetCurrentDirectory() + System.IO.Path.DirectorySeparatorChar + "character" + System.IO.Path.DirectorySeparatorChar; // Parse through avatar_lad.xml to find all of the mesh references XmlDocument lad = new XmlDocument(); lad.Load(basedir + LODfilename); attachment_points.Clear(); XmlNodeList attach_points = lad.GetElementsByTagName("attachment_point"); foreach (XmlNode apoint in attach_points) { attachment_point point = new attachment_point(); point.name = apoint.Attributes.GetNamedItem("name").Value; point.joint = apoint.Attributes.GetNamedItem("joint").Value; string pos = apoint.Attributes.GetNamedItem("position").Value; string[] posparts = pos.Split(' '); point.position = new Vector3(float.Parse(posparts[0]), float.Parse(posparts[1]), float.Parse(posparts[2])); string rot = apoint.Attributes.GetNamedItem("rotation").Value; string[] rotparts = rot.Split(' '); point.rotation = Quaternion.CreateFromEulers((float)(float.Parse(rotparts[0]) * Math.PI / 180f), (float)(float.Parse(rotparts[1]) * Math.PI / 180f), (float)(float.Parse(rotparts[2]) * Math.PI / 180f)); point.id = Int32.Parse(apoint.Attributes.GetNamedItem("id").Value); point.group = Int32.Parse(apoint.Attributes.GetNamedItem("group").Value); attachment_points.Add(point.id, point); } XmlNodeList bones = lad.GetElementsByTagName("bone"); XmlNodeList meshes = lad.GetElementsByTagName("mesh"); foreach (XmlNode meshNode in meshes) { string type = meshNode.Attributes.GetNamedItem("type").Value; int lod = Int32.Parse(meshNode.Attributes.GetNamedItem("lod").Value); string fileName = meshNode.Attributes.GetNamedItem("file_name").Value; //string minPixelWidth = meshNode.Attributes.GetNamedItem("min_pixel_width").Value; GLMesh mesh = (_meshes.ContainsKey(type) ? _meshes[type] : new GLMesh(type)); switch (mesh.Name) { case "lowerBodyMesh": mesh.teFaceID = (int)AvatarTextureIndex.LowerBaked; break; case "upperBodyMesh": mesh.teFaceID = (int)AvatarTextureIndex.UpperBaked; break; case "headMesh": mesh.teFaceID = (int)AvatarTextureIndex.HeadBaked; break; case "hairMesh": mesh.teFaceID = (int)AvatarTextureIndex.HairBaked; break; case "eyelashMesh": mesh.teFaceID = (int)AvatarTextureIndex.HeadBaked; break; case "eyeBallRightMesh": mesh.setMeshPos(Bone.getOffset("mEyeLeft")); //mesh.setMeshRot(Bone.getRotation("mEyeLeft")); mesh.teFaceID = (int)AvatarTextureIndex.EyesBaked; break; case "eyeBallLeftMesh": mesh.setMeshPos(Bone.getOffset("mEyeRight")); //mesh.setMeshRot(Bone.getRotation("mEyeRight")); mesh.teFaceID = (int)AvatarTextureIndex.EyesBaked; break; case "skirtMesh": mesh.teFaceID = (int)AvatarTextureIndex.SkirtBaked; break; default: mesh.teFaceID = 0; break; } if (lod == 0) { mesh.LoadMesh(basedir + fileName); } else { mesh.LoadLODMesh(lod, basedir + fileName); } _meshes[type] = mesh; } } } class RenderAvatar { public GLAvatar glavatar; public Avatar avatar; public FaceData[] data = new FaceData[32]; } public class Bone { public string name; public Vector3 pos; //public Vector3 rot; public Quaternion rot; public Vector3 scale; public Vector3 piviot; public Bone parent; public static Dictionary mBones = new Dictionary(); public static void loadbones(string skeletonfilename) { mBones.Clear(); string basedir = Directory.GetCurrentDirectory() + System.IO.Path.DirectorySeparatorChar + "character" + System.IO.Path.DirectorySeparatorChar; XmlDocument skeleton = new XmlDocument(); skeleton.Load(basedir + skeletonfilename); XmlNode boneslist = skeleton.GetElementsByTagName("linden_skeleton")[0]; addbone(boneslist.ChildNodes[0], null); } public static void addbone(XmlNode bone, Bone parent) { Bone b = new Bone(); b.name = bone.Attributes.GetNamedItem("name").Value; string pos = bone.Attributes.GetNamedItem("pos").Value; string[] posparts = pos.Split(' '); b.pos = new Vector3(float.Parse(posparts[0]), float.Parse(posparts[1]), float.Parse(posparts[2])); string rot = bone.Attributes.GetNamedItem("rot").Value; string[] rotparts = pos.Split(' '); b.rot = Quaternion.CreateFromEulers((float)(float.Parse(rotparts[0]) * Math.PI / 180f), (float)(float.Parse(rotparts[1]) * Math.PI / 180f), (float)(float.Parse(rotparts[2]) * Math.PI / 180f)); string scale = bone.Attributes.GetNamedItem("scale").Value; string[] scaleparts = pos.Split(' '); b.scale = new Vector3(float.Parse(scaleparts[0]), float.Parse(scaleparts[1]), float.Parse(scaleparts[2])); //TODO piviot b.parent = parent; mBones.Add(b.name, b); Logger.Log("Found bone " + b.name, Helpers.LogLevel.Info); foreach (XmlNode childbone in bone.ChildNodes) { addbone(childbone, b); } } //TODO check offset and rot calcuations should each offset be multiplied by its parent rotation in // a standard child/parent rot/offset way? public static Vector3 getOffset(string bonename) { Bone b; if (mBones.TryGetValue(bonename, out b)) { return (b.getOffset()); } else { return Vector3.Zero; } } public Vector3 getOffset() { Vector3 totalpos = pos; if (parent != null) { totalpos = parent.getOffset() + pos; } return totalpos; } public static Quaternion getRotation(string bonename) { Bone b; if (mBones.TryGetValue(bonename, out b)) { return (b.getRotation()); } else { return Quaternion.Identity; } } public Quaternion getRotation() { Quaternion totalrot = rot; if (parent != null) { totalrot = parent.getRotation() * rot; } return totalrot; } } }