Beispiel #1
0
        /// <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);
        }
Beispiel #2
0
        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);
        }
Beispiel #3
0
        /// <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);
        }
Beispiel #4
0
        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);
        }
Beispiel #5
0
        /// <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);
        }
Beispiel #6
0
        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);
        }