Пример #1
0
        private void AlignNormals()
        {
            using (var t = new ScopeTimer("Mesh.AlignNormals"))
            {
                var alignedNormals = new Vector3[Vertices.Count];

                foreach (var tri in Triangles)
                {
                    UpdateNormal(ref alignedNormals, (int)tri.I1, (int)tri.In1);
                    UpdateNormal(ref alignedNormals, (int)tri.I2, (int)tri.In2);
                    UpdateNormal(ref alignedNormals, (int)tri.I3, (int)tri.In3);
                }

                Normals = alignedNormals.ToList();
            }
        }
Пример #2
0
        public bool Load(string filename, bool fromResource)
        {
            Vertices.Clear();
            Normals.Clear();
            Colors.Clear();
            Triangles.Clear();
            UV.Clear();

            try
            {
                using (var t = new ScopeTimer("Mesh.Load"))
                {
                    if (fromResource)
                    {
                        var assembly = typeof(Mesh).GetTypeInfo().Assembly;
                        using (var fileStream = assembly.GetManifestResourceStream(filename))
                        {
                            LoadMeshStream(fileStream);
                        }
                    }
                    else
                    {
                        if (File.Exists(filename) == false)
                        {
                            return(false);
                        }

                        using (var fileStream = File.OpenRead(filename))
                        {
                            LoadMeshStream(fileStream);
                        }
                    }
                }
            }
            catch (Exception exp)
            {
                Debug.WriteLine("Mesh.Load exp: " + exp);
                return(false);
            }

            Debug.WriteLine($"Loaded with {Vertices.Count} Vertices, {Normals.Count} Normals, {Colors.Count} Colors, {Triangles.Count} Triangles");

            return(true);
        }
Пример #3
0
        private static Dictionary <string, Material> ParseMtlFile(string basePath, string mtlFileName)
        {
            Dictionary <string, Material> materials = new Dictionary <string, Material>();

            using (var timer = new ScopeTimer($"Parse MTL File: {mtlFileName}"))
            {
                using (var fileStream = File.OpenRead(Path.Combine(basePath, mtlFileName)))
                {
                    using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, 4096))
                    {
                        try
                        {
                            string currentTextureName = null;
                            string line = null;

                            Material currentMaterial = null;

                            while ((line = streamReader.ReadLine()) != null)
                            {
                                string[] tokens = line.Split(' ');
                                switch (tokens[0])
                                {
                                case "newmtl":
                                    if (tokens.Length != 2)
                                    {
                                        throw new Exception("New material declarations must have exactly 2 tokens.");
                                    }

                                    currentMaterial = new Material();

                                    currentTextureName = tokens[1];
                                    materials.Add(currentTextureName, currentMaterial);

                                    break;

                                case "map_Kd":
                                    if (tokens.Length != 2)
                                    {
                                        throw new Exception("Material path lines must have exactly 2 tokens.");
                                    }

                                    if (currentMaterial == null)
                                    {
                                        throw new Exception("No current material defined.");
                                    }

                                    currentMaterial.TextureFileName = tokens[1];

                                    break;

                                default:
                                    break;
                                }
                            }
                        }
                        catch (Exception e)
                        {
                            Debug.WriteLine($"Failed to parse {mtlFileName}. Exception: {e.Message}");
                        }
                    }
                }
            }

            return(materials);
        }
Пример #4
0
        /// <summary>
        /// Loads an OBJ file and its related textures (if present) into this Mesh object.
        /// </summary>
        /// <param name="path">The full path of the OBJ file.</param>
        /// <returns>True if the OBJ file was successfully parsed, false if something went wrong.</returns>
        public bool Load(string path)
        {
            if (IsLoaded && Filename == path)
            {
                return(true);
            }

            if (!File.Exists(path))
            {
                return(false);
            }

            ClearAll();

            string objFileName = Path.GetFileName(path);
            string basePath    = Path.GetDirectoryName(path);

            float minX, minY, minZ;

            minX = minY = minZ = float.MaxValue;

            float maxX, maxY, maxZ;

            maxX = maxY = maxZ = float.MinValue;

            using (var t = new ScopeTimer($"Mesh.Load: {objFileName}"))
            {
                using (var fileStream = File.OpenRead(Path.Combine(basePath, objFileName)))
                {
                    using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, 4096))
                    {
                        try
                        {
                            string line = null;
                            bool   allIndicesAreAligned = true;

                            Geometry currentGeometry = new Geometry();

                            while ((line = streamReader.ReadLine()) != null)
                            {
                                if (string.IsNullOrWhiteSpace(line) || line.Length < 2)
                                {
                                    continue;
                                }

                                var tokens = line.Split(' ');

                                switch (tokens[0])
                                {
                                // Mtl File Reference
                                case "mtllib":
                                    if (tokens.Length > 2)
                                    {
                                        throw new Exception("MTL file references must have only 2 tokens.");
                                    }

                                    Materials = ParseMtlFile(basePath, tokens[1]);

                                    break;

                                // Texture Reference
                                case "usemtl":
                                    if (tokens.Length > 2)
                                    {
                                        throw new Exception("Texture references must have only 2 tokens.");
                                    }

                                    currentGeometry = new Geometry(tokens[1]);
                                    Geometries.Add(currentGeometry);

                                    break;

                                // Vertex
                                case "v":
                                    if (tokens.Length != 4 && tokens.Length != 7)
                                    {
                                        throw new Exception("Vertices must have either 4 or 7 tokens.");
                                    }

                                    Vector3 newVertex = new Vector3(float.Parse(tokens[1]), float.Parse(tokens[2]), float.Parse(tokens[3]));
                                    newVertex = newVertex.ConvertBetweenCoordinateSystems();
                                    Vertices.Add(newVertex);

                                    minX = Math.Min(minX, newVertex.X);
                                    minY = Math.Min(minY, newVertex.Y);
                                    minZ = Math.Min(minZ, newVertex.Z);

                                    maxX = Math.Max(maxX, newVertex.X);
                                    maxY = Math.Max(maxY, newVertex.Y);
                                    maxZ = Math.Max(maxZ, newVertex.Z);

                                    if (tokens.Length == 7)
                                    {
                                        Colors.Add(new Color(float.Parse(tokens[4]), float.Parse(tokens[5]),
                                                             float.Parse(tokens[6])));
                                    }

                                    break;

                                // Normal
                                case "vn":
                                    if (tokens.Length != 4)
                                    {
                                        throw new Exception("Vertex normals must have exactly 4 tokens.");
                                    }

                                    Vector3 newNormal = new Vector3(float.Parse(tokens[1]), float.Parse(tokens[2]), float.Parse(tokens[3]));
                                    newNormal = newNormal.ConvertBetweenCoordinateSystems();
                                    Normals.Add(newNormal);

                                    break;

                                // Texture Coordinates (UV)
                                case "vt":
                                    if (tokens.Length != 3)
                                    {
                                        throw new Exception("Texture coordinates must have exactly 3 tokens.");
                                    }

                                    UV.Add(new Vector2(float.Parse(tokens[1]), 1.0f - float.Parse(tokens[2])));

                                    break;

                                // Face
                                case "f":
                                    if (tokens.Length != 4)
                                    {
                                        throw new Exception("Every face must have exactly 4 tokens.");
                                    }

                                    // [0] = Vertex Index, [1] = Texture Coordinate (UV), [2] = Normal Index
                                    var v1 = tokens[1].Split('/');
                                    var v2 = tokens[2].Split('/');
                                    var v3 = tokens[3].Split('/');

                                    // Each face token is a triplet, with exactly 3 sub-tokens
                                    if (v1.Length != 3 || v2.Length != 3 || v3.Length != 3)
                                    {
                                        throw new Exception("Every face token must be a triplet with exactly 3 sub-tokens.");
                                    }

                                    // OBJ indexing starts at 1, so subtract 1 for each to get correct list index
                                    uint i1  = uint.Parse(v1[0]) - 1;
                                    uint iu1 = uint.TryParse(v1[1], out uint uv1) ? uv1 - 1 : 0;
                                    uint in1 = uint.Parse(v1[2]) - 1;

                                    uint i2  = uint.Parse(v2[0]) - 1;
                                    uint iu2 = uint.TryParse(v2[1], out uint uv2) ? uv2 - 1 : 0;
                                    uint in2 = uint.Parse(v2[2]) - 1;

                                    uint i3  = uint.Parse(v3[0]) - 1;
                                    uint iu3 = uint.TryParse(v3[1], out uint uv3) ? uv3 - 1 : 0;
                                    uint in3 = uint.Parse(v3[2]) - 1;

                                    Triangle tri = new Triangle(i1, iu1, in1, i2, iu2, in2, i3, iu3, in3);

                                    if (!tri.IsNormalAligned)
                                    {
                                        allIndicesAreAligned = false;
                                    }

                                    tri.ConvertBetweenCoordinateSystems();
                                    currentGeometry.Triangles.Add(tri);
                                    TriangleCount++;

                                    break;

                                default:
                                    break;
                                }
                            }

                            // If <= 1 texture was used, it won't have been added while parsing (no "usemtl" defined in OBJ)
                            if (currentGeometry.MaterialName == Geometry.DefaultMaterialName)
                            {
                                Geometries.Add(currentGeometry);
                            }

                            if (!allIndicesAreAligned)
                            {
                                AlignNormals();
                            }

                            // Check to make sure the mesh is valid
                            if (Vertices.Count != Normals.Count)
                            {
                                throw new Exception("Vertex count doesn't not match normal count.");
                            }

                            if (Colors.Count > 0 && Colors.Count != Vertices.Count)
                            {
                                throw new Exception("Color count does not match vertex count.");
                            }

                            foreach (var g in Geometries)
                            {
                                foreach (var tri in g.Triangles)
                                {
                                    if (tri.I1 > Vertices.Count || tri.I2 > Vertices.Count || tri.I3 > Vertices.Count)
                                    {
                                        throw new Exception("Triangle vertex index out of bounds.");
                                    }

                                    if (tri.In1 > Normals.Count || tri.In2 > Normals.Count || tri.In3 > Normals.Count)
                                    {
                                        throw new Exception("Triangle normal index out of bounds.");
                                    }

                                    if (UV.Count > 0 && !(tri.Iu1 < UV.Count && tri.Iu2 < UV.Count && tri.Iu3 < UV.Count))
                                    {
                                        throw new Exception("Triangle uv index out of bounds.");
                                    }
                                }
                            }
                        }
                        catch (Exception e)
                        {
                            ClearAll();

                            Debug.WriteLine($"Failed to parse {objFileName}. Exception: {e.Message}");
                            return(false);
                        }
                    }
                }
            }

            BoundingBox = new BoundingBox(new Vector3(minX, minY, minZ), new Vector3(maxX, maxY, maxZ));

            Debug.WriteLine($"Mesh loaded with {Vertices.Count} Vertices, {Normals.Count} Normals, {UV.Count} UV's, {Colors.Count} Colors, {TriangleCount} Triangles, {Geometries.Count} Geometries");

            IsLoaded = true;
            Filename = path;

            return(true);
        }