/// <summary> /// /// </summary> /// <param name="n"></param> /// <param name="id"></param> /// <returns></returns> public IOMesh LoadGeometryFromID(Node n, string id, List <IOEnvelope> vertexEnvelopes = null) { // sanitize id = ColladaHelper.SanitizeID(id); // find geometry by id var geom = _collada.Library_Geometries.Geometry.First(e => e.ID == id); // not found if (geom == null) { return(null); } // create new mesh IOMesh mesh = new IOMesh() { Name = n.Name }; // create source manager helper SourceManager srcs = new SourceManager(); if (geom.Mesh.Source != null) { foreach (var src in geom.Mesh.Source) { srcs.AddSource(src); } } // load geomtry meshes if (geom.Mesh.Triangles != null) { foreach (var tri in geom.Mesh.Triangles) { var stride = tri.Input.Max(e => e.Offset) + 1; var poly = new IOPolygon() { PrimitiveType = IOPrimitive.TRIANGLE, MaterialName = tri.Material }; var p = tri.P.GetValues(); for (int i = 0; i < tri.Count * 3; i++) { IOVertex vertex = new IOVertex(); for (int j = 0; j < tri.Input.Length; j++) { var input = tri.Input[j]; var index = p[i * stride + input.Offset]; ProcessInput(input.Semantic, input.source, input.Set, vertex, geom.Mesh.Vertices, index, srcs, vertexEnvelopes); } poly.Indicies.Add(mesh.Vertices.Count); mesh.Vertices.Add(vertex); } mesh.Polygons.Add(poly); } } //TODO: collada trifan //TODO: collada tristrip //TODO: collada linestrip //TODO: collada polylist //TODO: collada polygon return(mesh); }
public override IOModel ImportFromFile(string filename) { var model = new IOModel(); var materials = new List <string>(); var positions = new List <Vector3>(); var textures = new Dictionary <int, Texture>(); using (var reader = new StreamReader(File.OpenRead(filename))) { var line = reader.ReadLine(); if (line.Trim() != "Metasequoia Document") { logger.Error("Not a valid Metasequoia file"); return(null); } while (!reader.EndOfStream) { line = reader.ReadLine().Trim(); if (line == "" || line == "}") { continue; } // Parse chunks var chunk = line.Split(' ')[0]; if (chunk == "Format") { } else if (chunk == "Thumbnail") { IgnoreChunk(reader); } else if (chunk == "Scene") { IgnoreChunk(reader); } else if (chunk == "Material") { var numMaterials = int.Parse(line.Split(' ')[1]); if (numMaterials == 0) { return(null); } for (var i = 0; i < numMaterials; i++) { var materialString = reader.ReadLine().Trim(); var tokensMaterial = materialString.Split(' '); var material = new IOMaterial(tokensMaterial[0]); for (var j = 0; j < tokensMaterial.Length; j++) { var texturePath = ""; if (tokensMaterial[j].StartsWith("tex")) { texturePath = tokensMaterial[j].Substring(5, tokensMaterial[j].Length - 7); if (texturePath != "") { string basePath = Path.GetDirectoryName(filename); if (!File.Exists(Path.Combine(basePath, texturePath))) { basePath = Path.Combine(Path.GetDirectoryName(filename), "Texture"); } if (!File.Exists(Path.Combine(basePath, texturePath))) { throw new FileNotFoundException("Texture " + texturePath + " could not be found"); } textures.Add(i, GetTexture(basePath, texturePath)); } material.Texture = textures[i]; break; } } model.Materials.Add(material); } } else if (chunk == "Object") { var name = line.Split(' ')[1]; var mesh = new IOMesh(name.Replace("\"", "")); var tokensName = mesh.Name.Split('_'); positions = new List <Vector3>(); if (name.Contains("TeRoom_")) { model.HasMultipleRooms = true; } var lastVertex = 0; var translation = Vector3.Zero; while (!reader.EndOfStream) { line = reader.ReadLine().Trim(); var tokens = line.Split(' '); if (tokens[0] == "translation" && model.HasMultipleRooms) { translation = ApplyAxesTransforms(new Vector3(ParseFloatCultureInvariant(tokens[1]), ParseFloatCultureInvariant(tokens[2]), ParseFloatCultureInvariant(tokens[3]))); } else if (tokens[0] == "vertex") { var numVertices = int.Parse(tokens[1]); for (var i = 0; i < numVertices; i++) { var tokensPosition = reader.ReadLine().Trim().Split(' '); var newPos = ApplyAxesTransforms(new Vector3(ParseFloatCultureInvariant(tokensPosition[0]), ParseFloatCultureInvariant(tokensPosition[1]), ParseFloatCultureInvariant(tokensPosition[2])) ); positions.Add(newPos); } line = reader.ReadLine().Trim(); } else if (tokens[0] == "face") { var numFaces = int.Parse(tokens[1]); for (var i = 0; i < numFaces; i++) { line = reader.ReadLine().Trim(); var numVerticesInFace = int.Parse(line.Substring(0, line.IndexOf(' '))); var poly = new IOPolygon(numVerticesInFace == 3 ? IOPolygonShape.Triangle : IOPolygonShape.Quad); // We MUST have vertices var stringVertices = GetSubBlock(line, "V"); if (stringVertices == "") { return(null); } var tokensVertices = stringVertices.Split(' '); for (var k = 0; k < numVerticesInFace; k++) { var index = int.Parse(tokensVertices[k]); mesh.Positions.Add(positions[index]); poly.Indices.Add(lastVertex); lastVertex++; } // Change vertex winding if (_settings.InvertFaces) { poly.Indices.Reverse(); } // UV var stringUV = GetSubBlock(line, "UV"); if (stringUV != "") { var tokensUV = stringUV.Split(' '); for (var k = 0; k < numVerticesInFace; k++) { var uv = ApplyUVTransform(new Vector2(ParseFloatCultureInvariant(tokensUV[2 * k]), ParseFloatCultureInvariant(tokensUV[2 * k + 1])), textures[0].Image.Width, textures[0].Image.Height); mesh.UV.Add(uv); } } // Colors var stringColor = GetSubBlock(line, "COL"); if (stringColor != "") { var tokensColor = stringColor.Split(' '); for (var k = 0; k < numVerticesInFace; k++) { var color = ApplyColorTransform(GetColor(long.Parse(tokensColor[k]))); mesh.Colors.Add(color); } } else { for (var k = 0; k < numVerticesInFace; k++) { var color = ApplyColorTransform(Vector4.One); mesh.Colors.Add(color); } } // Material index var stringMaterialIndex = GetSubBlock(line, "M"); var materialIndex = 0; if (stringMaterialIndex != "") { materialIndex = int.Parse(stringMaterialIndex); } // Add polygon to the submesh (and add submesh if not existing yet) var material = model.Materials[materialIndex]; if (!mesh.Submeshes.ContainsKey(material)) { mesh.Submeshes.Add(material, new IOSubmesh(material)); } mesh.Submeshes[material].Polygons.Add(poly); } line = reader.ReadLine().Trim(); } else if (tokens[0] == "vertexattr") { // section to ignore IgnoreChunk(reader); } else if (tokens[0] == "}") { break; } } model.Meshes.Add(mesh); } } } CalculateNormals(model); return(model); }
/// <summary> /// /// </summary> private void ProcessGeometry(FbxNode node, out IOPolygon poly, out List <IOVertex> verts) { double[] vertices; int[] indices; // load vertices if (node["Vertices"].Value is double[]) { vertices = (double[])node["Vertices"].Value; } else { vertices = node["Vertices"].Properties.Select(e => (double)e).ToArray(); } // load vertex indices if (node["PolygonVertexIndex"].Value is int[]) { indices = (int[])node["PolygonVertexIndex"].Value; } else { indices = node["PolygonVertexIndex"].Properties.Select(e => (int)e).ToArray(); } // get binds var deformers = GetChildConnections(node).Where(e => e.Name == "Deformer"); List <Dictionary <int, Tuple <float, string> > > deforms = new List <Dictionary <int, Tuple <float, string> > >(); foreach (var par in deformers) { foreach (var sub in GetChildConnections(par)) { // get bone name var name = ""; foreach (var mod in GetChildConnections(sub)) { if (mod.Name == "Model") { name = GetNameWithoutNamespace(mod.Properties[NodeDescSize - 2].ToString()); } } if (!string.IsNullOrEmpty(name) && sub["Indexes"] != null) { // create deform map Dictionary <int, Tuple <float, string> > deformmap = new Dictionary <int, Tuple <float, string> >(); // indices int[] ind; if (sub["Indexes"].Value is int[]) { ind = (int[])sub["Indexes"].Value; } else { ind = sub["Indexes"].Properties.Select(e => (int)e).ToArray(); } // weights float[] weights; if (sub["Weights"].Value is double[]) { weights = ((double[])sub["Weights"].Value).Select(e => (float)e).ToArray(); } else { weights = sub["Weights"].Properties.Select(e => (float)(double)e).ToArray(); } // generate map for (int i = 0; i < weights.Length; i++) { deformmap.Add(ind[i], new Tuple <float, string>(weights[i], name)); } // add deform entry deforms.Add(deformmap); } } } // generate polygon poly = new IOPolygon(); verts = new List <IOVertex>(); poly.PrimitiveType = IOPrimitive.TRIANGLE; // process primitives and convert to triangles var primLength = 0; for (int i = 0; i < indices.Length; i++) { var idx1 = indices[i]; primLength++; if (idx1 < 0) { switch (primLength) { case 4: // convert quad to triangle poly.Indicies.Add(i - 3); poly.Indicies.Add(i - 2); poly.Indicies.Add(i - 1); poly.Indicies.Add(i - 3); poly.Indicies.Add(i - 1); poly.Indicies.Add(i); break; case 3: // triangle poly.Indicies.Add(i - 2); poly.Indicies.Add(i - 1); poly.Indicies.Add(i); break; default: // tri strip for (var vi = i - primLength; vi < i - 2; vi++) { if ((vi & 1) != 0) { poly.Indicies.Add(vi); poly.Indicies.Add(vi + 1); poly.Indicies.Add(vi + 2); } else { poly.Indicies.Add(vi); poly.Indicies.Add(vi + 2); poly.Indicies.Add(vi + 1); } } break; } idx1 = Math.Abs(idx1) - 1; // xor -1 primLength = 0; } IOVertex v = new IOVertex() { Position = new Vector3((float)vertices[idx1 * 3], (float)vertices[idx1 * 3 + 1], (float)vertices[idx1 * 3 + 2]) }; // find deforms foreach (var map in deforms) { if (map.ContainsKey(idx1)) { v.Envelope.Weights.Add(new Core.IOBoneWeight() { Weight = map[idx1].Item1, BoneName = map[idx1].Item2, }); } } verts.Add(v); } // get layer information ProcessLayer(node, "Normal", indices, verts); ProcessLayer(node, "Tangent", indices, verts); ProcessLayer(node, "Binormal", indices, verts); ProcessLayer(node, "UV", indices, verts); ProcessLayer(node, "Color", indices, verts); }
public override IOScene GetIOModel() { IOScene scene = new IOScene(); IOModel iomodel = new IOModel(); scene.Models.Add(iomodel); iomodel.Skeleton = ((SBSkeleton)Skeleton).ToIOSkeleton(); // bone indices List <SBHsdBone> bones = new List <SBHsdBone>(); foreach (SBHsdBone bone in Skeleton.Bones) { bones.Add(bone); } // gather textures Dictionary <HSDStruct, string> tobjToName = new Dictionary <HSDStruct, string>(); List <SBSurface> textures = new List <SBSurface>(); foreach (var tex in tobjToSurface) { tex.Value.Name = $"TOBJ_{textures.Count}"; tobjToName.Add(tex.Key, tex.Value.Name); textures.Add(tex.Value); } // process mesh foreach (SBHsdMesh me in GetMeshObjects()) { var dobj = me.DOBJ; var parent = Skeleton.Bones[0]; foreach (var b in Skeleton.Bones) { if (b is SBHsdBone bone) { if (bone.GetJOBJ().Dobj != null) { if (bone.GetJOBJ().Dobj.List.Contains(dobj)) { parent = b; break; } } } } var iomesh = new IOMesh(); iomesh.Name = me.Name; iomodel.Meshes.Add(iomesh); IOPolygon poly = new IOPolygon(); iomesh.Polygons.Add(poly); if (dobj.Pobj != null) { foreach (var pobj in dobj.Pobj.List) { var dl = pobj.ToDisplayList(); var vertices = GX_VertexAccessor.GetDecodedVertices(dl, pobj); HSD_Envelope[] bindGroups = null;; if (pobj.EnvelopeWeights != null) { bindGroups = pobj.EnvelopeWeights; } var offset = 0; foreach (var v in dl.Primitives) { List <GX_Vertex> strip = new List <GX_Vertex>(); for (int i = 0; i < v.Count; i++) { strip.Add(vertices[offset + i]); } offset += v.Count; iomesh.Vertices.AddRange(ConvertGXDLtoTriangleList(v.PrimitiveType, SBHsdMesh.GXVertexToHsdVertex(strip, bones, bindGroups), (SBHsdBone)parent)); } } } // flip faces for (int i = 0; i < iomesh.Vertices.Count; i += 3) { if (i + 2 < iomesh.Vertices.Count) { poly.Indicies.Add(i + 2); poly.Indicies.Add(i + 1); poly.Indicies.Add(i); } else { break; } } poly.MaterialName = iomesh.Name + "_material"; // create material IOMaterial mat = new IOMaterial(); mat.Name = iomesh.Name + "_material"; if (dobj.Mobj.Material != null) { mat.DiffuseColor = new System.Numerics.Vector4( dobj.Mobj.Material.DiffuseColor.R / 255f, dobj.Mobj.Material.DiffuseColor.G / 255f, dobj.Mobj.Material.DiffuseColor.B / 255f, dobj.Mobj.Material.DiffuseColor.A / 255f); mat.SpecularColor = new System.Numerics.Vector4( dobj.Mobj.Material.SpecularColor.R / 255f, dobj.Mobj.Material.SpecularColor.G / 255f, dobj.Mobj.Material.SpecularColor.B / 255f, dobj.Mobj.Material.SpecularColor.A / 255f); mat.AmbientColor = new System.Numerics.Vector4( dobj.Mobj.Material.AmbientColor.R / 255f, dobj.Mobj.Material.AmbientColor.G / 255f, dobj.Mobj.Material.AmbientColor.B / 255f, dobj.Mobj.Material.AmbientColor.A / 255f); mat.Alpha = dobj.Mobj.Material.Alpha; mat.Shininess = dobj.Mobj.Material.Shininess; } if (dobj.Mobj.Textures != null) { mat.DiffuseMap = new IOTexture() { Name = tobjToName[dobj.Mobj.Textures._s], FilePath = tobjToName[dobj.Mobj.Textures._s] } } ; scene.Materials.Add(mat); } return(scene); }
/// <summary> /// /// </summary> /// <param name="filePath"></param> /// <returns></returns> public IOScene GetScene(string filePath) { // create scene and model IOScene scene = new IOScene(); // load materials var mtlFile = Path.Combine(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath) + ".mtl"); if (File.Exists(mtlFile)) { LoadMaterialLibrary(scene, mtlFile); } // parse obj file using (FileStream stream = new FileStream(filePath, FileMode.Open)) using (StreamReader r = new StreamReader(stream)) { List <Vector3> v = new List <Vector3>(); List <Vector2> vt = new List <Vector2>(); List <Vector3> vn = new List <Vector3>(); List <Tuple <string, string, Dictionary <IOPrimitive, List <int[]> > > > objects = new List <Tuple <string, string, Dictionary <IOPrimitive, List <int[]> > > >(); var objName = "Mesh"; var matNam = ""; Dictionary <IOPrimitive, List <int[]> > polygons = new Dictionary <IOPrimitive, List <int[]> >(); while (!r.EndOfStream) { var args = Regex.Replace(r.ReadLine().Trim(), @"\s+", " ").Split(' '); if (args.Length > 0) { switch (args[0]) { case "v": v.Add(new Vector3( args.Length > 1 ? float.Parse(args[1]) : 0, args.Length > 2 ? float.Parse(args[2]) : 0, args.Length > 3 ? float.Parse(args[3]) : 0)); break; case "vt": vt.Add(new Vector2( args.Length > 1 ? float.Parse(args[1]) : 0, args.Length > 2 ? float.Parse(args[2]) : 0)); break; case "vn": vn.Add(new Vector3( args.Length > 1 ? float.Parse(args[1]) : 0, args.Length > 2 ? float.Parse(args[2]) : 0, args.Length > 3 ? float.Parse(args[3]) : 0)); break; case "f": var faces = ParseFaces(args); if (args.Length == 2) { // point if (!polygons.ContainsKey(IOPrimitive.POINT)) { polygons.Add(IOPrimitive.POINT, new List <int[]>()); } polygons[IOPrimitive.POINT].AddRange(faces); } if (args.Length == 3) { // line if (!polygons.ContainsKey(IOPrimitive.LINE)) { polygons.Add(IOPrimitive.LINE, new List <int[]>()); } polygons[IOPrimitive.LINE].AddRange(faces); } if (args.Length == 4) { // triangle if (!polygons.ContainsKey(IOPrimitive.TRIANGLE)) { polygons.Add(IOPrimitive.TRIANGLE, new List <int[]>()); } polygons[IOPrimitive.TRIANGLE].AddRange(faces); } if (args.Length == 5) { // quad if (!polygons.ContainsKey(IOPrimitive.QUAD)) { polygons.Add(IOPrimitive.QUAD, new List <int[]>()); } polygons[IOPrimitive.QUAD].AddRange(faces); } if (args.Length == 6) { // strip if (!polygons.ContainsKey(IOPrimitive.TRISTRIP)) { polygons.Add(IOPrimitive.TRISTRIP, new List <int[]>()); } polygons[IOPrimitive.TRISTRIP].AddRange(faces); } break; case "o": if (polygons.Count > 0) { objects.Add(new Tuple <string, string, Dictionary <IOPrimitive, List <int[]> > >(objName, matNam, polygons)); } objName = args[1]; matNam = ""; polygons = new Dictionary <IOPrimitive, List <int[]> >(); break; case "usemtl": matNam = args[1]; break; } } } objects.Add(new Tuple <string, string, Dictionary <IOPrimitive, List <int[]> > >(objName, matNam, polygons)); // generate model IOModel model = new IOModel(); scene.Models.Add(model); // dummy bone model.Skeleton.RootBones.Add(new Core.Skeleton.IOBone() { Name = "Root" }); // convert and add polygons foreach (var obj in objects) { IOMesh mesh = new IOMesh() { Name = obj.Item1 }; foreach (var p in obj.Item3) { IOPolygon poly = new IOPolygon() { PrimitiveType = p.Key, MaterialName = obj.Item2, }; for (int i = 0; i < p.Value.Count; i++) { var face = p.Value[i]; IOVertex vert = new IOVertex() { Position = face[0] != -1 ? v[face[0]] : Vector3.Zero, Normal = face[2] != -1 ? vn[face[2]] : Vector3.Zero, }; if (face[1] != -1) { vert.UVs.Add(vt[face[1]]); } poly.Indicies.Add(mesh.Vertices.Count); mesh.Vertices.Add(vert); } mesh.Polygons.Add(poly); } // add mesh to model model.Meshes.Add(mesh); } ; } return(scene); }
public override IOModel ImportFromFile(string filename) { string path = Path.GetDirectoryName(filename); // Use Assimp.NET for importing model AssimpContext context = new AssimpContext(); context.SetConfig(new NormalSmoothingAngleConfig(90.0f)); Scene scene = context.ImportFile(filename, PostProcessPreset.TargetRealTimeMaximumQuality); var newModel = new IOModel(); var textures = new Dictionary <int, Texture>(); if (_settings.ProcessGeometry) { var tmpList = new List <IOMesh>(); // Create the list of materials to load for (int i = 0; i < scene.Materials.Count; i++) { var mat = scene.Materials[i]; var material = new IOMaterial(mat.HasName ? mat.Name : "Material_" + i); var diffusePath = mat.HasTextureDiffuse ? mat.TextureDiffuse.FilePath : null; if (string.IsNullOrWhiteSpace(diffusePath)) { continue; } // Don't add materials with missing textures var texture = GetTexture(path, diffusePath); if (texture == null) { logger.Warn("Texture for material " + mat.Name + " is missing. Meshes referencing this material won't be imported."); continue; } else { textures.Add(i, texture); } // Create the new material material.Texture = textures[i]; material.AdditiveBlending = (mat.HasBlendMode && mat.BlendMode == global::Assimp.BlendMode.Additive) || mat.Opacity < 1.0f; material.DoubleSided = mat.HasTwoSided && mat.IsTwoSided; material.Shininess = mat.HasShininess ? (int)mat.Shininess : 0; newModel.Materials.Add(material); } var lastBaseVertex = 0; // Loop for each mesh loaded in scene foreach (var mesh in scene.Meshes) { // Discard nullmeshes if (!mesh.HasFaces || !mesh.HasVertices || mesh.VertexCount < 3 || mesh.TextureCoordinateChannelCount == 0 || !mesh.HasTextureCoords(0)) { logger.Warn("Mesh \"" + (mesh.Name ?? "") + "\" has no faces, no texture coordinates or wrong vertex count."); continue; } // Import only textured meshes with valid materials Texture faceTexture; if (!textures.TryGetValue(mesh.MaterialIndex, out faceTexture)) { logger.Warn("Mesh \"" + (mesh.Name ?? "") + "\" does have material index " + mesh.MaterialIndex + " which is unsupported or can't be found."); continue; } // Make sure we have appropriate material in list. If not, skip mesh and warn user. var material = newModel.Materials.FirstOrDefault(mat => mat.Name.Equals(scene.Materials[mesh.MaterialIndex].Name)); if (material == null) { logger.Warn("Can't find material with specified index (" + mesh.MaterialIndex + "). Probably you're missing textures or using non-diffuse materials only for this mesh."); continue; } // Assimp's mesh is our IOSubmesh so we import meshes with just one submesh var newMesh = new IOMesh(mesh.Name); var newSubmesh = new IOSubmesh(material); newMesh.Submeshes.Add(material, newSubmesh); bool hasColors = _settings.UseVertexColor && mesh.VertexColorChannelCount > 0 && mesh.HasVertexColors(0); bool hasNormals = mesh.HasNormals; // Additional integrity checks if ((mesh.VertexCount != mesh.TextureCoordinateChannels[0].Count) || (hasColors && mesh.VertexCount != mesh.VertexColorChannels[0].Count) || (hasNormals && mesh.VertexCount != mesh.Normals.Count)) { logger.Warn("Mesh \"" + (mesh.Name ?? "") + "\" data structure is inconsistent."); continue; } // Source data var positions = mesh.Vertices; var normals = mesh.Normals; var texCoords = mesh.TextureCoordinateChannels[0]; var colors = mesh.VertexColorChannels[0]; for (int i = 0; i < mesh.VertexCount; i++) { // Create position var position = new Vector3(positions[i].X, positions[i].Y, positions[i].Z); position = ApplyAxesTransforms(position); newMesh.Positions.Add(position); // Create normal if (hasNormals) { var normal = new Vector3(normals[i].X, normals[i].Y, normals[i].Z); normal = ApplyAxesTransforms(normal); newMesh.Normals.Add(normal); } else { newMesh.CalculateNormals(); } // Create UV var currentUV = new Vector2(texCoords[i].X, texCoords[i].Y); if (faceTexture != null) { currentUV = ApplyUVTransform(currentUV, faceTexture.Image.Width, faceTexture.Image.Height); } newMesh.UV.Add(currentUV); // Create colors if (hasColors) { var color = ApplyColorTransform(new Vector4(colors[i].R, colors[i].G, colors[i].B, colors[i].A)); newMesh.Colors.Add(color); } } // Add polygons foreach (var face in mesh.Faces) { if (face.IndexCount == 3) { var poly = new IOPolygon(IOPolygonShape.Triangle); poly.Indices.Add(lastBaseVertex + face.Indices[0]); poly.Indices.Add(lastBaseVertex + face.Indices[1]); poly.Indices.Add(lastBaseVertex + face.Indices[2]); if (_settings.InvertFaces) { poly.Indices.Reverse(); } newSubmesh.Polygons.Add(poly); } else if (face.IndexCount == 4) { var poly = new IOPolygon(IOPolygonShape.Quad); poly.Indices.Add(lastBaseVertex + face.Indices[0]); poly.Indices.Add(lastBaseVertex + face.Indices[1]); poly.Indices.Add(lastBaseVertex + face.Indices[2]); poly.Indices.Add(lastBaseVertex + face.Indices[3]); if (_settings.InvertFaces) { poly.Indices.Reverse(); } newSubmesh.Polygons.Add(poly); } } tmpList.Add(newMesh); } // Sort meshes by name, if specified if (_settings.SortByName) { tmpList = tmpList.OrderBy(m => m.Name, new CustomComparer <string>(NaturalComparer.Do)).ToList(); } foreach (var mesh in tmpList) { newModel.Meshes.Add(mesh); } } if (_settings.ProcessAnimations && scene.HasAnimations && scene.AnimationCount > 0) { // Find all mesh nodes to count against animation nodes var meshNameList = CollectMeshNodeNames(scene.RootNode); // Sort animations by name, if specified if (_settings.SortByName) { meshNameList = meshNameList.OrderBy(s => s, new CustomComparer <string>(NaturalComparer.Do)).ToList(); } // Loop through all animations and add appropriate ones. // Integrity check: there should be meshes and mesh count should be equal to unique mesh name count. if (scene.MeshCount <= 0 || scene.MeshCount != meshNameList.Count) { logger.Warn("Actual number of meshes doesn't correspond to mesh list. Animations won't be imported."); } else { for (int i = 0; i < scene.AnimationCount; i++) { var anim = scene.Animations[i]; // Integrity check: support only time-based node animations if (!anim.HasNodeAnimations || anim.DurationInTicks <= 0) { logger.Warn("Anim " + i + " isn't a valid type of animation for TR formats."); continue; } // Guess possible maximum frame and time var frameCount = 0; double maximumTime = 0; foreach (var node in anim.NodeAnimationChannels) { if (node.HasPositionKeys) { var maxNodeTime = node.PositionKeys.Max(key => key.Time); maximumTime = maximumTime >= maxNodeTime ? maximumTime : maxNodeTime; frameCount = frameCount >= node.PositionKeyCount ? frameCount : node.PositionKeyCount; } if (node.HasRotationKeys) { var maxNodeTime = node.RotationKeys.Max(key => key.Time); maximumTime = maximumTime >= maxNodeTime ? maximumTime : maxNodeTime; frameCount = frameCount >= node.RotationKeyCount ? frameCount : node.RotationKeyCount; } } // Calculate time multiplier var timeMult = (double)(frameCount - 1) / anim.DurationInTicks; // Integrity check: maximum frame time shouldn't excess duration if (timeMult * maximumTime >= frameCount) { logger.Warn("Anim " + i + " has frames outside of time limits and won't be imported."); continue; } IOAnimation ioAnim = new IOAnimation(string.IsNullOrEmpty(anim.Name) ? "Imported animation " + i : anim.Name, scene.MeshCount); // Precreate frames and set them to identity for (int j = 0; j < frameCount; j++) { ioAnim.Frames.Add(new IOFrame()); } // Precreate rotations and set them to identity // I am using generic foreach here instead of linq foreach because for some reason it // returns wrong amount of angles during enumeration with Enumerable.Repeat. foreach (var frame in ioAnim.Frames) { var angleList = Enumerable.Repeat(Vector3.Zero, scene.MeshCount); frame.Angles.AddRange(angleList); } // Search through all nodes and put data into corresponding frames. // It's not clear what should we do in case if multiple nodes refer to same mesh, but sometimes // it happens, e. g. in case of fbx format. In this case, we'll just add to existing values for now. foreach (var chan in anim.NodeAnimationChannels) { // Look if this channel belongs to any mesh in list. // If so, attribute it to appropriate frame. var chanIndex = meshNameList.IndexOf(item => chan.NodeName.Contains(item)); // Integrity check: no appropriate mesh found if (chanIndex < 0) { logger.Warn("Anim " + i + " channel " + chan.NodeName + " has no corresponding mesh in meshtree and will be ignored"); continue; } // Apply translation only if found channel belongs to root mesh. if (chanIndex == 0 && chan.HasPositionKeys && chan.PositionKeyCount > 0) { foreach (var key in chan.PositionKeys) { // Integrity check: frame shouldn't fall out of keyframe array bounds. var frameIndex = (int)Math.Round(key.Time * timeMult, MidpointRounding.AwayFromZero); if (frameIndex >= frameCount) { logger.Warn("Anim " + i + " channel " + chan.NodeName + " has a key outside of time limits and will be ignored."); continue; } float rX = key.Value.X; float rY = key.Value.Y; float rZ = key.Value.Z; if (_settings.SwapXY) { var temp = rX; rX = rY; rY = temp; } if (_settings.SwapXZ) { var temp = rX; rX = rZ; rZ = temp; } if (_settings.SwapYZ) { var temp = rY; rY = rZ; rZ = temp; } if (_settings.FlipX) { rX = -rX; } if (_settings.FlipY) { rY = -rY; } if (_settings.FlipZ) { rZ = -rZ; } ioAnim.Frames[frameIndex].Offset += new Vector3(rX, rY, rZ); } } if (chan.HasRotationKeys && chan.RotationKeyCount > 0) { foreach (var key in chan.RotationKeys) { // Integrity check: frame shouldn't fall out of keyframe array bounds. var frameIndex = (int)Math.Round(key.Time * timeMult, MidpointRounding.AwayFromZero); if (frameIndex >= frameCount) { logger.Warn("Anim " + i + " channel " + chan.NodeName + " has a key outside of time limits and will be ignored."); continue; } // Convert quaternions back to rotations. // This is similar to TRViewer's conversion routine. var quatI = System.Numerics.Quaternion.Identity; var quat = new System.Numerics.Quaternion(key.Value.X, key.Value.Z, key.Value.Y, -key.Value.W); quatI *= quat; var eulers = MathC.QuaternionToEuler(quatI); var rotation = new Vector3(eulers.X * 180.0f / (float)Math.PI, eulers.Y * 180.0f / (float)Math.PI, eulers.Z * 180.0f / (float)Math.PI); ioAnim.Frames[frameIndex].Angles[chanIndex] += MathC.NormalizeAngle(rotation); } } } newModel.Animations.Add(ioAnim); } } } return(newModel); }