Exemplo n.º 1
0
        /// <summary>
        /// Loads a 3D model as an array of vertices from an OBJ file.
        /// </summary>
        /// <typeparam name="T">The type of vertex to load. This defines which vertex data will and will not be loaded.</typeparam>
        /// <param name="stream">The <see cref="Stream"/> from which the OBJ file will be read from.</param>
        /// <param name="options">Specifies options that modify how an OBJ file is parsed.</param>
        /// <returns>An array with the parsed vertex data as a triangle list.</returns>
        /// <exception cref="ObjLoaderException"/>
        public static T[] FromStream <T>(Stream stream, ObjLoadOptions options = ObjLoadOptions.None) where T : unmanaged
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }

            return(FromStream <T>(new StreamReader(stream), options));
        }
Exemplo n.º 2
0
        /// <summary>
        /// Loads a 3D model as an array of vertices from an OBJ file.
        /// </summary>
        /// <typeparam name="T">The type of vertex to load. This defines which vertex data will and will not be loaded.</typeparam>
        /// <param name="file">The path to the OBJ file on disk.</param>
        /// <param name="options">Specifies options that modify how an OBJ file is parsed.</param>
        /// <returns>An array with the parsed vertex data as a triangle list.</returns>
        /// <exception cref="ObjLoaderException"/>
        public static T[] FromFile <T>(string file, ObjLoadOptions options = ObjLoadOptions.None) where T : unmanaged
        {
            if (string.IsNullOrEmpty(file))
            {
                throw new ArgumentNullException(nameof(file));
            }

            using FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
            return(FromStream <T>(new StreamReader(fs), options));
        }
Exemplo n.º 3
0
 public static void ObjLoadOption()
 {
     // ExStart:ObjLoadOption
     // The path to the documents directory.
     string MyDir = RunExamples.GetDataDir();
     // Initialize an object
     ObjLoadOptions loadObjOpts = new ObjLoadOptions();
     // Import materials from external material library file
     loadObjOpts.EnableMaterials = true;
     // Flip the coordinate system.
     loadObjOpts.FlipCoordinateSystem = true;
     // Configure the look up paths to allow importer to find external dependencies.
     loadObjOpts.LookupPaths = new List<string>(new string[] { MyDir});
     // ExEnd:ObjLoadOption
 }
Exemplo n.º 4
0
        private static void ObjLoadOption()
        {
            // ExStart:ObjLoadOption
            // The path to the documents directory.
            string dataDir = RunExamples.GetDataDir();
            // Initialize an object
            ObjLoadOptions loadObjOpts = new ObjLoadOptions();

            // Import materials from external material library file
            loadObjOpts.EnableMaterials = true;
            // Flip the coordinate system.
            loadObjOpts.FlipCoordinateSystem = true;
            // Configure the look up paths to allow importer to find external dependencies.
            loadObjOpts.LookupPaths = new List <string>(new string[] { dataDir });
            // ExEnd:ObjLoadOption
        }
Exemplo n.º 5
0
        /// <summary>
        /// Loads a 3D model as an array of vertices from an OBJ file.
        /// </summary>
        /// <typeparam name="T">The type of vertex to load. This defines which vertex data will and will not be loaded.</typeparam>
        /// <param name="streamReader">The <see cref="StreamReader"/> from which the OBJ file will be read from.</param>
        /// <param name="options">Specifies options that modify how an OBJ file is parsed.</param>
        /// <returns>An array with the parsed vertex data as a triangle list.</returns>
        /// <exception cref="ObjLoaderException"/>
        public static T[] FromStream <T>(StreamReader streamReader, ObjLoadOptions options = ObjLoadOptions.None) where T : unmanaged
        {
            if (streamReader == null)
            {
                throw new ArgumentNullException(nameof(streamReader));
            }

            // tmp will be used to check which vertex data to load with the is pattern at the beginning
            // and to format the vertex attributes into memory after the file has been parsed.
            T tmp = default;

            // We check which vertex attributes to load and which not to depending on the vertex type.
            bool loadNormals   = tmp is VertexNormal || tmp is VertexNormalColor || tmp is VertexNormalTexture || tmp is VertexNormalColorTexture;
            bool loadColors    = tmp is VertexColor || tmp is VertexColorTexture || tmp is VertexNormalColor || tmp is VertexNormalColorTexture;
            bool loadTexCoords = tmp is VertexTexture || tmp is VertexColorTexture || tmp is VertexNormalTexture || tmp is VertexNormalColorTexture;

            // Let's ensure the vertex type is valid.
            if (!loadNormals && !loadColors && !loadTexCoords && !(tmp is VertexPosition || tmp is Vector3))
            {
                throw new ObjLoaderException("Vertex format not supported. Use a library-provided vertex type instead.");
            }

            bool largePolygons = !options.HasFlag(ObjLoadOptions.TrianglesOnly);

            // We're gonna need lists to store temporary data. Let's get them form here.
            GetLists(loadNormals, loadColors, loadTexCoords, out List <Vector3> positions, out List <Vector3> normals,
                     out List <Color4b> colors, out List <Vector2> texCoords, out List <int> indices);

            // When something goes wrong, it's gonna be nice to have the line number on the exception's message ;)
            int  currentLineNumber = 0;
            bool doneParsingData   = false;

            try
            {
                // In this variable we will count the amount of vertices
                int vertexCount = 0;

                // This is the buffer we'll use to parse numbers. The initial buffer is a relatively
                // small, stackallocated array but if we need more space a char[] will be allocated
                // and this span will be replaced to point at that larger array instead.
                Span <char> charBuffer = stackalloc char[CharBufferLength];

                while (!streamReader.EndOfStream)
                {
                    currentLineNumber++;

                    // This will skip empty lines (or lines that are only whitespaces) and advance
                    // the reader to skip any whitespaces at the beginning of a line.
                    if (SkipWhitespaces(streamReader))
                    {
                        SkipLine(streamReader);
                        continue;
                    }

                    // We read the next character (it's guaranteed not to be a whitespace).
                    char currentChar = (char)streamReader.Read();
                    if (currentChar == 'v')
                    {
                        // The line starts with 'v'. Let's read the next character to see what to do next.
                        currentChar = (char)streamReader.Read();

                        // The entire line might be just "v\n". This should throw an error.
                        if (streamReader.EndOfStream || currentChar == NewlineIndicator)
                        {
                            throw new FormatException("Unexpected end of line");
                        }

                        // We check that character and
                        if (char.IsWhiteSpace(currentChar))
                        {
                            // We need to load a Vector3 position
                            positions.Add(new Vector3(
                                              ReadNextFloat(streamReader, ref charBuffer),
                                              ReadNextFloat(streamReader, ref charBuffer),
                                              ReadNextFloat(streamReader, ref charBuffer)
                                              ));

                            if (loadColors)
                            {
                                // If we're also loading colors, we load a color4b from 3 floats
                                colors.Add(new Color4b(
                                               ReadNextFloat(streamReader, ref charBuffer),
                                               ReadNextFloat(streamReader, ref charBuffer),
                                               ReadNextFloat(streamReader, ref charBuffer)
                                               ));
                            }
                        }
                        else if (loadNormals && currentChar == 'n')
                        {
                            // We need to load a Vector3 normal
                            Vector3 norm = new Vector3(
                                ReadNextFloat(streamReader, ref charBuffer),
                                ReadNextFloat(streamReader, ref charBuffer),
                                ReadNextFloat(streamReader, ref charBuffer)
                                );
                            normals.Add(norm);
                        }
                        else if (loadTexCoords && currentChar == 't')
                        {
                            // We need to load a Vector2 with texture coordinates
                            Vector2 coords = new Vector2(
                                ReadNextFloat(streamReader, ref charBuffer),
                                ReadNextFloat(streamReader, ref charBuffer)
                                );
                            texCoords.Add(coords);
                        }
                    }
                    else if (currentChar == 'f')
                    {
                        // This line is specifying a face. We need to read a polygon face.
                        // The way we load faces into triangles is by making a triangle fan.
                        // Example: vertices 0 1 2 3 4 5 will be turned into the triangles:
                        // (0 1 2) (0 2 3) (0 3 4) (0 4 5)
                        // Looking at this you can see that for each triangle, we need to know
                        // the polygon's first vertex, and the last vertex of the previous triangle.

                        // We start by loading the first two vertices and verifying their indices.
                        // The indices of the first vertex will be in "first0", "second0" and "third0".
                        ReadThreeIntegers(streamReader, ref charBuffer, out int first0, out int second0, out int third0);
                        VerifyIndices(first0, second0, third0);
                        ReadThreeIntegers(streamReader, ref charBuffer, out int lastFirst, out int lastSecond, out int lastThird);
                        VerifyIndices(lastFirst, lastSecond, lastThird);

                        // When we add the indices to the list we need to substract one, because OBJ indices
                        // start at 1 (and array indices start at 0, f**k all languages where it doesn't).
                        first0--;
                        second0--;
                        third0--;
                        lastFirst--;
                        lastSecond--;
                        lastThird--;

                        // We now load the third vertex
                        ReadThreeIntegers(streamReader, ref charBuffer, out int first, out int second, out int third);

                        do
                        {
                            // We have to add one more triangle. Let's start by verifying the last indices we read.
                            VerifyIndices(first, second, third);

                            // We add the polygon's first vertex.
                            indices.Add(first0);
                            if (loadNormals)
                            {
                                indices.Add(third0);
                            }
                            if (loadTexCoords)
                            {
                                indices.Add(second0);
                            }

                            // We add the last vertex (taken from the previous triangle)
                            indices.Add(lastFirst);
                            if (loadNormals)
                            {
                                indices.Add(lastThird);
                            }
                            if (loadTexCoords)
                            {
                                indices.Add(lastSecond);
                            }

                            first--;
                            second--;
                            third--;

                            // We add the current vertex and increment vertexCount by 3.
                            indices.Add(first);
                            if (loadNormals)
                            {
                                indices.Add(third);
                            }
                            if (loadTexCoords)
                            {
                                indices.Add(second);
                            }

                            vertexCount += 3;

                            // The next triangle will need to know the last vertex of the previous triangle.
                            lastFirst  = first;
                            lastSecond = second;
                            lastThird  = third;

                            // If largePolygons is disabled, this process ends after only one triangle.
                            // Otherwise, this process continues while there are more vertices in this line.
                        } while (largePolygons && TryReadThreeIntegers(streamReader, ref charBuffer, out first, out second, out third));
                    }

                    // The entire while cycle ensures the end of the line isn't reached until now.
                    // This ensures our currentLineNumber is counting correctly.
                    SkipLine(streamReader);
                }

                // We're done reading the file. We now need to process the data.
                doneParsingData = true;
                T[] vertices = new T[vertexCount];

                // Time to get funky
                unsafe
                {
                    int ind = 0;
                    for (int i = 0; i < vertexCount; i++)
                    {
                        // We don't know what type of vertex we have, but we know the attributes
                        // are always in the same order: POSITION NORMAL COLOR TEXCOORDS
                        // So the easiest way to set the data of whatever a T is, is to just
                        // grab a pointer and manually set it, since we can't do new T(position, etc).

                        // The data will be set to tmp, which has a fixed address (it's in the stack)
                        float *ptrToTmp = (float *)&tmp;

                        // We write the position onto tmp and advance the pointer
                        int     posIndex = indices[ind++];
                        Vector3 pos      = positions[posIndex];
                        ptrToTmp[0] = pos.X;
                        ptrToTmp[1] = pos.Y;
                        ptrToTmp[2] = pos.Z;
                        ptrToTmp   += 3;

                        // If the vertex type has normal, we write the normal and advance the pointer
                        if (loadNormals)
                        {
                            pos         = normals[indices[ind++]];
                            ptrToTmp[0] = pos.X;
                            ptrToTmp[1] = pos.Y;
                            ptrToTmp[2] = pos.Z;
                            ptrToTmp   += 3;
                        }

                        // If the vertex type has color, we write the color and advance the pointer
                        if (loadColors)
                        {
                            ((Color4b *)ptrToTmp)[0] = colors[posIndex];
                            ptrToTmp++;
                        }

                        // If the vertex type has texcoords, we write the texcoords and advance the pointer
                        if (loadTexCoords)
                        {
                            Vector2 tex = texCoords[indices[ind++]];
                            ptrToTmp[0] = tex.X;
                            ptrToTmp[1] = 1 - tex.Y;
                            ptrToTmp   += 2;
                        }

                        // Now T has the value we wanted. We can store it in the array.
                        vertices[i] = tmp;

                        // Yes, we could write directly to the array. But we'd have to fix it in place.
                    }
                }

                // Done!
                return(vertices);
            }
            catch (Exception e)
            {
                // If the error happened before doneParsingData was set to true, it was an error
                // in a specific line. Otherwise, it was an error constructing the vertex data.
                if (doneParsingData)
                {
                    throw new ObjLoaderException("Error processing obj data.", e);
                }
                else
                {
                    throw new ObjLoaderException("Error in line " + currentLineNumber + ": " + e.Message, e);
                }
            }
            finally
            {
                // We're done here. Let's return the lists so they can be reused if needed.
                ReturnLists(positions, normals, colors, texCoords, indices);
            }

            // Verifies that the given indices read from a group of three integers are valid.
            void VerifyIndices(int first, int second, int third)
            {
                // We check that the position index is valid.
                if (first < 1 || first > positions.Count)
                {
                    throw new FormatException("Invalid position index integer: " + first.ToString());
                }

                // If we're loading normals, we check that the normal index is valid.
                if (loadNormals && (third < 1 || third > normals.Count))
                {
                    throw new FormatException("Invalid normal index integer: " + third.ToString());
                }

                // If we're loading texcoords, we check that the texcoord index is valid.
                if (loadTexCoords && (second < 1 || second > texCoords.Count))
                {
                    throw new FormatException("Invalid texture coordinates index integer: " + second.ToString());
                }
            }
        }