[
  {
    "path": ".gitignore",
    "content": "target/\n.classpath\n.project\n.settings\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "\r\nwww.javagl.de - Obj\r\n\r\nCopyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n\r\nPermission is hereby granted, free of charge, to any person\r\nobtaining a copy of this software and associated documentation\r\nfiles (the \"Software\"), to deal in the Software without\r\nrestriction, including without limitation the rights to use,\r\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the\r\nSoftware is furnished to do so, subject to the following\r\nconditions:\r\n\r\nThe above copyright notice and this permission notice shall be\r\nincluded in all copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\nOTHER DEALINGS IN THE SOFTWARE.\r\n\r\n"
  },
  {
    "path": "README.md",
    "content": "# Obj - a simple Wavefront OBJ file loader and writer\n\nMaven dependency:\n\n    <dependency>\n      <groupId>de.javagl</groupId>\n      <artifactId>obj</artifactId>\n      <version>0.4.0</version>\n    </dependency>\n    \nDirect JAR link: [https://oss.sonatype.org/content/repositories/releases/de/javagl/obj/0.4.0/obj-0.4.0.jar](https://oss.sonatype.org/content/repositories/releases/de/javagl/obj/0.4.0/obj-0.4.0.jar)\n\n# Samples\n\nSamples showing how to use this library are available in the [ObjSamples project](https://github.com/javagl/ObjSamples).\n    \n\n# Overview\n\nThis is a simple loader and writer for Wavefront `.OBJ` files. The elements\nthat are currently supported are\n\n - Vertices\n - Texture coordinates\n - Normals\n - Faces (with positive or negative indices)\n - Groups\n - Material groups\n - MTL files\n \nThe `Obj` interface is basically an in-memory representation of an OBJ file.\nIt combines a `ReadableObj`, which provides the contents of the OBJ file,\nand a `WritableObj`, which may receive elements like vertices and faces\nin order to build an OBJ in memory.\n\nThe `ObjReader` class may either create a new `Obj` object directly \nfrom an input stream, or pass the elements that are read from the input \nstream to a `WritableObj`.\n\nThe `ObjWriter` class offers a method to write a `ReadableObj` object\nto an output stream.\n\nThe `ObjData` class offers various methods to obtain the data that is\nstored in a `ReadableObj` as plain arrays or *direct* buffers. \n\nThe `ObjUtils` class offers basic utility methods for general operations\non the OBJ data. \n\n## Rendering OBJ data with OpenGL\n   \nThe `ObjUtils` class contains methods that aim at preparing the OBJ so \nthat it may easily be rendered with OpenGL. These methods may...\n\n - convert a single group of an OBJ into a new OBJ\n - triangulate OBJ data\n - make sure that texture coordinates or normal coordinates are unique\n   for each vertex\n - convert an OBJ to an OBJ that is uses the same index sets for vertices,\n   texture coordinates and normals\n\nThe latter operations are also summarized in one dedicated method, namely\nthe `ObjUtils.convertToRenderable` method:\n\n    InputStream inputStream = ...;\n\n    Obj obj = ObjUtils.convertToRenderable(\n        ObjReader.read(inputStream));\n\n    IntBuffer indices = ObjData.getFaceVertexIndices(obj);\n    FloatBuffer vertices = ObjData.getVertices(obj);\n    FloatBuffer texCoords = ObjData.getTexCoords(obj, 2);\n    FloatBuffer normals = ObjData.getNormals(obj);\n\nThese buffers may directly be used as the data for vertex buffer objects (VBO)\nin OpenGL. \n\n### Extracting material groups\n\nAn OBJ may contain multiple material definitions. When such an OBJ should\nbe rendered with OpenGL, this usually means that there will be one shader\nfor each material - or at least, different textures may have to be used\nfor different parts of the objects. This library offers methods to extract \nthe parts of the OBJ that have the same material. In the OBJ format, these \ngroups consist of the triangles that follow one `usemtl` directive.\n\nWhen such an OBJ file is read, the resulting material groups may be obtained\nfrom the `ReadableObj` object, and each of them can be converted into a new\n`Obj` object using the `ObjUtils#groupToObj` method. \n\nThe `ObjSplitting` class contains a convenience method for this:\n\n    Obj obj = ObjReader.read(...);\n    Map<String, Obj> mtlObjs = ObjSplitting.splitByMaterialGroups(obj);;\n\nEach of these `Obj` objects may then be converted into a renderable OBJ,\nusing the `ObjUtils.convertToRenderable` method as described above, \nand then be rendered with the appropriate shader for the respective\nmaterial.\n\n### Limiting the number of vertices per OBJ\n\nIn certain environments, the number of vertices that may be involved in\none rendering call is limited. Particularly, in WebGL or OpenGL ES 2.0,\nthe indices that are used for indexed draw calls may only be of the type\n`GL_UNSIGNED_SHORT`, which means that no object may have more than\n65k vertices. In these cases, larger OBJ files have to be split into\nmultiple parts. Additionally, the index buffers that are passed to \nthe rendering API may not contain (4-byte) `int` elements, but only\n(2-byte) `short` elements. \n\nThe `ObjSplitting` class contains a method that allows splitting an\nOBJ into multiple parts, each having only a maximum number of vertices.\nAdditionally, the `ObjData` class contains methods for converting \nan `IntBuffer` into a `ShortBuffer`. \n \nSo in order to split a large OBJ into multiple parts, and render each\npart with WebGL or OpenGL ES 2.0, the following code can be used:\n\n    Obj largeObj = ObjReader.read(...);\n    Obj renderableObj = ObjUtils.convertToRenderable(largeObj);\n    \n    if (renderableObj.getNumVertices() > 65000)\n    {\n        // If this has to be rendered with OpenGL ES 2.0, then\n        // the object may not contain more than 65k vertices!\n        // Split it into multiple parts: \n        List<Obj> renderableParts = \n            ObjSplitting.splitByMaxNumVertices(renderableObj, 65000);\n        for (Obj renderablePart : renderableParts)\n        {\n        \n            // Obtain the indices as a \"short\" buffer that may\n            // be used for OpenGL rendering with the index \n            // type GL_UNSIGNED_SHORT\n            ShortBuffer indices = ObjData.convertToShortBuffer(\n                ObjData.getFaceVertexIndices(renderablePart));\n            ...\n            \n            sendToRenderer(indices, ...);\n        }\n     }\n     ...\n\n\n--- \n\n# Change log\n\n**0.4.1-SNAPSHOT**\n - ...\n \n**0.4.0** (2023-03-04)\n\n- Updated MTL handling to support additional options. This includes the\n  options that have been part of the original MTL specification, as well\n  as PBR (Physically Based Rendering) options.\n\n  - A new interface `TextureOptions` has been introduced. For texture\n    maps, the `Mtl` interface now has methods `getMap...Options()`\n    and `setMap...Options(...)`. This `TextureOptions` object\n    contains the parameters that are common for all texture maps, like\n    blending states, offsets, or scales, as described in the MTL specification.\n\n  - **API change**: The `Mtl#get...` methods will now return `null` when\n    no information was parsed from the input file.\n    Code that originally called one of these methods, like\n\n    ```\n    float d = mtl.getD();\n    ```\n\n    should now check whether these values are not `null`, as in\n\n    ```\n    float d = 1.0f; // The default value for the opacity\n    if (mtl.getD() != null)\n    {\n        d = mtl.getD();\n    }\n    ```\n\n    This will not affect clients that only read or write the `Mtl` with the\n    `MtlReader` or `MtlWriter`, because these classes will handle the\n    `null`-cases internally.\n\n\n**0.3.0** (2018-01-12)\n\n- Added `ObjSplitting` class for splitting OBJs\n- Added `ObjData#convertToShortBuffer` method\n- Added `ObjUtils#add` method for combining OBJs \n\n**0.2.1** (2015-10-26)\n\n- Bugfix: Made `AbstractWritableObj#addNormal(FloatTuple)` non-final\n- Added methods in `ObjReader` and `MtlReader` that accept a `Reader`\n  instead of an `InputStream`, and methods in `ObjWriter` and \n  `MtlWriter` that accept a `Writer` instead of an `OutputStream`\n- Added implementation of `ObjUtils.convertToRenderable` that\n   receives a `WritableObj`  \n  \n**0.2.0** (2015-10-05) : \n\n- Initial public release on GitHub and Maven Central\n   \n\n"
  },
  {
    "path": "pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\r\n\r\n    <parent>\r\n        <groupId>org.sonatype.oss</groupId>\r\n        <artifactId>oss-parent</artifactId>\r\n        <version>9</version>\r\n     </parent>\r\n\r\n    <modelVersion>4.0.0</modelVersion>\r\n\r\n    <groupId>de.javagl</groupId>\r\n    <artifactId>obj</artifactId>\r\n    <version>0.4.1-SNAPSHOT</version>\r\n\r\n    <name>obj</name>\r\n    <url>https://github.com/javagl</url>\r\n\r\n    <developers>\r\n        <developer>\r\n            <name>Marco Hutter</name>\r\n            <email>javagl@javagl.de</email>\r\n            <roles>\r\n                <role>developer</role>\r\n            </roles>\r\n        </developer>\r\n    </developers>\r\n\r\n    <scm>\r\n        <connection>scm:git:git@github.com:javagl/Obj.git</connection>\r\n        <developerConnection>scm:git:git@github.com:javagl/Obj.git</developerConnection>\r\n        <url>git@github.com:javagl/Obj.git</url>\r\n    </scm>\r\n\r\n    <licenses>\r\n        <license>\r\n            <name>MIT</name>\r\n            <url>https://github.com/javagl/Obj/blob/master/LICENSE.txt</url>\r\n            <distribution>repo</distribution>\r\n        </license>\r\n    </licenses>\r\n\r\n    <properties>\r\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\r\n    </properties>\r\n\r\n    <build>\r\n        <plugins>\r\n            <plugin>\r\n                <artifactId>maven-compiler-plugin</artifactId>\r\n                <version>2.3.2</version>\r\n                <configuration>\r\n                    <source>1.8</source>\r\n                    <target>1.8</target>\r\n                </configuration>\r\n            </plugin>\r\n            <plugin>\r\n                <groupId>org.apache.maven.plugins</groupId>\r\n                <artifactId>maven-source-plugin</artifactId>\r\n                <version>2.1.2</version>\r\n                <executions>\r\n                    <execution>\r\n                        <id>attach-sources</id>\r\n                        <goals>\r\n                            <goal>jar</goal>\r\n                        </goals>\r\n                    </execution>\r\n                </executions>\r\n            </plugin>\r\n            <plugin>\r\n                <groupId>org.apache.maven.plugins</groupId>\r\n                <artifactId>maven-javadoc-plugin</artifactId>\r\n                <version>2.10.1</version>\r\n                <executions>\r\n                    <execution>\r\n                        <id>attach-javadocs</id>\r\n                        <goals>\r\n                            <goal>jar</goal>\r\n                        </goals>\r\n                    </execution>\r\n                </executions>\r\n            </plugin>\r\n\r\n        </plugins>\r\n    </build>\r\n\r\n\r\n    <dependencies>\r\n        <dependency>\r\n            <groupId>junit</groupId>\r\n            <artifactId>junit</artifactId>\r\n            <version>4.13.1</version>\r\n            <scope>test</scope>\r\n        </dependency>\r\n    </dependencies>\r\n\r\n    <description>A simple Wavefront OBJ file loader and writer</description>\r\n</project>"
  },
  {
    "path": "src/main/java/de/javagl/obj/AbstractWritableObj.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\n\r\nimport java.util.Collection;\r\nimport java.util.Objects;\r\n\r\n/**\r\n * Abstract base implementation of a {@link WritableObj}. <br>\r\n * <br>\r\n * The final implementations of the methods in this class all delegate\r\n * to the following (non-final) methods, which may be overridden by\r\n * implementors:\r\n * <ul>\r\n *    <li>{@link #addVertex(FloatTuple)}</li>\r\n *    <li>{@link #addTexCoord(FloatTuple)}</li>\r\n *    <li>{@link #addFace(ObjFace)}</li>\r\n *    <li>{@link #setActiveGroupNames(Collection)}</li>\r\n *    <li>{@link #setActiveMaterialGroupName(String)}</li>\r\n *    <li>{@link #setMtlFileNames(Collection)}</li>\r\n * </ul>\r\n */\r\npublic class AbstractWritableObj implements WritableObj\r\n{\r\n    /**\r\n     * Default constructor\r\n     */\r\n    protected AbstractWritableObj()\r\n    {\r\n        // Default constructor\r\n    }\r\n    \r\n    @Override\r\n    public final void addVertex(float x, float y, float z)\r\n    {\r\n        addVertex(FloatTuples.create(x, y, z));\r\n    }\r\n    \r\n    @Override\r\n    public void addVertex(FloatTuple vertex)\r\n    {\r\n        // Empty default implementation\r\n    }\r\n    \r\n    @Override\r\n    public final void addTexCoord(float x)\r\n    {\r\n        addTexCoord(FloatTuples.create(x));\r\n    }\r\n    \r\n    @Override\r\n    public final void addTexCoord(float x, float y)\r\n    {\r\n        addTexCoord(FloatTuples.create(x, y));\r\n    }\r\n    \r\n    @Override\r\n    public final void addTexCoord(float x, float y, float z)\r\n    {\r\n        addTexCoord(FloatTuples.create(x, y, z));\r\n    }\r\n    \r\n    @Override\r\n    public void addTexCoord(FloatTuple texCoord)\r\n    {\r\n        // Empty default implementation\r\n    }\r\n    \r\n\r\n    @Override\r\n    public void addNormal(FloatTuple normal)\r\n    {\r\n        // Empty default implementation\r\n    }\r\n\r\n    @Override\r\n    public final void addNormal(float x, float y, float z)\r\n    {\r\n        addNormal(FloatTuples.create(x, y, z));\r\n    }\r\n    \r\n    @Override\r\n    public void setActiveGroupNames(\r\n        Collection<? extends String> groupNames)\r\n    {\r\n        // Empty default implementation\r\n    }\r\n    \r\n    \r\n    @Override\r\n    public void setActiveMaterialGroupName(String materialGroupName)\r\n    {\r\n        // Empty default implementation\r\n    }\r\n    \r\n    @Override\r\n    public void addFace(ObjFace face)\r\n    {\r\n        // Empty default implementation\r\n    }\r\n    \r\n    @Override\r\n    public final void addFace(int ... v)\r\n    {\r\n        addFace(v, null, null);\r\n    }\r\n\r\n    @Override\r\n    public final void addFaceWithTexCoords(int... v)\r\n    {\r\n        addFace(v, v, null);\r\n    }\r\n\r\n    @Override\r\n    public final void addFaceWithNormals(int... v)\r\n    {\r\n        addFace(v, null, v);\r\n    }\r\n\r\n    @Override\r\n    public final void addFaceWithAll(int... v)\r\n    {\r\n        addFace(v, v, v);\r\n    }\r\n    \r\n    @Override\r\n    public final void addFace(int[] v, int[] vt, int[] vn)\r\n    {\r\n        Objects.requireNonNull(v, \"The vertex indices are null\");\r\n        addFace(ObjFaces.create(v, vt, vn));\r\n    }\r\n\r\n    @Override\r\n    public void setMtlFileNames(Collection<? extends String> mtlFileNames)\r\n    {\r\n        // Empty default implementation\r\n    }\r\n\r\n    \r\n    \r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/BasicWritableObj.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\n\r\nimport java.util.Collection;\r\nimport java.util.Objects;\r\nimport java.util.function.Consumer;\r\n\r\n/**\r\n * Basic implementation of a {@link WritableObj} that delegates all calls\r\n * to consumer callbacks. <br>\r\n * <br>\r\n * The consumers for the elements of an OBJ are <code>null</code> by default,\r\n * causing the respective elements to be ignored. The callbacks may be set \r\n * individually. For example, in order to print all vertices and faces that \r\n * are read from an OBJ file, the following may be used:\r\n * <pre><code>\r\n * BasicWritableObj obj = new BasicWritableObj();\r\n * obj.setVertexConsumer(t -&gt; System.out.println(t));\r\n * obj.setFaceConsumer(t -&gt; System.out.println(t));\r\n * ObjReader.read(inputStream, obj);\r\n * </code></pre> \r\n */\r\npublic class BasicWritableObj implements WritableObj\r\n{\r\n    /**\r\n     * The vertex consumer\r\n     */\r\n    private Consumer<? super FloatTuple> vertexConsumer;\r\n\r\n    /**\r\n     * The texture coordinate consumer\r\n     */\r\n    private Consumer<? super FloatTuple> texCoordConsumer;\r\n    \r\n    /**\r\n     * The normal consumer\r\n     */\r\n    private Consumer<? super FloatTuple> normalConsumer;\r\n    \r\n    /**\r\n     * The face consumer\r\n     */\r\n    private Consumer<? super ObjFace> faceConsumer;\r\n    \r\n    /**\r\n     * The consumer for group names\r\n     */\r\n    private Consumer<? super Collection<? extends String>> groupNamesConsumer;\r\n    \r\n    /**\r\n     * The consumer for material group names\r\n     */\r\n    private Consumer<? super String> materialGroupNameConsumer;\r\n    \r\n    /**\r\n     * The consumer for MTL file names\r\n     */\r\n    private Consumer<? super Collection<? extends String>> mtlFileNamesConsumer;\r\n    \r\n    /**\r\n     * Default constructor\r\n     */\r\n    public BasicWritableObj()\r\n    {\r\n        // Default constructor\r\n    }\r\n    \r\n    /**\r\n     * Set the vertex consumer\r\n     * \r\n     * @param vertexConsumer The consumer\r\n     */\r\n    public void setVertexConsumer(Consumer<? super FloatTuple> vertexConsumer)\r\n    {\r\n        this.vertexConsumer = vertexConsumer;\r\n    }\r\n\r\n    /**\r\n     * Set the texture coordinate consumer\r\n     * \r\n     * @param texCoordConsumer The consumer\r\n     */\r\n    public void setTexCoordConsumer(\r\n        Consumer<? super FloatTuple> texCoordConsumer)\r\n    {\r\n        this.texCoordConsumer = texCoordConsumer;\r\n    }\r\n\r\n    /**\r\n     * Set the normal consumer\r\n     * \r\n     * @param normalConsumer The consumer\r\n     */\r\n    public void setNormalConsumer(Consumer<? super FloatTuple> normalConsumer)\r\n    {\r\n        this.normalConsumer = normalConsumer;\r\n    }\r\n\r\n    /**\r\n     * Set the face consumer\r\n     * \r\n     * @param faceConsumer The consumer\r\n     */\r\n    public void setFaceConsumer(Consumer<? super ObjFace> faceConsumer)\r\n    {\r\n        this.faceConsumer = faceConsumer;\r\n    }\r\n\r\n    /**\r\n     * Set the group names consumer\r\n     * \r\n     * @param groupNamesConsumer The consumer\r\n     */\r\n    public void setGroupNamesConsumer(\r\n        Consumer<? super Collection<? extends String>> groupNamesConsumer)\r\n    {\r\n        this.groupNamesConsumer = groupNamesConsumer;\r\n    }\r\n\r\n    /**\r\n     * Set the material group name consumer\r\n     * \r\n     * @param materialGroupNameConsumer The consumer\r\n     */\r\n    public void setMaterialGroupNameConsumer(\r\n        Consumer<? super String> materialGroupNameConsumer)\r\n    {\r\n        this.materialGroupNameConsumer = materialGroupNameConsumer;\r\n    }\r\n\r\n    /**\r\n     * Set the MTL file names consumer\r\n     * \r\n     * @param mtlFileNamesConsumer The consumer\r\n     */\r\n    public void setMtlFileNamesConsumer(\r\n        Consumer<? super Collection<? extends String>> mtlFileNamesConsumer)\r\n    {\r\n        this.mtlFileNamesConsumer = mtlFileNamesConsumer;\r\n    }\r\n\r\n    \r\n    @Override\r\n    public final void addVertex(FloatTuple vertex)\r\n    {\r\n        if (vertexConsumer != null)\r\n        {\r\n            vertexConsumer.accept(vertex);\r\n        }\r\n    }\r\n    \r\n    @Override\r\n    public final void addVertex(float x, float y, float z)\r\n    {\r\n        addVertex(FloatTuples.create(x, y, z));\r\n    }\r\n    \r\n    @Override\r\n    public final void addTexCoord(FloatTuple texCoord)\r\n    {\r\n        if (texCoordConsumer != null)\r\n        {\r\n            texCoordConsumer.accept(texCoord);\r\n        }\r\n    }\r\n    \r\n    @Override\r\n    public final void addTexCoord(float x)\r\n    {\r\n        addTexCoord(FloatTuples.create(x));\r\n    }\r\n    \r\n    @Override\r\n    public final void addTexCoord(float x, float y)\r\n    {\r\n        addTexCoord(FloatTuples.create(x, y));\r\n    }\r\n    \r\n    @Override\r\n    public final void addTexCoord(float x, float y, float z)\r\n    {\r\n        addTexCoord(FloatTuples.create(x, y, z));\r\n    }\r\n    \r\n\r\n    @Override\r\n    public final void addNormal(FloatTuple normal)\r\n    {\r\n        if (normalConsumer != null)\r\n        {\r\n            normalConsumer.accept(normal);\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public final void addNormal(float x, float y, float z)\r\n    {\r\n        addNormal(FloatTuples.create(x, y, z));\r\n    }\r\n    \r\n    @Override\r\n    public final void setActiveGroupNames(\r\n        Collection<? extends String> groupNames)\r\n    {\r\n        if (groupNamesConsumer != null)\r\n        {\r\n            groupNamesConsumer.accept(groupNames);\r\n        }\r\n    }\r\n    \r\n    \r\n    @Override\r\n    public final void setActiveMaterialGroupName(String materialGroupName)\r\n    {\r\n        if (materialGroupNameConsumer != null)\r\n        {\r\n            materialGroupNameConsumer.accept(materialGroupName);\r\n        }\r\n    }\r\n    \r\n    @Override\r\n    public final void addFace(ObjFace face)\r\n    {\r\n        if (faceConsumer != null)\r\n        {\r\n            faceConsumer.accept(face);\r\n        }\r\n    }\r\n    \r\n    @Override\r\n    public final void addFace(int ... v)\r\n    {\r\n        addFace(v, null, null);\r\n    }\r\n\r\n    @Override\r\n    public final void addFaceWithTexCoords(int... v)\r\n    {\r\n        addFace(v, v, null);\r\n    }\r\n\r\n    @Override\r\n    public final void addFaceWithNormals(int... v)\r\n    {\r\n        addFace(v, null, v);\r\n    }\r\n\r\n    @Override\r\n    public final void addFaceWithAll(int... v)\r\n    {\r\n        addFace(v, v, v);\r\n    }\r\n    \r\n    @Override\r\n    public final void addFace(int[] v, int[] vt, int[] vn)\r\n    {\r\n        Objects.requireNonNull(v, \"The vertex indices are null\");\r\n        if (faceConsumer != null)\r\n        {\r\n            addFace(ObjFaces.create(v, vt, vn));\r\n        }\r\n    }\r\n    \r\n\r\n    @Override\r\n    public final void setMtlFileNames(Collection<? extends String> mtlFileNames)\r\n    {\r\n        if (mtlFileNamesConsumer != null)\r\n        {\r\n            mtlFileNamesConsumer.accept(mtlFileNames);\r\n        }\r\n    }\r\n\r\n    \r\n    \r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/DefaultFloatTuple.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\n\r\nimport java.util.Arrays;\r\n\r\n/**\r\n * Default implementation of a {@link FloatTuple}\r\n */\r\nfinal class DefaultFloatTuple implements FloatTuple\r\n{\r\n    /**\r\n     * The values of this tuple\r\n     */\r\n    private final float[] values;\r\n    \r\n    /**\r\n     * Creates a new DefaultFloatTuple with the given values\r\n     *\r\n     * @param values The values\r\n     */\r\n    DefaultFloatTuple(float[] values)\r\n    {\r\n        this.values = values;\r\n    }\r\n    \r\n    /**\r\n     * Creates a new DefaultFloatTuple with the given values\r\n     * \r\n     * @param x The x value\r\n     * @param y The y value\r\n     * @param z The z value\r\n     * @param w The w value\r\n     */\r\n    DefaultFloatTuple(float x, float y, float z, float w)\r\n    {\r\n        this(new float[]{x,y,z,w});\r\n    }\r\n\r\n    /**\r\n     * Creates a new DefaultFloatTuple with the given values\r\n     * \r\n     * @param x The x value\r\n     * @param y The y value\r\n     * @param z The z value\r\n     */\r\n    DefaultFloatTuple(float x, float y, float z)\r\n    {\r\n        this(new float[]{x,y,z});\r\n    }\r\n\r\n    /**\r\n     * Creates a new DefaultFloatTuple with the given values\r\n     * \r\n     * @param x The x value\r\n     * @param y The y value\r\n     */\r\n    DefaultFloatTuple(float x, float y)\r\n    {\r\n        this(new float[]{x,y});\r\n    }\r\n\r\n    /**\r\n     * Creates a new DefaultFloatTuple with the given value\r\n     * \r\n     * @param x The x value\r\n     */\r\n    DefaultFloatTuple(float x)\r\n    {\r\n        this(new float[]{x});\r\n    }\r\n    \r\n    \r\n    /**\r\n     * Copy constructor.\r\n     * \r\n     * @param other The other FloatTuple\r\n     */\r\n    DefaultFloatTuple(FloatTuple other)\r\n    {\r\n        this(getValues(other));\r\n    }\r\n    \r\n    /**\r\n     * Returns the values of the given {@link FloatTuple} as an array\r\n     * \r\n     * @param f The {@link FloatTuple}\r\n     * @return The values\r\n     */\r\n    private static float[] getValues(FloatTuple f)\r\n    {\r\n        if (f instanceof DefaultFloatTuple)\r\n        {\r\n            DefaultFloatTuple other = (DefaultFloatTuple)f;\r\n            return other.values.clone();\r\n        }\r\n        float[] values = new float[f.getDimensions()];\r\n        for (int i=0; i<values.length; i++)\r\n        {\r\n            values[i] = f.get(i);\r\n        }\r\n        return values;\r\n    }\r\n\r\n    @Override\r\n    public float get(int index)\r\n    {\r\n        return values[index];\r\n    }\r\n    \r\n    @Override\r\n    public float getX()\r\n    {\r\n        return values[0];\r\n    }\r\n\r\n    /**\r\n     * Set the given component of this tuple\r\n     * \r\n     * @param x The component to set\r\n     * @throws IndexOutOfBoundsException If this tuple has less than 1 \r\n     * dimensions\r\n     */\r\n    void setX(float x)\r\n    {\r\n        values[0] = x;\r\n    }\r\n\r\n    @Override\r\n    public float getY()\r\n    {\r\n        return values[1];\r\n    }\r\n\r\n    /**\r\n     * Set the given component of this tuple\r\n     * \r\n     * @param y The component to set\r\n     * @throws IndexOutOfBoundsException If this tuple has less than 2 \r\n     * dimensions\r\n     */\r\n    void setY(float y)\r\n    {\r\n        values[1] = y;\r\n    }\r\n\r\n    @Override\r\n    public float getZ()\r\n    {\r\n        return values[2];\r\n    }\r\n\r\n    /**\r\n     * Set the given component of this tuple\r\n     * \r\n     * @param z The component to set\r\n     * @throws IndexOutOfBoundsException If this tuple has less than 3 \r\n     * dimensions\r\n     */\r\n    void setZ(float z)\r\n    {\r\n        values[2] = z;\r\n    }\r\n\r\n    @Override\r\n    public float getW()\r\n    {\r\n        return values[3];\r\n    }\r\n    \r\n    /**\r\n     * Set the given component of this tuple\r\n     * \r\n     * @param w The component to set\r\n     * @throws IndexOutOfBoundsException If this tuple has less than 4 \r\n     * dimensions\r\n     */\r\n    void setW(float w)\r\n    {\r\n        values[3] = w;\r\n    }\r\n\r\n    @Override\r\n    public int getDimensions()\r\n    {\r\n        return values.length;\r\n    }\r\n\r\n    \r\n    @Override\r\n    public String toString()\r\n    {\r\n        StringBuilder sb = new StringBuilder();\r\n        sb.append(\"(\");\r\n        for (int i=0; i<getDimensions(); i++)\r\n        {\r\n            sb.append(get(i));\r\n            if (i < getDimensions()-1)\r\n            {\r\n                sb.append(\",\");\r\n            }\r\n        }\r\n        sb.append(\")\");\r\n        return sb.toString();\r\n    }\r\n    \r\n    @Override\r\n    public int hashCode()\r\n    {\r\n        return Arrays.hashCode(values);\r\n    }\r\n\r\n    @Override\r\n    public boolean equals(Object object)\r\n    {\r\n        if (this == object)\r\n        {\r\n            return true;\r\n        }\r\n        if (object == null)\r\n        {\r\n            return false;\r\n        }\r\n        if (object instanceof DefaultFloatTuple)\r\n        {\r\n            DefaultFloatTuple other = (DefaultFloatTuple)object;\r\n            return Arrays.equals(values, other.values);\r\n        }\r\n        if (object instanceof FloatTuple)\r\n        {\r\n            FloatTuple other = (FloatTuple)object;\r\n            if (other.getDimensions() != getDimensions())\r\n            {\r\n                return false;\r\n            }\r\n            for (int i=0; i<getDimensions(); i++)\r\n            {\r\n                if (get(i) != other.get(i))\r\n                {\r\n                    return false;\r\n                }\r\n            }\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n\r\n    \r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/DefaultMtl.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n *\r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\n/**\r\n * Default implementation of an Mtl (material)\r\n */\r\nfinal class DefaultMtl implements Mtl\r\n{\r\n    /**\r\n     * The name of this material\r\n     */\r\n    private final String name;\r\n\r\n    /**\r\n     * The illumination mode\r\n     */\r\n    private Integer illum;\r\n\r\n    /**\r\n     * The optical density\r\n     */\r\n    private Float ni;\r\n\r\n    /**\r\n     * The transmission filter\r\n     */\r\n    private FloatTuple tf;\r\n\r\n    /**\r\n     * The sharpness of reflections\r\n     */\r\n    private Float sharpness;\r\n\r\n    /**\r\n     * The ambient part of this material\r\n     */\r\n    private FloatTuple ka;\r\n\r\n    /**\r\n     * The ambient map texture options\r\n     */\r\n    private TextureOptions mapKaOptions;\r\n\r\n    /**\r\n     * The diffuse part of this material\r\n     */\r\n    private FloatTuple kd;\r\n\r\n    /**\r\n     * The diffuse map texture options\r\n     */\r\n    private TextureOptions mapKdOptions;\r\n\r\n    /**\r\n     * The specular part of this material\r\n     */\r\n    private FloatTuple ks;\r\n\r\n    /**\r\n     * The specular map texture options\r\n     */\r\n    private TextureOptions mapKsOptions;\r\n\r\n    /**\r\n     * The shininess of this material\r\n     */\r\n    private Float ns;\r\n\r\n    /**\r\n     * The shininess map texture options\r\n     */\r\n    private TextureOptions mapNsOptions;\r\n\r\n    /**\r\n     * The opacity of this material\r\n     */\r\n    private Float d;\r\n\r\n    /**\r\n     * The halo flag for the opacity\r\n     */\r\n    private Boolean halo;\r\n\r\n    /**\r\n     * The opacity map texture options\r\n     */\r\n    private TextureOptions mapDOptions;\r\n\r\n    /**\r\n     * The bump map texture options\r\n     */\r\n    private TextureOptions bumpOptions;\r\n\r\n    /**\r\n     * The displacement map texture options\r\n     */\r\n    private TextureOptions dispOptions;\r\n\r\n    /**\r\n     * The decal map texture options\r\n     */\r\n    private TextureOptions decalOptions;\r\n\r\n    /**\r\n     * The reflection map texture options\r\n     */\r\n    private final List<TextureOptions> reflOptions;\r\n    \r\n    // PBR Parameters:\r\n    \r\n    /**\r\n     * The roughness of this material\r\n     */\r\n    private Float pr;\r\n\r\n    /**\r\n     * The roughness map texture options\r\n     */\r\n    private TextureOptions mapPrOptions;\r\n\r\n    /**\r\n     * The metallic part of this material\r\n     */\r\n    private Float pm;\r\n\r\n    /**\r\n     * The metallic map texture options\r\n     */\r\n    private TextureOptions mapPmOptions;\r\n\r\n    /**\r\n     * The sheen part of this material\r\n     */\r\n    private Float ps;\r\n\r\n    /**\r\n     * The sheen map texture options\r\n     */\r\n    private TextureOptions mapPsOptions;\r\n\r\n    /**\r\n     * The clearcoat thickness of this material\r\n     */\r\n    private Float pc;\r\n\r\n    /**\r\n     * The clearcoat roughness of this material\r\n     */\r\n    private Float pcr;\r\n    \r\n    /**\r\n     * The emissive part of this material\r\n     */\r\n    private FloatTuple ke;\r\n\r\n    /**\r\n     * The emissive map texture options\r\n     */\r\n    private TextureOptions mapKeOptions;\r\n\r\n    /**\r\n     * The anisotropy of this material\r\n     */\r\n    private Float aniso;\r\n    \r\n    /**\r\n     * The anisotropy rotation of this material\r\n     */\r\n    private Float anisor;\r\n    \r\n    /**\r\n     * The normal map texture options\r\n     */\r\n    private TextureOptions normOptions;\r\n\r\n    /**\r\n     * Creates a new material with the given name\r\n     *\r\n     * @param name The name of this material\r\n     */\r\n    DefaultMtl(String name)\r\n    {\r\n        this.name = name;\r\n        this.reflOptions = new ArrayList<>();\r\n    }\r\n\r\n    @Override\r\n    public String getName()\r\n    {\r\n        return name;\r\n    }\r\n\r\n    @Override\r\n    public Integer getIllum()\r\n    {\r\n        return illum;\r\n    }\r\n\r\n    @Override\r\n    public void setIllum(Integer illum)\r\n    {\r\n        this.illum = illum;\r\n    }\r\n\r\n    @Override\r\n    public Float getNi()\r\n    {\r\n        return ni;\r\n    }\r\n\r\n    @Override\r\n    public void setNi(Float ni)\r\n    {\r\n        this.ni = ni;\r\n    }\r\n\r\n    @Override\r\n    public FloatTuple getTf()\r\n    {\r\n        return tf;\r\n    }\r\n\r\n    @Override\r\n    public void setTf(Float r, Float g, Float b)\r\n    {\r\n        this.tf = Utils.createRgbTuple(r, g, b);\r\n    }\r\n\r\n    @Override\r\n    public Float getSharpness()\r\n    {\r\n        return sharpness;\r\n    }\r\n\r\n    @Override\r\n    public void setSharpness(Float sharpness)\r\n    {\r\n        this.sharpness = sharpness;\r\n    }\r\n\r\n\r\n    @Override\r\n    public FloatTuple getKa()\r\n    {\r\n        return ka;\r\n    }\r\n\r\n    @Override\r\n    public void setKa(Float r, Float g, Float b)\r\n    {\r\n        this.ka = Utils.createRgbTuple(r, g, b);\r\n    }\r\n\r\n    @Override\r\n    public String getMapKa()\r\n    {\r\n        if (mapKaOptions == null)\r\n        {\r\n            return null;\r\n        }\r\n        return mapKaOptions.getFileName();\r\n    }\r\n\r\n    @Override\r\n    public void setMapKa(String mapKa)\r\n    {\r\n        if (mapKaOptions == null)\r\n        {\r\n            mapKaOptions = new DefaultTextureOptions();\r\n        }\r\n        mapKaOptions.setFileName(mapKa);\r\n    }\r\n\r\n    @Override\r\n    public TextureOptions getMapKaOptions()\r\n    {\r\n        return mapKaOptions;\r\n    }\r\n\r\n    @Override\r\n    public void setMapKaOptions(TextureOptions options)\r\n    {\r\n        this.mapKaOptions = options;\r\n    }\r\n\r\n    @Override\r\n    public FloatTuple getKd()\r\n    {\r\n        return kd;\r\n    }\r\n\r\n    @Override\r\n    public void setKd(Float r, Float g, Float b)\r\n    {\r\n        this.kd = Utils.createRgbTuple(r, g, b);\r\n    }\r\n\r\n    @Override\r\n    public String getMapKd()\r\n    {\r\n        if (mapKdOptions == null)\r\n        {\r\n            return null;\r\n        }\r\n        return mapKdOptions.getFileName();\r\n    }\r\n\r\n    @Override\r\n    public void setMapKd(String mapKd)\r\n    {\r\n        if (mapKdOptions == null)\r\n        {\r\n            mapKdOptions = new DefaultTextureOptions();\r\n        }\r\n        mapKdOptions.setFileName(mapKd);\r\n    }\r\n\r\n    @Override\r\n    public TextureOptions getMapKdOptions()\r\n    {\r\n        return mapKdOptions;\r\n    }\r\n\r\n    @Override\r\n    public void setMapKdOptions(TextureOptions options)\r\n    {\r\n        this.mapKdOptions = options;\r\n    }\r\n\r\n    @Override\r\n    public FloatTuple getKs()\r\n    {\r\n        return ks;\r\n    }\r\n\r\n    @Override\r\n    public void setKs(Float r, Float g, Float b)\r\n    {\r\n        this.ks = Utils.createRgbTuple(r, g, b);\r\n    }\r\n\r\n    @Override\r\n    public String getMapKs()\r\n    {\r\n        if (mapKsOptions == null)\r\n        {\r\n            return null;\r\n        }\r\n        return mapKsOptions.getFileName();\r\n    }\r\n\r\n    @Override\r\n    public void setMapKs(String mapKs)\r\n    {\r\n        if (mapKsOptions == null)\r\n        {\r\n            mapKsOptions = new DefaultTextureOptions();\r\n        }\r\n        mapKsOptions.setFileName(mapKs);\r\n    }\r\n\r\n    @Override\r\n    public TextureOptions getMapKsOptions()\r\n    {\r\n        return mapKsOptions;\r\n    }\r\n\r\n    @Override\r\n    public void setMapKsOptions(TextureOptions options)\r\n    {\r\n        this.mapKsOptions = options;\r\n    }\r\n\r\n    @Override\r\n    public Float getNs()\r\n    {\r\n        return ns;\r\n    }\r\n\r\n    @Override\r\n    public void setNs(Float ns)\r\n    {\r\n        this.ns = ns;\r\n    }\r\n\r\n    @Override\r\n    public String getMapNs()\r\n    {\r\n        if (mapNsOptions == null)\r\n        {\r\n            return null;\r\n        }\r\n        return mapNsOptions.getFileName();\r\n    }\r\n\r\n    @Override\r\n    public void setMapNs(String mapNs)\r\n    {\r\n        if (mapNsOptions == null)\r\n        {\r\n            mapNsOptions = new DefaultTextureOptions();\r\n        }\r\n        mapNsOptions.setFileName(mapNs);\r\n    }\r\n\r\n    @Override\r\n    public TextureOptions getMapNsOptions()\r\n    {\r\n        return mapNsOptions;\r\n    }\r\n\r\n    @Override\r\n    public void setMapNsOptions(TextureOptions options)\r\n    {\r\n        this.mapNsOptions = options;\r\n    }\r\n\r\n    @Override\r\n    public Float getD()\r\n    {\r\n        return d;\r\n    }\r\n\r\n    @Override\r\n    public void setD(Float d)\r\n    {\r\n        this.d = d;\r\n    }\r\n\r\n    @Override\r\n    public Boolean isHalo()\r\n    {\r\n        return halo;\r\n    }\r\n\r\n    @Override\r\n    public void setHalo(Boolean halo)\r\n    {\r\n        this.halo = halo;\r\n    }\r\n\r\n    @Override\r\n    public String getMapD()\r\n    {\r\n        if (mapDOptions == null)\r\n        {\r\n            return null;\r\n        }\r\n        return mapDOptions.getFileName();\r\n    }\r\n\r\n    @Override\r\n    public void setMapD(String mapD)\r\n    {\r\n        if (mapDOptions == null)\r\n        {\r\n            mapDOptions = new DefaultTextureOptions();\r\n        }\r\n        mapDOptions.setFileName(mapD);\r\n    }\r\n\r\n    @Override\r\n    public TextureOptions getMapDOptions()\r\n    {\r\n        return mapDOptions;\r\n    }\r\n\r\n    @Override\r\n    public void setMapDOptions(TextureOptions options)\r\n    {\r\n        this.mapDOptions = options;\r\n    }\r\n\r\n    @Override\r\n    public String getBump()\r\n    {\r\n        if (bumpOptions == null)\r\n        {\r\n            return null;\r\n        }\r\n        return bumpOptions.getFileName();\r\n    }\r\n\r\n    @Override\r\n    public void setBump(String bump)\r\n    {\r\n        if (bumpOptions == null)\r\n        {\r\n            bumpOptions = new DefaultTextureOptions();\r\n        }\r\n        bumpOptions.setFileName(bump);\r\n    }\r\n\r\n    @Override\r\n    public TextureOptions getBumpOptions()\r\n    {\r\n        return bumpOptions;\r\n    }\r\n\r\n    @Override\r\n    public void setBumpOptions(TextureOptions options)\r\n    {\r\n        this.bumpOptions = options;\r\n    }\r\n\r\n    @Override\r\n    public String getDisp()\r\n    {\r\n        if (dispOptions == null)\r\n        {\r\n            return null;\r\n        }\r\n        return dispOptions.getFileName();\r\n    }\r\n\r\n    @Override\r\n    public void setDisp(String disp)\r\n    {\r\n        if (dispOptions == null)\r\n        {\r\n            dispOptions = new DefaultTextureOptions();\r\n        }\r\n        dispOptions.setFileName(disp);\r\n    }\r\n\r\n    @Override\r\n    public TextureOptions getDispOptions()\r\n    {\r\n        return dispOptions;\r\n    }\r\n\r\n    @Override\r\n    public void setDispOptions(TextureOptions options)\r\n    {\r\n        this.dispOptions = options;\r\n    }\r\n\r\n    @Override\r\n    public String getDecal()\r\n    {\r\n        if (decalOptions == null)\r\n        {\r\n            return null;\r\n        }\r\n        return decalOptions.getFileName();\r\n    }\r\n\r\n    @Override\r\n    public void setDecal(String decal)\r\n    {\r\n        if (decalOptions == null)\r\n        {\r\n            decalOptions = new DefaultTextureOptions();\r\n        }\r\n        decalOptions.setFileName(decal);\r\n    }\r\n\r\n    @Override\r\n    public TextureOptions getDecalOptions()\r\n    {\r\n        return decalOptions;\r\n    }\r\n\r\n    @Override\r\n    public void setDecalOptions(TextureOptions options)\r\n    {\r\n        this.decalOptions = options;\r\n    }\r\n\r\n    @Override\r\n    public List<TextureOptions> getReflOptions()\r\n    {\r\n        return reflOptions;\r\n    }\r\n    \r\n    // PRB parameters\r\n    \r\n    @Override\r\n    public Float getPr()\r\n    {\r\n        return pr;\r\n    }\r\n\r\n    @Override\r\n    public void setPr(Float pr)\r\n    {\r\n        this.pr = pr;\r\n    }\r\n\r\n    @Override\r\n    public String getMapPr()\r\n    {\r\n        if (mapPrOptions == null)\r\n        {\r\n            return null;\r\n        }\r\n        return mapPrOptions.getFileName();\r\n    }\r\n\r\n    @Override\r\n    public void setMapPr(String mapPr)\r\n    {\r\n        if (mapPrOptions == null)\r\n        {\r\n            mapPrOptions = new DefaultTextureOptions();\r\n        }\r\n        mapPrOptions.setFileName(mapPr);\r\n    }\r\n\r\n    @Override\r\n    public TextureOptions getMapPrOptions()\r\n    {\r\n        return mapPrOptions;\r\n    }\r\n\r\n    @Override\r\n    public void setMapPrOptions(TextureOptions options)\r\n    {\r\n        this.mapPrOptions = options;\r\n    }\r\n\r\n    @Override\r\n    public Float getPm()\r\n    {\r\n        return pm;\r\n    }\r\n\r\n    @Override\r\n    public void setPm(Float pm)\r\n    {\r\n        this.pm = pm;\r\n    }\r\n\r\n    @Override\r\n    public String getMapPm()\r\n    {\r\n        if (mapPmOptions == null)\r\n        {\r\n            return null;\r\n        }\r\n        return mapPmOptions.getFileName();\r\n    }\r\n\r\n    @Override\r\n    public void setMapPm(String mapPm)\r\n    {\r\n        if (mapPmOptions == null)\r\n        {\r\n            mapPmOptions = new DefaultTextureOptions();\r\n        }\r\n        mapPmOptions.setFileName(mapPm);\r\n    }\r\n\r\n    @Override\r\n    public TextureOptions getMapPmOptions()\r\n    {\r\n        return mapPmOptions;\r\n    }\r\n\r\n    @Override\r\n    public void setMapPmOptions(TextureOptions options)\r\n    {\r\n        this.mapPmOptions = options;\r\n    }\r\n\r\n    @Override\r\n    public Float getPs()\r\n    {\r\n        return ps;\r\n    }\r\n\r\n    @Override\r\n    public void setPs(Float ps)\r\n    {\r\n        this.ps = ps;\r\n    }\r\n\r\n    @Override\r\n    public String getMapPs()\r\n    {\r\n        if (mapPsOptions == null)\r\n        {\r\n            return null;\r\n        }\r\n        return mapPsOptions.getFileName();\r\n    }\r\n\r\n    @Override\r\n    public void setMapPs(String mapPs)\r\n    {\r\n        if (mapPsOptions == null)\r\n        {\r\n            mapPsOptions = new DefaultTextureOptions();\r\n        }\r\n        mapPsOptions.setFileName(mapPs);\r\n    }\r\n\r\n    @Override\r\n    public TextureOptions getMapPsOptions()\r\n    {\r\n        return mapPsOptions;\r\n    }\r\n\r\n    @Override\r\n    public void setMapPsOptions(TextureOptions options)\r\n    {\r\n        this.mapPsOptions = options;\r\n    }\r\n\r\n    @Override\r\n    public Float getPc()\r\n    {\r\n        return pc;\r\n    }\r\n\r\n    @Override\r\n    public void setPc(Float pc)\r\n    {\r\n        this.pc = pc;\r\n    }\r\n\r\n    @Override\r\n    public Float getPcr()\r\n    {\r\n        return pcr;\r\n    }\r\n\r\n    @Override\r\n    public void setPcr(Float pcr)\r\n    {\r\n        this.pcr = pcr;\r\n    }\r\n\r\n    @Override\r\n    public FloatTuple getKe()\r\n    {\r\n        return ke;\r\n    }\r\n\r\n    @Override\r\n    public void setKe(Float r, Float g, Float b)\r\n    {\r\n        this.ke = Utils.createRgbTuple(r, g, b);\r\n    }\r\n\r\n    @Override\r\n    public String getMapKe()\r\n    {\r\n        if (mapKeOptions == null)\r\n        {\r\n            return null;\r\n        }\r\n        return mapKeOptions.getFileName();\r\n    }\r\n\r\n    @Override\r\n    public void setMapKe(String mapKe)\r\n    {\r\n        if (mapKeOptions == null)\r\n        {\r\n            mapKeOptions = new DefaultTextureOptions();\r\n        }\r\n        mapKeOptions.setFileName(mapKe);\r\n    }\r\n\r\n    @Override\r\n    public TextureOptions getMapKeOptions()\r\n    {\r\n        return mapKeOptions;\r\n    }\r\n\r\n    @Override\r\n    public void setMapKeOptions(TextureOptions options)\r\n    {\r\n        this.mapKeOptions = options;\r\n    }\r\n\r\n    @Override\r\n    public Float getAniso()\r\n    {\r\n        return aniso;\r\n    }\r\n\r\n    @Override\r\n    public void setAniso(Float aniso)\r\n    {\r\n        this.aniso = aniso;\r\n    }\r\n\r\n    @Override\r\n    public Float getAnisor()\r\n    {\r\n        return anisor;\r\n    }\r\n\r\n    @Override\r\n    public void setAnisor(Float anisor)\r\n    {\r\n        this.anisor = anisor;\r\n    }\r\n\r\n    @Override\r\n    public String getNorm()\r\n    {\r\n        if (normOptions == null)\r\n        {\r\n            return null;\r\n        }\r\n        return normOptions.getFileName();\r\n    }\r\n\r\n    @Override\r\n    public void setNorm(String norm)\r\n    {\r\n        if (normOptions == null)\r\n        {\r\n            normOptions = new DefaultTextureOptions();\r\n        }\r\n        normOptions.setFileName(norm);\r\n    }\r\n\r\n    @Override\r\n    public TextureOptions getNormOptions()\r\n    {\r\n        return normOptions;\r\n    }\r\n\r\n    @Override\r\n    public void setNormOptions(TextureOptions options)\r\n    {\r\n        this.normOptions = options;\r\n    }\r\n\r\n    @Override\r\n    public String toString()\r\n    {\r\n        StringBuilder sb = new StringBuilder();\r\n        sb.append(\"Mtl\");\r\n        sb.append(\"[\");\r\n        sb.append(\"name=\").append(getName());\r\n        sb.append(\"]\");\r\n        return sb.toString();\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/DefaultObj.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.Collection;\r\nimport java.util.Collections;\r\nimport java.util.HashMap;\r\nimport java.util.LinkedHashMap;\r\nimport java.util.LinkedHashSet;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.Objects;\r\nimport java.util.Set;\r\n\r\n/**\r\n * Default implementation of an {@link Obj}\r\n */\r\nfinal class DefaultObj implements Obj\r\n{\r\n    /**\r\n     * The vertices in this Obj\r\n     */\r\n    private final List<FloatTuple> vertices;\r\n    \r\n    /**\r\n     * The texture coordinates in this Obj.\r\n     */\r\n    private final List<FloatTuple> texCoords;\r\n\r\n    /**\r\n     * The normals in this Obj\r\n     */\r\n    private final List<FloatTuple> normals;\r\n\r\n    /**\r\n     * The faces in this Obj.\r\n     */\r\n    private final List<ObjFace> faces;\r\n\r\n    /**\r\n     * The groups in this Obj.\r\n     */\r\n    private final List<ObjGroup> groups;\r\n    \r\n    /**\r\n     * The material groups in this Obj.\r\n     */\r\n    private final List<ObjGroup> materialGroups;\r\n\r\n    /** \r\n     * Maps a group name to a group \r\n     */\r\n    private final Map<String, DefaultObjGroup> groupMap;\r\n\r\n    /** \r\n     * Maps a material name to a material group \r\n     */\r\n    private final Map<String, DefaultObjGroup> materialGroupMap;\r\n\r\n    /**\r\n     * The names of the MTL files for this Obj.\r\n     */\r\n    private List<String> mtlFileNames = Collections.emptyList();\r\n    \r\n    /**\r\n     * A map from the faces to the names of the groups that started\r\n     * at this face\r\n     */\r\n    private final Map<ObjFace, Set<String>> startedGroupNames;\r\n\r\n    /**\r\n     * A map from the faces to the name of the material group that started\r\n     * at this face\r\n     */\r\n    private final Map<ObjFace, String> startedMaterialGroupNames;\r\n    \r\n    /**\r\n     * The names for the groups that should be used for faces that are\r\n     * added subsequently \r\n     */\r\n    private Set<String> nextActiveGroupNames = null;\r\n    \r\n    /**\r\n     * The name for the material group that should be used for faces that are\r\n     * added subsequently \r\n     */\r\n    private String nextActiveMaterialGroupName = null;\r\n\r\n    /**\r\n     * The groups that are currently active, and to which faces will be\r\n     * added \r\n     */\r\n    private List<DefaultObjGroup> activeGroups = null;\r\n\r\n    /** \r\n     * The names of the groups that faces are currently added to\r\n     */\r\n    private Set<String> activeGroupNames = null;\r\n\r\n    /**\r\n     * The material group that is currently active, and to which faces will be\r\n     * added \r\n     */\r\n    private DefaultObjGroup activeMaterialGroup = null;\r\n    \r\n    /**\r\n     * The name of the material group that is currently active\r\n     */\r\n    private String activeMaterialGroupName = null;\r\n\r\n    /**\r\n     * Creates a new, empty DefaultObj.\r\n     */\r\n    DefaultObj()\r\n    {\r\n        vertices = new ArrayList<>();\r\n        normals = new ArrayList<>();\r\n        texCoords = new ArrayList<>();\r\n        faces = new ArrayList<>();\r\n\r\n        groups = new ArrayList<>();\r\n        materialGroups = new ArrayList<>();\r\n\r\n        groupMap = new LinkedHashMap<>();\r\n        materialGroupMap = new LinkedHashMap<>();\r\n        \r\n        startedGroupNames = new HashMap<>();\r\n        startedMaterialGroupNames = new HashMap<>();\r\n        \r\n        setActiveGroupNames(Arrays.asList(\"default\"));\r\n        getGroupInternal(\"default\");\r\n    }\r\n\r\n\r\n    @Override\r\n    public int getNumVertices()\r\n    {\r\n        return vertices.size();\r\n    }\r\n\r\n    @Override\r\n    public FloatTuple getVertex(int index)\r\n    {\r\n        return vertices.get(index);\r\n    }\r\n\r\n    @Override\r\n    public int getNumTexCoords()\r\n    {\r\n        return texCoords.size();\r\n    }\r\n\r\n    @Override\r\n    public FloatTuple getTexCoord(int index)\r\n    {\r\n        return texCoords.get(index);\r\n    }\r\n\r\n    @Override\r\n    public int getNumNormals()\r\n    {\r\n        return normals.size();\r\n    }\r\n\r\n    @Override\r\n    public FloatTuple getNormal(int index)\r\n    {\r\n        return normals.get(index);\r\n    }\r\n\r\n\r\n    @Override\r\n    public int getNumFaces()\r\n    {\r\n        return faces.size();\r\n    }\r\n\r\n    @Override\r\n    public ObjFace getFace(int index)\r\n    {\r\n        return faces.get(index);\r\n    }\r\n    \r\n    @Override\r\n    public Set<String> getActivatedGroupNames(ObjFace face)\r\n    {\r\n        return startedGroupNames.get(face);\r\n    }\r\n    \r\n    @Override\r\n    public String getActivatedMaterialGroupName(ObjFace face)\r\n    {\r\n        return startedMaterialGroupNames.get(face);\r\n    }\r\n\r\n    @Override\r\n    public int getNumGroups()\r\n    {\r\n        return groups.size();\r\n    }\r\n\r\n    @Override\r\n    public ObjGroup getGroup(int index)\r\n    {\r\n        return groups.get(index);\r\n    }\r\n\r\n    @Override\r\n    public ObjGroup getGroup(String name)\r\n    {\r\n        return groupMap.get(name);\r\n    }\r\n\r\n    @Override\r\n    public int getNumMaterialGroups()\r\n    {\r\n        return materialGroups.size();\r\n    }\r\n\r\n    @Override\r\n    public ObjGroup getMaterialGroup(int index)\r\n    {\r\n        return materialGroups.get(index);\r\n    }\r\n\r\n    @Override\r\n    public ObjGroup getMaterialGroup(String name)\r\n    {\r\n        return materialGroupMap.get(name);\r\n    }\r\n\r\n\r\n    @Override\r\n    public List<String> getMtlFileNames()\r\n    {\r\n        return mtlFileNames;\r\n    }\r\n\r\n\r\n    \r\n    \r\n    @Override\r\n    public void addVertex(FloatTuple vertex)\r\n    {\r\n        Objects.requireNonNull(vertex, \"The vertex is null\");\r\n        vertices.add(vertex);\r\n    }\r\n    \r\n    @Override\r\n    public void addVertex(float x, float y, float z)\r\n    {\r\n        vertices.add(new DefaultFloatTuple(x, y, z));\r\n    }\r\n    \r\n    @Override\r\n    public void addTexCoord(FloatTuple texCoord)\r\n    {\r\n        Objects.requireNonNull(texCoord, \"The texCoord is null\");\r\n        texCoords.add(texCoord);\r\n    }\r\n    \r\n    @Override\r\n    public void addTexCoord(float x)\r\n    {\r\n        texCoords.add(new DefaultFloatTuple(x));\r\n    }\r\n    \r\n    @Override\r\n    public void addTexCoord(float x, float y)\r\n    {\r\n        texCoords.add(new DefaultFloatTuple(x, y));\r\n    }\r\n    \r\n    @Override\r\n    public void addTexCoord(float x, float y, float z)\r\n    {\r\n        texCoords.add(new DefaultFloatTuple(x, y, z));\r\n    }\r\n    \r\n\r\n    @Override\r\n    public void addNormal(FloatTuple normal)\r\n    {\r\n        Objects.requireNonNull(normal, \"The normal is null\");\r\n        normals.add(normal);\r\n    }\r\n\r\n    @Override\r\n    public void addNormal(float x, float y, float z)\r\n    {\r\n        normals.add(new DefaultFloatTuple(x, y, z));\r\n    }\r\n    \r\n    @Override\r\n    public void setActiveGroupNames(Collection<? extends String> groupNames)\r\n    {\r\n        if (groupNames == null)\r\n        {\r\n            return;\r\n        }\r\n        if (groupNames.size() == 0)\r\n        {\r\n            groupNames = Arrays.asList(\"default\");\r\n        }\r\n        else if (groupNames.contains(null))\r\n        {\r\n            throw new NullPointerException(\"The groupNames contains null\");\r\n        }\r\n        nextActiveGroupNames = \r\n            Collections.unmodifiableSet(new LinkedHashSet<String>(groupNames));\r\n    }\r\n    \r\n    \r\n    @Override\r\n    public void setActiveMaterialGroupName(String materialGroupName)\r\n    {\r\n        if (materialGroupName == null)\r\n        {\r\n            return;\r\n        }\r\n        nextActiveMaterialGroupName = materialGroupName;\r\n    }\r\n    \r\n    @Override\r\n    public void addFace(ObjFace face)\r\n    {\r\n        if (face == null)\r\n        {\r\n            throw new NullPointerException(\"The face is null\");\r\n        }\r\n        if (nextActiveGroupNames != null)\r\n        {\r\n            activeGroups = getGroupsInternal(nextActiveGroupNames);\r\n            if (!nextActiveGroupNames.equals(activeGroupNames))\r\n            {\r\n                startedGroupNames.put(face, nextActiveGroupNames);\r\n            }\r\n            activeGroupNames = nextActiveGroupNames;\r\n            nextActiveGroupNames = null;\r\n        }\r\n        if (nextActiveMaterialGroupName != null)\r\n        {\r\n            activeMaterialGroup = \r\n                getMaterialGroupInternal(nextActiveMaterialGroupName);\r\n            if (!nextActiveMaterialGroupName.equals(activeMaterialGroupName))\r\n            {\r\n                startedMaterialGroupNames.put(face, nextActiveMaterialGroupName);\r\n            }\r\n            activeMaterialGroupName = nextActiveMaterialGroupName;\r\n            nextActiveMaterialGroupName = null;\r\n        }\r\n        faces.add(face);\r\n        if (activeMaterialGroup != null)\r\n        {\r\n            activeMaterialGroup.addFace(face);\r\n        }\r\n        for (DefaultObjGroup group : activeGroups)\r\n        {\r\n            group.addFace(face);\r\n        }\r\n    }\r\n    \r\n\r\n    @Override\r\n    public void addFace(int ... v)\r\n    {\r\n        addFace(v, null, null);\r\n    }\r\n\r\n    @Override\r\n    public void addFaceWithTexCoords(int... v)\r\n    {\r\n        addFace(v, v, null);\r\n    }\r\n\r\n    @Override\r\n    public void addFaceWithNormals(int... v)\r\n    {\r\n        addFace(v, null, v);\r\n    }\r\n\r\n    @Override\r\n    public void addFaceWithAll(int... v)\r\n    {\r\n        addFace(v, v, v);\r\n    }\r\n    \r\n    @Override\r\n    public void addFace(int[] v, int[] vt, int[] vn)\r\n    {\r\n        Objects.requireNonNull(v, \"The vertex indices are null\");\r\n        checkIndices(v, getNumVertices(), \"Vertex\");\r\n        checkIndices(vt, getNumTexCoords(), \"TexCoord\");\r\n        checkIndices(vn, getNumNormals(), \"Normal\");\r\n        DefaultObjFace face = new DefaultObjFace(v, vt, vn);\r\n        addFace(face);\r\n    }\r\n    \r\n\r\n    @Override\r\n    public void setMtlFileNames(Collection<? extends String> mtlFileNames)\r\n    {\r\n        this.mtlFileNames = Collections.unmodifiableList(\r\n            new ArrayList<String>(mtlFileNames));\r\n    }\r\n\r\n    \r\n    @Override\r\n    public String toString()\r\n    {\r\n        return \"Obj[\" +\r\n            \"#vertices=\"+ vertices.size() + \",\" +\r\n            \"#texCoords=\" + texCoords.size() + \",\" +\r\n            \"#normals=\" + normals.size() + \",\" +\r\n            \"#faces=\" + faces.size() + \",\" +\r\n            \"#groups=\" + groups.size() + \",\" +\r\n            \"#materialGroups=\" + materialGroups.size() + \",\" +\r\n            \"mtlFileNames=\" + mtlFileNames + \"]\";\r\n    }\r\n\r\n    /**\r\n     * Returns a set containing all groups with the given names. If the\r\n     * groups with the given names do not exist, they are created and\r\n     * added to this Obj.\r\n     * \r\n     * @param groupNames The group names\r\n     * @return The groups\r\n     */\r\n    private List<DefaultObjGroup> getGroupsInternal(\r\n        Collection<? extends String> groupNames)\r\n    {\r\n        List<DefaultObjGroup> groups =\r\n                new ArrayList<>(groupNames.size());\r\n        for (String groupName : groupNames)\r\n        {\r\n            DefaultObjGroup group = getGroupInternal(groupName);\r\n            groups.add(group);\r\n        }\r\n        return groups;\r\n    }\r\n    \r\n    /**\r\n     * Returns the group with the given names. If the group with the given \r\n     * name does not exist, it is created and added to this Obj.\r\n     * \r\n     * @param groupName The group name\r\n     * @return The group\r\n     */\r\n    private DefaultObjGroup getGroupInternal(String groupName)\r\n    {\r\n        DefaultObjGroup group = groupMap.get(groupName);\r\n        if (group == null)\r\n        {\r\n            group = new DefaultObjGroup(groupName);\r\n            groupMap.put(groupName, group);\r\n            groups.add(group);\r\n        }\r\n        return group;\r\n    }\r\n\r\n    /**\r\n     * Returns the material group with the given names. If the material group \r\n     * with the given name does not exist, it is created and added to this Obj.\r\n     * \r\n     * @param materialGroupName The material group name\r\n     * @return The material group\r\n     */\r\n    private DefaultObjGroup getMaterialGroupInternal(String materialGroupName)\r\n    {\r\n        DefaultObjGroup group = materialGroupMap.get(materialGroupName);\r\n        if (group == null)\r\n        {\r\n            group = new DefaultObjGroup(materialGroupName);\r\n            materialGroupMap.put(materialGroupName, group);\r\n            materialGroups.add(group);\r\n        }\r\n        return group;\r\n    }\r\n\r\n    /**\r\n     * If the given indices are <code>null</code>, then this method will\r\n     * do nothing. Otherwise, it will check whether the given indices \r\n     * are valid, and throw an IllegalArgumentException if not. They\r\n     * are valid when they are all not negative, and all smaller than \r\n     * the given maximum.\r\n     * \r\n     * @param indices The indices\r\n     * @param max The maximum index, exclusive\r\n     * @param name The name of the index set\r\n     * @throws IllegalArgumentException If the given indices are not valid\r\n     */\r\n    private static void checkIndices(int[] indices, int max, String name)\r\n    {\r\n        if (indices == null)\r\n        {\r\n            return;\r\n        }\r\n        for (int index : indices) {\r\n            if (index < 0) {\r\n                throw new IllegalArgumentException(\r\n                        name + \" index is negative: \" + index);\r\n            }\r\n            if (index >= max) {\r\n                throw new IllegalArgumentException(\r\n                        name + \" index is \" + index +\r\n                                \", but must be smaller than \" + max);\r\n            }\r\n        }\r\n    }\r\n    \r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/DefaultObjFace.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\n\r\n\r\n/**\r\n * Default implementation of an ObjFace\r\n */\r\nfinal class DefaultObjFace implements ObjFace\r\n{\r\n    /**\r\n     * The vertex indices of this face\r\n     */\r\n    private final int[] vertexIndices;\r\n    \r\n    /**\r\n     * The texture coordinate indices of this face\r\n     */\r\n    private final int[] texCoordIndices;\r\n    \r\n    /**\r\n     * The normal indices of this face\r\n     */\r\n    private final int[] normalIndices;\r\n\r\n    /**\r\n     * Creates a face from the given parameters. References to the\r\n     * given objects will be stored.\r\n     * \r\n     * @param vertexIndices The vertex indices\r\n     * @param texCoordIndices The texture coordinate indices\r\n     * @param normalIndices The normal indices\r\n     */\r\n    DefaultObjFace(\r\n            int[] vertexIndices, int[] texCoordIndices, int[] normalIndices)\r\n    {\r\n        this.vertexIndices = vertexIndices;\r\n        this.texCoordIndices = texCoordIndices;\r\n        this.normalIndices = normalIndices;\r\n    }\r\n\r\n\r\n    @Override\r\n    public boolean containsTexCoordIndices()\r\n    {\r\n        return texCoordIndices != null;\r\n    }\r\n\r\n    @Override\r\n    public boolean containsNormalIndices()\r\n    {\r\n        return normalIndices != null;\r\n    }\r\n\r\n    @Override\r\n    public int getVertexIndex(int number)\r\n    {\r\n        return this.vertexIndices[number];\r\n    }\r\n\r\n    @Override\r\n    public int getTexCoordIndex(int number)\r\n    {\r\n        return this.texCoordIndices[number];\r\n    }\r\n\r\n    @Override\r\n    public int getNormalIndex(int number)\r\n    {\r\n        return this.normalIndices[number];\r\n    }\r\n\r\n    /**\r\n     * Set the specified index to the given value\r\n     * \r\n     * @param n The index to set\r\n     * @param index The value of the index\r\n     */\r\n    void setVertexIndex(int n, int index)\r\n    {\r\n        vertexIndices[n] = index;\r\n    }\r\n\r\n    /**\r\n     * Set the specified index to the given value\r\n     * \r\n     * @param n The index to set\r\n     * @param index The value of the index\r\n     */\r\n    void setNormalIndex(int n, int index)\r\n    {\r\n        normalIndices[n] = index;\r\n    }\r\n\r\n    /**\r\n     * Set the specified index to the given value\r\n     * \r\n     * @param n The index to set\r\n     * @param index The value of the index\r\n     */\r\n    void setTexCoordIndex(int n, int index)\r\n    {\r\n        texCoordIndices[n] = index;\r\n    }\r\n\r\n    @Override\r\n    public int getNumVertices()\r\n    {\r\n        return this.vertexIndices.length;\r\n    }\r\n\r\n    @Override\r\n    public String toString()\r\n    {\r\n        String result = \"ObjFace[\";\r\n        for(int i = 0; i < getNumVertices(); i++)\r\n        {\r\n            result += vertexIndices[i];\r\n            if(texCoordIndices != null || normalIndices != null)\r\n            {\r\n                result += \"/\";\r\n            }\r\n            if(texCoordIndices != null)\r\n            {\r\n                result += texCoordIndices[i];\r\n            }\r\n            if(normalIndices != null)\r\n            {\r\n                result += \"/\" + normalIndices[i];\r\n            }\r\n            if(i < getNumVertices() - 1)\r\n            {\r\n                result += \" \";\r\n            }\r\n        }\r\n        result += \"]\";\r\n        return result;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/DefaultObjGroup.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\n/**\r\n * Default implementation of an ObjGroup\r\n */\r\nfinal class DefaultObjGroup implements ObjGroup\r\n{\r\n    /**\r\n     * The name of this group.\r\n     */\r\n    private String name;\r\n    \r\n    /**\r\n     * The faces in this group\r\n     */\r\n    private List<ObjFace> faces;\r\n\r\n    /**\r\n     * Creates a new ObjGroup with the given name\r\n     * \r\n     * @param name The name of this ObjGroup\r\n     */\r\n    DefaultObjGroup(String name)\r\n    {\r\n        this.name = name;\r\n        faces = new ArrayList<>();\r\n    }\r\n\r\n    @Override\r\n    public String getName()\r\n    {\r\n        return name;\r\n    }\r\n\r\n    /**\r\n     * Add the given face to this group\r\n     * \r\n     * @param face The face to add\r\n     */\r\n    void addFace(ObjFace face)\r\n    {\r\n        faces.add(face);\r\n    }\r\n\r\n    @Override\r\n    public int getNumFaces()\r\n    {\r\n        return faces.size();\r\n    }\r\n\r\n    @Override\r\n    public ObjFace getFace(int index)\r\n    {\r\n        return faces.get(index);\r\n    }\r\n\r\n    @Override\r\n    public String toString()\r\n    {\r\n        return \"ObjGroup[name=\" + name + \",#faces=\" + faces.size() + \"]\";\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/DefaultTextureOptions.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n *\r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\npackage de.javagl.obj;\r\n\r\nimport java.util.Objects;\r\n\r\n/**\r\n * Default implementation of {@link TextureOptions}\r\n */\r\nfinal class DefaultTextureOptions implements TextureOptions\r\n{\r\n    /**\r\n     * The file name\r\n     */\r\n    private String fileName;\r\n\r\n    /**\r\n     * The horizontal blending state\r\n     */\r\n    private Boolean blendu;\r\n\r\n    /**\r\n     * The vertical blending state\r\n     */\r\n    private Boolean blendv;\r\n\r\n    /**\r\n     * The color correction state\r\n     */\r\n    private Boolean cc;\r\n\r\n    /**\r\n     * The mip-map boost value\r\n     */\r\n    private Float boost;\r\n\r\n    /**\r\n     * The map modifiers\r\n     */\r\n    private FloatTuple mm;\r\n\r\n    /**\r\n     * The origin offset\r\n     */\r\n    private FloatTuple o;\r\n\r\n    /**\r\n     * The scale\r\n     */\r\n    private FloatTuple s;\r\n\r\n    /**\r\n     * The turbulence\r\n     */\r\n    private FloatTuple t;\r\n\r\n    /**\r\n     * The texture resolution\r\n     */\r\n    private Float texres;\r\n\r\n    /**\r\n     * The clamping state\r\n     */\r\n    private Boolean clamp;\r\n\r\n    /**\r\n     * The bump multiplier\r\n     */\r\n    private Float bm;\r\n\r\n    /**\r\n     * The IMF channel\r\n     */\r\n    private String imfchan;\r\n\r\n    /**\r\n     * The type\r\n     */\r\n    private String type;\r\n\r\n    /**\r\n     * Default constructor\r\n     */\r\n    DefaultTextureOptions()\r\n    {\r\n        // Default constructor\r\n    }\r\n\r\n    @Override\r\n    public String getFileName()\r\n    {\r\n        return fileName;\r\n    }\r\n\r\n    @Override\r\n    public void setFileName(String fileName)\r\n    {\r\n        this.fileName = fileName;\r\n    }\r\n\r\n    @Override\r\n    public Boolean isBlendu()\r\n    {\r\n        return blendu;\r\n    }\r\n\r\n    @Override\r\n    public void setBlendu(Boolean blendu)\r\n    {\r\n        this.blendu = blendu;\r\n    }\r\n\r\n    @Override\r\n    public Boolean isBlendv()\r\n    {\r\n        return blendv;\r\n    }\r\n\r\n    @Override\r\n    public void setBlendv(Boolean blendv)\r\n    {\r\n        this.blendv = blendv;\r\n    }\r\n\r\n    @Override\r\n    public Float getBoost()\r\n    {\r\n        return boost;\r\n    }\r\n\r\n    @Override\r\n    public Boolean isCc()\r\n    {\r\n        return cc;\r\n    }\r\n\r\n    @Override\r\n    public void setCc(Boolean cc)\r\n    {\r\n        this.cc = cc;\r\n    }\r\n\r\n    @Override\r\n    public void setBoost(Float boost)\r\n    {\r\n        this.boost = boost;\r\n    }\r\n\r\n    @Override\r\n    public FloatTuple getMm()\r\n    {\r\n        return mm;\r\n    }\r\n\r\n    @Override\r\n    public void setMm(Float base, Float gain)\r\n    {\r\n        if (base == null && gain == null)\r\n        {\r\n            this.mm = null;\r\n        }\r\n        float baseValue = (base == null ? 0.0f : base);\r\n        float gainValue = (gain == null ? 1.0f : gain);\r\n        this.mm = FloatTuples.create(baseValue, gainValue);\r\n    }\r\n\r\n    @Override\r\n    public FloatTuple getO()\r\n    {\r\n        return o;\r\n    }\r\n\r\n    @Override\r\n    public void setO(Float u, Float v, Float w)\r\n    {\r\n        this.o = Utils.createUvwTuple(u, v, w, 0.0f);\r\n    }\r\n\r\n    @Override\r\n    public FloatTuple getS()\r\n    {\r\n        return s;\r\n    }\r\n\r\n    @Override\r\n    public void setS(Float u, Float v, Float w)\r\n    {\r\n        this.s = Utils.createUvwTuple(u, v, w, 1.0f);\r\n    }\r\n\r\n    @Override\r\n    public FloatTuple getT()\r\n    {\r\n        return t;\r\n    }\r\n\r\n    @Override\r\n    public void setT(Float u, Float v, Float w)\r\n    {\r\n        this.t = Utils.createUvwTuple(u, v, w, 0.0f);\r\n    }\r\n\r\n    @Override\r\n    public Float getTexres()\r\n    {\r\n        return texres;\r\n    }\r\n\r\n    @Override\r\n    public void setTexres(Float texres)\r\n    {\r\n        this.texres = texres;\r\n    }\r\n\r\n    @Override\r\n    public Boolean isClamp()\r\n    {\r\n        return clamp;\r\n    }\r\n\r\n    @Override\r\n    public void setClamp(Boolean clamp)\r\n    {\r\n        this.clamp = clamp;\r\n    }\r\n\r\n    @Override\r\n    public Float getBm()\r\n    {\r\n        return bm;\r\n    }\r\n\r\n    @Override\r\n    public void setBm(Float bm)\r\n    {\r\n        this.bm = bm;\r\n    }\r\n\r\n    @Override\r\n    public String getImfchan()\r\n    {\r\n        return imfchan;\r\n    }\r\n\r\n    @Override\r\n    public void setImfchan(String imfchan)\r\n    {\r\n        this.imfchan = imfchan;\r\n    }\r\n\r\n    @Override\r\n    public String getType()\r\n    {\r\n        return type;\r\n    }\r\n\r\n    @Override\r\n    public void setType(String type)\r\n    {\r\n        this.type = type;\r\n    }\r\n\r\n    @Override\r\n    public String toString()\r\n    {\r\n        StringBuilder sb = new StringBuilder();\r\n        sb.append(\"TextureOptions\");\r\n        sb.append(\"[\");\r\n        sb.append(MtlWriter.createString(this));\r\n        sb.append(\"]\");\r\n        return sb.toString();\r\n    }\r\n\r\n    @Override\r\n    public int hashCode()\r\n    {\r\n        return Objects.hash(blendu, blendv, cc, bm, boost, clamp, fileName,\r\n            imfchan, mm, o, s, t, texres, type);\r\n    }\r\n\r\n    @Override\r\n    public boolean equals(Object object)\r\n    {\r\n        if (this == object)\r\n        {\r\n            return true;\r\n        }\r\n        if (object == null)\r\n        {\r\n            return false;\r\n        }\r\n        if (!(object instanceof TextureOptions))\r\n        {\r\n            return false;\r\n        }\r\n        TextureOptions other = (TextureOptions) object;\r\n\r\n        return\r\n            Objects.equals(isBlendu(), other.isBlendu()) &&\r\n            Objects.equals(isBlendv(), other.isBlendv()) &&\r\n            Objects.equals(isCc(), other.isCc()) &&\r\n            Objects.equals(getBm(), other.getBm()) &&\r\n            Objects.equals(getBoost(), other.getBoost()) &&\r\n            Objects.equals(isClamp(), other.isClamp()) &&\r\n            Objects.equals(getFileName(), other.getFileName()) &&\r\n            Objects.equals(getImfchan(), other.getImfchan()) &&\r\n            Objects.equals(getMm(), other.getMm()) &&\r\n            Objects.equals(getO(), other.getO()) &&\r\n            Objects.equals(getS(), other.getS()) &&\r\n            Objects.equals(getT(), other.getT()) &&\r\n            Objects.equals(getTexres(), other.getTexres()) &&\r\n            Objects.equals(getType(), other.getType());\r\n    }\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/FloatTuple.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\n\r\n/**\r\n * Interface for tuples consisting of float values\r\n */\r\npublic interface FloatTuple\r\n{\r\n    /**\r\n     * Return the x-component of this tuple\r\n     * \r\n     * @return The x-component of this tuple\r\n     * @throws IndexOutOfBoundsException If this tuple has less than 1 \r\n     * dimension\r\n     */\r\n    float getX();\r\n\r\n    /**\r\n     * Return the y-component of this tuple\r\n     * \r\n     * @return The y-component of this tuple\r\n     * @throws IndexOutOfBoundsException If this tuple has less than 2 \r\n     * dimensions\r\n     */\r\n    float getY();\r\n\r\n    /**\r\n     * Return the z-component of this tuple\r\n     * \r\n     * @return The z-component of this tuple\r\n     * @throws IndexOutOfBoundsException If this tuple has less than 3 \r\n     * dimensions\r\n     */\r\n    float getZ();\r\n\r\n    /**\r\n     * Return the w-component of this tuple\r\n     * \r\n     * @return The w-component of this tuple\r\n     * @throws IndexOutOfBoundsException If this tuple has less than 4 \r\n     * dimensions\r\n     */\r\n    float getW();\r\n\r\n    /**\r\n     * Return the specified component of this tuple\r\n     * \r\n     * @param index The index of the component\r\n     * @return The specified component of this tuple\r\n     * @throws IndexOutOfBoundsException If the given index is negative \r\n     * or not smaller than the {@link #getDimensions() dimensions}\r\n     */\r\n    float get(int index);\r\n\r\n    /**\r\n     * Return the dimensions of this tuple\r\n     * \r\n     * @return The dimensions of this tuple\r\n     */\r\n    int getDimensions();\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/FloatTuples.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\npackage de.javagl.obj;\r\n\r\n/**\r\n * Methods to create {@link FloatTuple} instances\r\n */\r\npublic class FloatTuples\r\n{\r\n    /**\r\n     * Create a copy of the given {@link FloatTuple}\r\n     * \r\n     * @param other The other tuple\r\n     * @return The {@link FloatTuple}\r\n     */\r\n    public static FloatTuple copy(FloatTuple other)\r\n    {\r\n        return new DefaultFloatTuple(other);\r\n    }\r\n    \r\n    /**\r\n     * Create a new {@link FloatTuple} with the given coordinate\r\n     * \r\n     * @param x The x-coordinate\r\n     * @return The {@link FloatTuple}\r\n     */\r\n    public static FloatTuple create(float x)\r\n    {\r\n        return new DefaultFloatTuple(x);\r\n    }\r\n    \r\n    /**\r\n     * Create a new {@link FloatTuple} with the given coordinates\r\n     * \r\n     * @param x The x-coordinate\r\n     * @param y The y-coordinate\r\n     * @return The {@link FloatTuple}\r\n     */\r\n    public static FloatTuple create(float x, float y)\r\n    {\r\n        return new DefaultFloatTuple(x, y);\r\n    }\r\n    \r\n    /**\r\n     * Create a new {@link FloatTuple} with the given coordinates\r\n     * \r\n     * @param x The x-coordinate\r\n     * @param y The y-coordinate\r\n     * @param z The z-coordinate\r\n     * @return The {@link FloatTuple}\r\n     */\r\n    public static FloatTuple create(float x, float y, float z)\r\n    {\r\n        return new DefaultFloatTuple(x, y, z);\r\n    }\r\n    \r\n    /**\r\n     * Create a new {@link FloatTuple} with the given coordinates\r\n     * \r\n     * @param x The x-coordinate\r\n     * @param y The y-coordinate\r\n     * @param z The z-coordinate\r\n     * @param w The w-coordinate\r\n     * @return The {@link FloatTuple}\r\n     */\r\n    public static FloatTuple create(float x, float y, float z, float w)\r\n    {\r\n        return new DefaultFloatTuple(x, y, z, w);\r\n    }\r\n    \r\n    \r\n    /**\r\n     * Returns the string for the given tuple that is used for representing\r\n     * the given tuple in an OBJ file\r\n     * \r\n     * @param tuple The tuple\r\n     * @return The string for the given tuple\r\n     */\r\n    public static String createString(FloatTuple tuple)\r\n    {\r\n        StringBuilder sb = new StringBuilder();\r\n        for (int i = 0; i < tuple.getDimensions(); i++)\r\n        {\r\n            if (i > 0)\r\n            {\r\n                sb.append(\" \");\r\n            }\r\n            sb.append(tuple.get(i));\r\n        }\r\n        return sb.toString();\r\n    }\r\n    \r\n    /**\r\n     * Private constructor to prevent instantiation\r\n     */\r\n    private FloatTuples()\r\n    {\r\n        // Private constructor to prevent instantiation\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/Mtl.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n *\r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\n\r\nimport java.util.List;\r\n\r\n/**\r\n * An in-memory representation of an MTL file. For details about the\r\n * semantics of the properties in this interface, refer to the MTL\r\n * specification.\r\n */\r\npublic interface Mtl\r\n{\r\n    /**\r\n     * Return the name of the material\r\n     *\r\n     * @return The name of the material\r\n     */\r\n    String getName();\r\n\r\n    /**\r\n     * Returns the illumination mode (<code>-illum</code>) of the material,\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The illumination mode of the material.\r\n     */\r\n    Integer getIllum();\r\n\r\n    /**\r\n     * Set the illumination mode of the material\r\n     *\r\n     * @param illum The illumination mode of the material.\r\n     */\r\n    void setIllum(Integer illum);\r\n\r\n    /**\r\n     * Returns the optical density, also known as index of refraction, of\r\n     * the material, or <code>null</code> if it was not specified\r\n     *\r\n     * @return The optical density\r\n     */\r\n    Float getNi();\r\n\r\n    /**\r\n     * Set the optical density of the material\r\n     *\r\n     * @param ni The optical density\r\n     */\r\n    void setNi(Float ni);\r\n\r\n    /**\r\n     * Returns the transmission filter of the material,\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The transmission filter\r\n     */\r\n    FloatTuple getTf();\r\n\r\n    /**\r\n     * Set the transmission filter of this material\r\n     *\r\n     * @param r The red component\r\n     * @param g The green component\r\n     * @param b The blue component\r\n     */\r\n    void setTf(Float r, Float g, Float b);\r\n\r\n    /**\r\n     * Returns the sharpness of reflections from the reflection map,\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The sharpness\r\n     */\r\n    Float getSharpness();\r\n\r\n    /**\r\n     * Set the sharpness of reflections\r\n     *\r\n     * @param sharpness The sharpness\r\n     */\r\n    void setSharpness(Float sharpness);\r\n\r\n\r\n    //--------------------------------------------------------------------------\r\n    // Ambient\r\n\r\n    /**\r\n     * Returns the ambient component of the material,\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The ambient component of the material\r\n     */\r\n    FloatTuple getKa();\r\n\r\n    /**\r\n     * Set the ambient part of this material\r\n     *\r\n     * @param r The red component\r\n     * @param g The green component\r\n     * @param b The blue component\r\n     */\r\n    void setKa(Float r, Float g, Float b);\r\n\r\n    /**\r\n     * Returns the name of the ambient map of the material,\r\n     * or <code>null</code> if it has no such map.\r\n     *\r\n     * @return The name of the ambient map of the material\r\n     */\r\n    String getMapKa();\r\n\r\n    /**\r\n     * Set the ambient map name of this material\r\n     *\r\n     * @param mapKa The ambient map name of this material\r\n     */\r\n    void setMapKa(String mapKa);\r\n\r\n    /**\r\n     * Returns the ambient map options of the material,\r\n     * or <code>null</code> if it has no options.\r\n     *\r\n     * @return The ambient map {@link TextureOptions}\r\n     */\r\n    TextureOptions getMapKaOptions();\r\n\r\n    /**\r\n     * Set the ambient map {@link TextureOptions}\r\n     *\r\n     * @param options The ambient map {@link TextureOptions}\r\n     */\r\n    void setMapKaOptions(TextureOptions options);\r\n\r\n\r\n\r\n\r\n    //--------------------------------------------------------------------------\r\n    // Diffuse\r\n\r\n    /**\r\n     * Returns the diffuse component of the material,\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The diffuse component of the material\r\n     */\r\n    FloatTuple getKd();\r\n\r\n    /**\r\n     * Set the diffuse part of this material\r\n     *\r\n     * @param r The red component\r\n     * @param g The green component\r\n     * @param b The blue component\r\n     */\r\n    void setKd(Float r, Float g, Float b);\r\n\r\n\r\n    /**\r\n     * Returns the name of the diffuse map of the material,\r\n     * or <code>null</code> if it has no such map.\r\n     *\r\n     * @return The name of the diffuse map of the material\r\n     */\r\n    String getMapKd();\r\n\r\n    /**\r\n     * Set the diffuse map name of this material\r\n     *\r\n     * @param mapKd The diffuse map name of this material\r\n     */\r\n    void setMapKd(String mapKd);\r\n\r\n    /**\r\n     * Returns the diffuse map options of the material,\r\n     * or <code>null</code> if it has no options.\r\n     *\r\n     * @return The diffuse map {@link TextureOptions}\r\n     */\r\n    TextureOptions getMapKdOptions();\r\n\r\n    /**\r\n     * Set the diffuse map {@link TextureOptions}\r\n     *\r\n     * @param options The diffuse map {@link TextureOptions}\r\n     */\r\n    void setMapKdOptions(TextureOptions options);\r\n\r\n\r\n\r\n    //--------------------------------------------------------------------------\r\n    // Specular reflectivity\r\n\r\n    /**\r\n     * Returns the specular component of the material,\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The specular component of the material\r\n     */\r\n    FloatTuple getKs();\r\n\r\n    /**\r\n     * Set the specular part of this material\r\n     *\r\n     * @param r The red component\r\n     * @param g The green component\r\n     * @param b The blue component\r\n     */\r\n    void setKs(Float r, Float g, Float b);\r\n\r\n    /**\r\n     * Returns the name of the specular reflectivity map of the material,\r\n     * or <code>null</code> if it has no such map.\r\n     *\r\n     * @return The name of the specular reflectivity map of the material\r\n     */\r\n    String getMapKs();\r\n\r\n    /**\r\n     * Set the specular reflectivity map name of this material\r\n     *\r\n     * @param mapKs The specular reflectivity map name of this material\r\n     */\r\n    void setMapKs(String mapKs);\r\n\r\n    /**\r\n     * Returns the specular reflectivity map options of the material,\r\n     * or <code>null</code> if it has no options.\r\n     *\r\n     * @return The specular reflectivity map {@link TextureOptions}\r\n     */\r\n    TextureOptions getMapKsOptions();\r\n\r\n    /**\r\n     * Set the specular reflectivity map {@link TextureOptions}\r\n     *\r\n     * @param options The specular reflectivity map {@link TextureOptions}\r\n     */\r\n    void setMapKsOptions(TextureOptions options);\r\n\r\n\r\n\r\n    //--------------------------------------------------------------------------\r\n    // Specular exponent (shininess)\r\n\r\n    /**\r\n     * Returns the shininess of the material.\r\n     *\r\n     * @return The shininess of the material.\r\n     */\r\n    Float getNs();\r\n\r\n    /**\r\n     * Set the shininess of this material,\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @param ns The shininess of this material\r\n     */\r\n    void setNs(Float ns);\r\n\r\n    /**\r\n     * Returns the name of the shininess map of the material,\r\n     * or <code>null</code> if it has no map.\r\n     *\r\n     * @return The name of the shininess map of the material\r\n     */\r\n    String getMapNs();\r\n\r\n    /**\r\n     * Set the shininess map name of this material\r\n     *\r\n     * @param mapNs The shininess map name of this material\r\n     */\r\n    void setMapNs(String mapNs);\r\n\r\n    /**\r\n     * Returns the shininess map options of the material,\r\n     * or <code>null</code> if it has no options.\r\n     *\r\n     * @return The shininess map {@link TextureOptions}\r\n     */\r\n    TextureOptions getMapNsOptions();\r\n\r\n    /**\r\n     * Set the shininess map {@link TextureOptions}\r\n     *\r\n     * @param options The shininess map {@link TextureOptions}\r\n     */\r\n    void setMapNsOptions(TextureOptions options);\r\n\r\n\r\n    //--------------------------------------------------------------------------\r\n    // Opacity\r\n\r\n    /**\r\n     * Returns the opacity of the material,\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The opacity of the material.\r\n     */\r\n    Float getD();\r\n\r\n    /**\r\n     * Set the opacity of the material\r\n     *\r\n     * @param d The opacity of the material\r\n     */\r\n    void setD(Float d);\r\n\r\n    /**\r\n     * Returns whether dissolve (opacity) is dependent on the surface\r\n     * orientation relative to the viewer\r\n     *\r\n     * @return The halo flag\r\n     */\r\n    Boolean isHalo();\r\n\r\n    /**\r\n     * Set the halo flag\r\n     *\r\n     * @param halo The halo flag\r\n     */\r\n    void setHalo(Boolean halo);\r\n\r\n    /**\r\n     * Returns the name of the opacity map of the material,\r\n     * or <code>null</code> if it has no map.\r\n     *\r\n     * @return The name of the opacity map of the material\r\n     */\r\n    String getMapD();\r\n\r\n    /**\r\n     * Set the opacity map name of this material\r\n     *\r\n     * @param mapD The opacity map name of this material\r\n     */\r\n    void setMapD(String mapD);\r\n\r\n    /**\r\n     * Returns the opacity map options of the material,\r\n     * or <code>null</code> if it has no options.\r\n     *\r\n     * @return The opacity map {@link TextureOptions}\r\n     */\r\n    TextureOptions getMapDOptions();\r\n\r\n    /**\r\n     * Set the opacity map {@link TextureOptions}\r\n     *\r\n     * @param options The opacity map {@link TextureOptions}\r\n     */\r\n    void setMapDOptions(TextureOptions options);\r\n\r\n\r\n    //--------------------------------------------------------------------------\r\n    // Bump\r\n\r\n    /**\r\n     * Returns the name of the bump map of the material,\r\n     * or <code>null</code> if it has no map.\r\n     *\r\n     * @return The name of the bump map of the material\r\n     */\r\n    String getBump();\r\n\r\n    /**\r\n     * Set the bump map name of this material\r\n     *\r\n     * @param bump The bump map name of this material\r\n     */\r\n    void setBump(String bump);\r\n\r\n    /**\r\n     * Returns the bump map options of the material,\r\n     * or <code>null</code> if it has no options.\r\n     *\r\n     * @return The bump map {@link TextureOptions}\r\n     */\r\n    TextureOptions getBumpOptions();\r\n\r\n    /**\r\n     * Set the bump map {@link TextureOptions}\r\n     *\r\n     * @param options The bump map {@link TextureOptions}\r\n     */\r\n    void setBumpOptions(TextureOptions options);\r\n\r\n\r\n\r\n    //--------------------------------------------------------------------------\r\n    // Disp (displacement)\r\n\r\n    /**\r\n     * Returns the name of the displacement map of the material,\r\n     * or <code>null</code> if it has no map.\r\n     *\r\n     * @return The name of the displacement map of the material\r\n     */\r\n    String getDisp();\r\n\r\n    /**\r\n     * Set the displacement map name of this material\r\n     *\r\n     * @param disp The displacement map name of this material\r\n     */\r\n    void setDisp(String disp);\r\n\r\n    /**\r\n     * Returns the displacement map options of the material,\r\n     * or <code>null</code> if it has no options.\r\n     *\r\n     * @return The displacement map {@link TextureOptions}\r\n     */\r\n    TextureOptions getDispOptions();\r\n\r\n    /**\r\n     * Set the displacement map {@link TextureOptions}\r\n     *\r\n     * @param options The displacement map {@link TextureOptions}\r\n     */\r\n    void setDispOptions(TextureOptions options);\r\n\r\n\r\n\r\n    //--------------------------------------------------------------------------\r\n    // Decal\r\n\r\n    /**\r\n     * Returns the name of the decal map of the material,\r\n     * or <code>null</code> if it has no map.\r\n     *\r\n     * @return The name of the decal map of the material\r\n     */\r\n    String getDecal();\r\n\r\n    /**\r\n     * Set the decal map name of this material\r\n     *\r\n     * @param decal The decal map name of this material\r\n     */\r\n    void setDecal(String decal);\r\n\r\n    /**\r\n     * Returns the decal map options of the material,\r\n     * or <code>null</code> if it has no options.\r\n     *\r\n     * @return The decal map {@link TextureOptions}\r\n     */\r\n    TextureOptions getDecalOptions();\r\n\r\n    /**\r\n     * Set the decal map {@link TextureOptions}\r\n     *\r\n     * @param options The decal map {@link TextureOptions}\r\n     */\r\n    void setDecalOptions(TextureOptions options);\r\n\r\n\r\n    //--------------------------------------------------------------------------\r\n    // Refl (reflection)\r\n\r\n    /**\r\n     * Returns the list of {@link TextureOptions} objects for the reflection\r\n     * maps of the material. This will never be <code>null</code>, but may\r\n     * be an empty list if no reflection maps have been defined.\r\n     *\r\n     * @return The reflection map {@link TextureOptions}\r\n     */\r\n    List<TextureOptions> getReflOptions();\r\n\r\n    //==========================================================================\r\n    // PBR parameters:\r\n\r\n    //--------------------------------------------------------------------------\r\n    // Roughness\r\n\r\n    /**\r\n     * Returns the roughness component of the material,\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The roughness component of the material\r\n     */\r\n    Float getPr();\r\n\r\n    /**\r\n     * Set the roughness part of this material\r\n     *\r\n     * @param pr The roughness\r\n     */\r\n    void setPr(Float pr);\r\n\r\n    /**\r\n     * Returns the name of the roughness map of the material,\r\n     * or <code>null</code> if it has no such map.\r\n     *\r\n     * @return The name of the roughness map of the material\r\n     */\r\n    String getMapPr();\r\n\r\n    /**\r\n     * Set the roughness map name of this material\r\n     *\r\n     * @param mapPr The roughness map name of this material\r\n     */\r\n    void setMapPr(String mapPr);\r\n\r\n    /**\r\n     * Returns the roughness map options of the material,\r\n     * or <code>null</code> if it has no options.\r\n     *\r\n     * @return The roughness map {@link TextureOptions}\r\n     */\r\n    TextureOptions getMapPrOptions();\r\n\r\n    /**\r\n     * Set the roughness map {@link TextureOptions}\r\n     *\r\n     * @param options The roughness map {@link TextureOptions}\r\n     */\r\n    void setMapPrOptions(TextureOptions options);\r\n    \r\n\r\n    //--------------------------------------------------------------------------\r\n    // Metallic\r\n\r\n    /**\r\n     * Returns the metallic component of the material,\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The metallic component of the material\r\n     */\r\n    Float getPm();\r\n\r\n    /**\r\n     * Set the metallic part of this material\r\n     *\r\n     * @param pm The metallic part\r\n     */\r\n    void setPm(Float pm);\r\n\r\n    /**\r\n     * Returns the name of the metallic map of the material,\r\n     * or <code>null</code> if it has no such map.\r\n     *\r\n     * @return The name of the metallic map of the material\r\n     */\r\n    String getMapPm();\r\n\r\n    /**\r\n     * Set the metallic map name of this material\r\n     *\r\n     * @param mapPm The metallic map name of this material\r\n     */\r\n    void setMapPm(String mapPm);\r\n\r\n    /**\r\n     * Returns the metallic map options of the material,\r\n     * or <code>null</code> if it has no options.\r\n     *\r\n     * @return The metallic map {@link TextureOptions}\r\n     */\r\n    TextureOptions getMapPmOptions();\r\n\r\n    /**\r\n     * Set the metallic map {@link TextureOptions}\r\n     *\r\n     * @param options The metallic map {@link TextureOptions}\r\n     */\r\n    void setMapPmOptions(TextureOptions options);\r\n\r\n    \r\n    //--------------------------------------------------------------------------\r\n    // Sheen\r\n\r\n    /**\r\n     * Returns the sheen component of the material,\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The sheen component of the material\r\n     */\r\n    Float getPs();\r\n\r\n    /**\r\n     * Set the sheen part of this material\r\n     *\r\n     * @param ps The sheen part\r\n     */\r\n    void setPs(Float ps);\r\n\r\n    /**\r\n     * Returns the name of the sheen map of the material,\r\n     * or <code>null</code> if it has no such map.\r\n     *\r\n     * @return The name of the sheen map of the material\r\n     */\r\n    String getMapPs();\r\n\r\n    /**\r\n     * Set the sheen map name of this material\r\n     *\r\n     * @param mapPs The sheen map name of this material\r\n     */\r\n    void setMapPs(String mapPs);\r\n\r\n    /**\r\n     * Returns the sheen map options of the material,\r\n     * or <code>null</code> if it has no options.\r\n     *\r\n     * @return The sheen map {@link TextureOptions}\r\n     */\r\n    TextureOptions getMapPsOptions();\r\n\r\n    /**\r\n     * Set the sheen map {@link TextureOptions}\r\n     *\r\n     * @param options The sheen map {@link TextureOptions}\r\n     */\r\n    void setMapPsOptions(TextureOptions options);\r\n    \r\n    \r\n    //--------------------------------------------------------------------------\r\n    // Pc (clearcoat thickness)\r\n    \r\n    /**\r\n     * Returns the clearcoat thickness of the material, \r\n     * or <code>null</code> if it was not specified\r\n     * \r\n     * @return The clearcoat thickness\r\n     */\r\n    Float getPc();\r\n    \r\n    /**\r\n     * Set the clearcoat thickness of the material\r\n     * \r\n     * @param pc The clearcoat thickness\r\n     */\r\n    void setPc(Float pc);\r\n\r\n    //--------------------------------------------------------------------------\r\n    // Pcr (clearcoat roughness)\r\n    \r\n    /**\r\n     * Returns the clearcoat roughness of the material, \r\n     * or <code>null</code> if it was not specified\r\n     * \r\n     * @return The clearcoat roughness\r\n     */\r\n    Float getPcr();\r\n    \r\n    /**\r\n     * Set the clearcoat roughness of the material\r\n     * \r\n     * @param pcr The clearcoat roughness\r\n     */\r\n    void setPcr(Float pcr);\r\n    \r\n    \r\n    //--------------------------------------------------------------------------\r\n    // Emissive\r\n\r\n    /**\r\n     * Returns the emissive component of the material,\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The emissive component of the material\r\n     */\r\n    FloatTuple getKe();\r\n\r\n    /**\r\n     * Set the emissive part of this material\r\n     *\r\n     * @param r The red component\r\n     * @param g The green component\r\n     * @param b The blue component\r\n     */\r\n    void setKe(Float r, Float g, Float b);\r\n\r\n    /**\r\n     * Returns the name of the emissive map of the material,\r\n     * or <code>null</code> if it has no such map.\r\n     *\r\n     * @return The name of the emissive map of the material\r\n     */\r\n    String getMapKe();\r\n\r\n    /**\r\n     * Set the emissive map name of this material\r\n     *\r\n     * @param mapKe The emissive map name of this material\r\n     */\r\n    void setMapKe(String mapKe);\r\n\r\n    /**\r\n     * Returns the emissive map options of the material,\r\n     * or <code>null</code> if it has no options.\r\n     *\r\n     * @return The emissive map {@link TextureOptions}\r\n     */\r\n    TextureOptions getMapKeOptions();\r\n\r\n    /**\r\n     * Set the emissive map {@link TextureOptions}\r\n     *\r\n     * @param options The emissive map {@link TextureOptions}\r\n     */\r\n    void setMapKeOptions(TextureOptions options);\r\n \r\n    \r\n    //--------------------------------------------------------------------------\r\n    // aniso (anisotropy)\r\n    \r\n    /**\r\n     * Returns the anisotropy of the material, \r\n     * or <code>null</code> if it was not specified\r\n     * \r\n     * @return The anisotropy\r\n     */\r\n    Float getAniso();\r\n    \r\n    /**\r\n     * Set the anisotropy of the material\r\n     * \r\n     * @param aniso The anisotropy\r\n     */\r\n    void setAniso(Float aniso);\r\n    \r\n    //--------------------------------------------------------------------------\r\n    // anisor (anisotropy rotation)\r\n    \r\n    /**\r\n     * Returns the anisotropy rotation of the material, \r\n     * or <code>null</code> if it was not specified\r\n     * \r\n     * @return The anisotropy rotation\r\n     */\r\n    Float getAnisor();\r\n    \r\n    /**\r\n     * Set the anisotropy rotation of the material\r\n     * \r\n     * @param anisor The anisotropy rotation\r\n     */\r\n    void setAnisor(Float anisor);\r\n    \r\n\r\n    //--------------------------------------------------------------------------\r\n    // Normal\r\n\r\n    /**\r\n     * Returns the name of the normal map of the material,\r\n     * or <code>null</code> if it has no such map.\r\n     *\r\n     * @return The name of the normal map of the material\r\n     */\r\n    String getNorm();\r\n\r\n    /**\r\n     * Set the normal map name of this material\r\n     *\r\n     * @param norm The normal map name of this material\r\n     */\r\n    void setNorm(String norm);\r\n\r\n    /**\r\n     * Returns the normal map options of the material,\r\n     * or <code>null</code> if it has no options.\r\n     *\r\n     * @return The normal map {@link TextureOptions}\r\n     */\r\n    TextureOptions getNormOptions();\r\n\r\n    /**\r\n     * Set the normal map {@link TextureOptions}\r\n     *\r\n     * @param options The normal map {@link TextureOptions}\r\n     */\r\n    void setNormOptions(TextureOptions options);\r\n    \r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/MtlReader.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n *\r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\nimport java.io.BufferedReader;\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.io.InputStreamReader;\r\nimport java.io.Reader;\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.LinkedList;\r\nimport java.util.List;\r\nimport java.util.Queue;\r\n\r\n/**\r\n * A class that may read MTL data, and return the materials as a\r\n * list of {@link Mtl} objects.\r\n */\r\npublic class MtlReader\r\n{\r\n    /**\r\n     * Read the MTL data from the given stream, and return\r\n     * it as {@link Mtl} objects.\r\n     * The caller is responsible for closing the given stream.\r\n     *\r\n     * @param inputStream The stream to read from.\r\n     * @return The list of Mtl object.\r\n     * @throws IOException If an IO error occurs\r\n     */\r\n    public static List<Mtl> read(InputStream inputStream)\r\n        throws IOException\r\n    {\r\n        BufferedReader reader = new BufferedReader(\r\n            new InputStreamReader(inputStream));\r\n        return readImpl(reader);\r\n    }\r\n\r\n    /**\r\n     * Read the MTL data from the given reader, and return\r\n     * it as {@link Mtl} objects.\r\n     * The caller is responsible for closing the given reader.\r\n     *\r\n     * @param reader The reader to read from.\r\n     * @return The list of Mtl object.\r\n     * @throws IOException If an IO error occurs\r\n     */\r\n    public static List<Mtl> read(Reader reader)\r\n        throws IOException\r\n    {\r\n        if (reader instanceof BufferedReader)\r\n        {\r\n            return readImpl((BufferedReader)reader);\r\n        }\r\n        return readImpl(new BufferedReader(reader));\r\n    }\r\n\r\n    /**\r\n     * Read the MTL data from the given reader, and return\r\n     * it as {@link Mtl} objects.\r\n     * The caller is responsible for closing the given reader.\r\n     *\r\n     * @param reader The reader to read from.\r\n     * @return The list of Mtl object.\r\n     * @throws IOException If an IO error occurs\r\n     */\r\n    private static List<Mtl> readImpl(BufferedReader reader)\r\n        throws IOException\r\n    {\r\n        List<Mtl> mtlList = new ArrayList<>();\r\n\r\n        Mtl currentMtl = null;\r\n\r\n        while(true)\r\n        {\r\n            String line = reader.readLine();\r\n            if(line == null)\r\n            {\r\n                break;\r\n            }\r\n\r\n            line = line.trim();\r\n\r\n            //System.out.println(\"read line: \"+line);\r\n\r\n            // Combine lines that have been broken\r\n            boolean finished = false;\r\n            while(line.endsWith(\"\\\\\"))\r\n            {\r\n                line = line.substring(0, line.length() - 2);\r\n                String nextLine = reader.readLine();\r\n                if (nextLine == null)\r\n                {\r\n                    finished = true;\r\n                    break;\r\n                }\r\n                line += \" \" + nextLine;\r\n            }\r\n            if (finished)\r\n            {\r\n                break;\r\n            }\r\n\r\n            line = line.trim();\r\n            if (line.startsWith(\"newmtl\"))\r\n            {\r\n                String name = line.substring(\"newmtl\".length()).trim();\r\n                currentMtl = new DefaultMtl(name);\r\n                mtlList.add(currentMtl);\r\n            }\r\n            else if (!line.startsWith(\"#\") && !line.isEmpty())\r\n            {\r\n                if (currentMtl == null)\r\n                {\r\n                    throw new IOException(\r\n                        \"Missing newmtl statement before \" + line);\r\n                }\r\n                processLine(currentMtl, line);\r\n            }\r\n        }\r\n        return mtlList;\r\n    }\r\n\r\n    /**\r\n     * Process one (trimmed) line that is part of a <code>newmtl</code>\r\n     * material definition, and write the result into the given {@link Mtl}.\r\n     *\r\n     * @param mtl The {@link Mtl}\r\n     * @param line The line\r\n     * @throws IOException If an IO error occurs\r\n     */\r\n    private static void processLine(Mtl mtl, String line)\r\n        throws IOException\r\n    {\r\n        Queue<String> tokens =\r\n                new LinkedList<>(Arrays.asList(line.split(\"[ \\t\\n\\r\\f]+\")));\r\n        String command = tokens.poll();\r\n\r\n        // Illumination mode\r\n        if (command.equalsIgnoreCase(\"illum\"))\r\n        {\r\n            int value = Utils.parseInt(tokens.poll());\r\n            mtl.setIllum(value);\r\n        }\r\n\r\n        // Color values: R, and optional G and B\r\n        if (command.equalsIgnoreCase(\"Ka\"))\r\n        {\r\n            Float[] values = Utils.parseFloats(tokens, 3);\r\n            mtl.setKa(values[0], values[1], values[2]);\r\n        }\r\n        else if (command.equalsIgnoreCase(\"Kd\"))\r\n        {\r\n            Float[] values = Utils.parseFloats(tokens, 3);\r\n            mtl.setKd(values[0], values[1], values[2]);\r\n        }\r\n        else if (command.equalsIgnoreCase(\"Ks\"))\r\n        {\r\n            Float[] values = Utils.parseFloats(tokens, 3);\r\n            mtl.setKs(values[0], values[1], values[2]);\r\n        }\r\n        else if (command.equalsIgnoreCase(\"Tf\"))\r\n        {\r\n            Float[] values = Utils.parseFloats(tokens, 3);\r\n            mtl.setTf(values[0], values[1], values[2]);\r\n        }\r\n        \r\n        // Color values for PBR\r\n        else if (command.equalsIgnoreCase(\"Ke\"))\r\n        {\r\n            Float[] values = Utils.parseFloats(tokens, 3);\r\n            mtl.setKe(values[0], values[1], values[2]);\r\n        }\r\n        \r\n\r\n        // Single float values\r\n        else if (command.equalsIgnoreCase(\"Tr\"))\r\n        {\r\n            float value = Utils.parseFloat(tokens.poll());\r\n            mtl.setD(1.0f - value);\r\n        }\r\n        else if (command.equalsIgnoreCase(\"sharpness\"))\r\n        {\r\n            float value = Utils.parseFloat(tokens.poll());\r\n            mtl.setSharpness(value);\r\n        }\r\n        else if (command.equalsIgnoreCase(\"d\"))\r\n        {\r\n            String token = tokens.peek();\r\n            if (\"-halo\".equals(token))\r\n            {\r\n                mtl.setHalo(true);\r\n                tokens.poll();\r\n            }\r\n            float value = Utils.parseFloat(tokens.poll());\r\n            mtl.setD(value);\r\n        }\r\n        else if (command.equalsIgnoreCase(\"Ni\"))\r\n        {\r\n            float value = Utils.parseFloat(tokens.poll());\r\n            mtl.setNi(value);\r\n        }\r\n        else if (command.equalsIgnoreCase(\"Ns\"))\r\n        {\r\n            float value = Utils.parseFloat(tokens.poll());\r\n            mtl.setNs(value);\r\n        }\r\n\r\n        // Single float values for PBR\r\n        else if (command.equalsIgnoreCase(\"Pr\"))\r\n        {\r\n            float value = Utils.parseFloat(tokens.poll());\r\n            mtl.setPr(value);\r\n        }\r\n        else if (command.equalsIgnoreCase(\"Pm\"))\r\n        {\r\n            float value = Utils.parseFloat(tokens.poll());\r\n            mtl.setPm(value);\r\n        }\r\n        else if (command.equalsIgnoreCase(\"Ps\"))\r\n        {\r\n            float value = Utils.parseFloat(tokens.poll());\r\n            mtl.setPs(value);\r\n        }\r\n        else if (command.equalsIgnoreCase(\"Pc\"))\r\n        {\r\n            float value = Utils.parseFloat(tokens.poll());\r\n            mtl.setPc(value);\r\n        }\r\n        else if (command.equalsIgnoreCase(\"Pcr\"))\r\n        {\r\n            float value = Utils.parseFloat(tokens.poll());\r\n            mtl.setPcr(value);\r\n        }\r\n        else if (command.equalsIgnoreCase(\"aniso\"))\r\n        {\r\n            float value = Utils.parseFloat(tokens.poll());\r\n            mtl.setAniso(value);\r\n        }\r\n        else if (command.equalsIgnoreCase(\"anisor\"))\r\n        {\r\n            float value = Utils.parseFloat(tokens.poll());\r\n            mtl.setAnisor(value);\r\n        }\r\n        \r\n\r\n        // Texture map definitions\r\n        else\r\n        {\r\n            readTextureMap(mtl, command, tokens);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Process the line of an MTL file that is supposed to contain a\r\n     * texture map definition, and write the resulting texture\r\n     * information into the given {@link Mtl}.<br>\r\n     * <br>\r\n     * The texture definition will be determined from the given command,\r\n     * which may, for example, be <code>\"map_Ka\"</code> or\r\n     * <code>\"refl\"</code>\r\n     *\r\n     * @param mtl The {@link Mtl}\r\n     * @param command The command at the beginning of the line\r\n     * @param tokens The tokens that have been created from the line\r\n     * @throws IOException If an IO error occurs\r\n     */\r\n    private static void readTextureMap(\r\n        Mtl mtl, String command, Queue<String> tokens)\r\n            throws IOException\r\n    {\r\n        if (command.equalsIgnoreCase(\"map_Ka\"))\r\n        {\r\n            mtl.setMapKaOptions(readTextureOptions(tokens));\r\n        }\r\n        else if (command.equalsIgnoreCase(\"map_Kd\"))\r\n        {\r\n            mtl.setMapKdOptions(readTextureOptions(tokens));\r\n        }\r\n        else if (command.equalsIgnoreCase(\"map_Ks\"))\r\n        {\r\n            mtl.setMapKsOptions(readTextureOptions(tokens));\r\n        }\r\n        else if (command.equalsIgnoreCase(\"map_d\"))\r\n        {\r\n            mtl.setMapDOptions(readTextureOptions(tokens));\r\n        }\r\n        else if (command.equalsIgnoreCase(\"map_Ns\"))\r\n        {\r\n            mtl.setMapNsOptions(readTextureOptions(tokens));\r\n        }\r\n        else if (command.equalsIgnoreCase(\"bump\")\r\n            || command.equalsIgnoreCase(\"map_bump\"))\r\n        {\r\n            mtl.setBumpOptions(readTextureOptions(tokens));\r\n        }\r\n        else if (command.equalsIgnoreCase(\"disp\"))\r\n        {\r\n            mtl.setDispOptions(readTextureOptions(tokens));\r\n        }\r\n        else if (command.equalsIgnoreCase(\"decal\"))\r\n        {\r\n            mtl.setDecalOptions(readTextureOptions(tokens));\r\n        }\r\n        else if (command.equalsIgnoreCase(\"refl\"))\r\n        {\r\n            TextureOptions refl = readTextureOptions(tokens);\r\n            mtl.getReflOptions().add(refl);\r\n        }\r\n        \r\n        // Texture map definitions for PBR\r\n        else if (command.equalsIgnoreCase(\"map_Pr\"))\r\n        {\r\n            mtl.setMapPrOptions(readTextureOptions(tokens));\r\n        }\r\n        else if (command.equalsIgnoreCase(\"map_Pm\"))\r\n        {\r\n            mtl.setMapPmOptions(readTextureOptions(tokens));\r\n        }\r\n        else if (command.equalsIgnoreCase(\"map_Ps\"))\r\n        {\r\n            mtl.setMapPsOptions(readTextureOptions(tokens));\r\n        }\r\n        else if (command.equalsIgnoreCase(\"map_Ke\"))\r\n        {\r\n            mtl.setMapKeOptions(readTextureOptions(tokens));\r\n        }\r\n        else if (command.equalsIgnoreCase(\"norm\"))\r\n        {\r\n            mtl.setNormOptions(readTextureOptions(tokens));\r\n        }\r\n        \r\n    }\r\n\r\n\r\n    /**\r\n     * Process the tokens in the given queue and construct a\r\n     * {@link TextureOptions} object from them\r\n     *\r\n     * @param tokens The input token\r\n     * @return The {@link TextureOptions}\r\n     * @throws IOException If an IO error occurs\r\n     */\r\n    static TextureOptions readTextureOptions(Queue<String> tokens)\r\n        throws IOException {\r\n\r\n        DefaultTextureOptions textureOptions = new DefaultTextureOptions();\r\n        while (!tokens.isEmpty())\r\n        {\r\n            String optionName = tokens.poll();\r\n            if (optionName.equalsIgnoreCase(\"-blendu\"))\r\n            {\r\n                boolean value = Utils.parseBoolean(tokens.poll());\r\n                textureOptions.setBlendu(value);\r\n            }\r\n            else if (optionName.equalsIgnoreCase(\"-blendv\"))\r\n            {\r\n                boolean value = Utils.parseBoolean(tokens.poll());\r\n                textureOptions.setBlendv(value);\r\n            }\r\n            else if (optionName.equalsIgnoreCase(\"-boost\"))\r\n            {\r\n                float value = Utils.parseFloat(tokens.poll());\r\n                textureOptions.setBoost(value);\r\n            }\r\n            else if (optionName.equalsIgnoreCase(\"-cc\"))\r\n            {\r\n                boolean value = Utils.parseBoolean(tokens.poll());\r\n                textureOptions.setCc(value);\r\n            }\r\n            else if (optionName.equalsIgnoreCase(\"-mm\"))\r\n            {\r\n                float base = Utils.parseFloat(tokens.poll());\r\n                float gain = Utils.parseFloat(tokens.poll());\r\n                textureOptions.setMm(base, gain);\r\n            }\r\n            else if (optionName.equalsIgnoreCase(\"-o\"))\r\n            {\r\n                Float[] values = Utils.parseFloats(tokens, 3);\r\n                textureOptions.setO(values[0], values[1], values[2]);\r\n            }\r\n            else if (optionName.equalsIgnoreCase(\"-s\"))\r\n            {\r\n                Float[] values = Utils.parseFloats(tokens, 3);\r\n                textureOptions.setS(values[0], values[1], values[2]);\r\n            }\r\n            else if (optionName.equalsIgnoreCase(\"-t\"))\r\n            {\r\n                Float[] values = Utils.parseFloats(tokens, 3);\r\n                textureOptions.setT(values[0], values[1], values[2]);\r\n            }\r\n            else if (optionName.equalsIgnoreCase(\"-texres\"))\r\n            {\r\n                float value = Utils.parseFloat(tokens.poll());\r\n                textureOptions.setTexres(value);\r\n            }\r\n            else if (optionName.equalsIgnoreCase(\"-clamp\"))\r\n            {\r\n                boolean value = Utils.parseBoolean(tokens.poll());\r\n                textureOptions.setClamp(value);\r\n            }\r\n            else if (optionName.equalsIgnoreCase(\"-bm\"))\r\n            {\r\n                float value = Utils.parseFloat(tokens.poll());\r\n                textureOptions.setBm(value);\r\n            }\r\n            else if (optionName.equalsIgnoreCase(\"-imfchan\"))\r\n            {\r\n                String value = tokens.poll();\r\n                textureOptions.setImfchan(value);\r\n            }\r\n            else if (optionName.equalsIgnoreCase(\"-type\"))\r\n            {\r\n                String value = tokens.poll();\r\n                textureOptions.setType(value);\r\n            }\r\n            else\r\n            {\r\n                textureOptions.setFileName(optionName);\r\n            }\r\n        }\r\n        return textureOptions;\r\n    }\r\n\r\n\r\n    /**\r\n     * Private constructor to prevent instantiation\r\n     */\r\n    private MtlReader()\r\n    {\r\n        // Private constructor to prevent instantiation\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/MtlWriter.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n *\r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\nimport java.io.IOException;\r\nimport java.io.OutputStream;\r\nimport java.io.OutputStreamWriter;\r\nimport java.io.Writer;\r\nimport java.util.List;\r\n\r\n/**\r\n * A class that may write {@link Mtl} objects into an MTL file\r\n */\r\npublic class MtlWriter\r\n{\r\n    /**\r\n     * Write the given {@link Mtl} objects to the given stream. The caller\r\n     * is responsible for closing the stream.\r\n     *\r\n     * @param mtls The {@link Mtl} objects\r\n     * @param outputStream The stream to write to\r\n     * @throws IOException If an IO error occurs\r\n     */\r\n    public static void write(\r\n        Iterable<? extends Mtl> mtls, OutputStream outputStream)\r\n        throws IOException\r\n    {\r\n        OutputStreamWriter outputStreamWriter =\r\n            new OutputStreamWriter(outputStream);\r\n        write(mtls, outputStreamWriter);\r\n    }\r\n\r\n    /**\r\n     * Write the given {@link Mtl} objects to the given writer. The caller\r\n     * is responsible for closing the writer.\r\n     *\r\n     * @param mtls The {@link Mtl} objects\r\n     * @param writer The writer to write to\r\n     * @throws IOException If an IO error occurs\r\n     */\r\n    public static void write(\r\n        Iterable<? extends Mtl> mtls, Writer writer)\r\n        throws IOException\r\n    {\r\n        for (Mtl mtl : mtls)\r\n        {\r\n            write(mtl, writer);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Write the given {@link Mtl} to the given writer\r\n     *\r\n     * @param mtl The {@link Mtl}\r\n     * @param writer The writer\r\n     * @throws IOException If an IO error occurs\r\n     */\r\n    private static void write(Mtl mtl, Writer writer)\r\n        throws IOException\r\n    {\r\n        writer.write(createString(mtl));\r\n        writer.flush();\r\n    }\r\n\r\n    /**\r\n     * Create the string representation of the given {@link Mtl}, as it\r\n     * is written into an MTL file\r\n     *\r\n     * @param mtl The {@link Mtl}\r\n     * @return The string representation\r\n     */\r\n    private static String createString(Mtl mtl)\r\n    {\r\n        StringBuilder sb = new StringBuilder(\"newmtl \");\r\n        sb.append(mtl.getName()).append(\"\\n\");\r\n\r\n        append(sb, \"illum\", mtl.getIllum(), \"\\n\");\r\n        append(sb, \"Ns\", mtl.getNs(), \"\\n\");\r\n        append(sb, \"Ni\", mtl.getNi(), \"\\n\");\r\n\r\n        Float opacity = mtl.getD();\r\n        if (opacity != null)\r\n        {\r\n            sb.append(\"d\").append(\" \");\r\n            if (Boolean.TRUE.equals(mtl.isHalo()))\r\n            {\r\n                sb.append(\"-halo\").append(\" \");\r\n            }\r\n            sb.append(opacity);\r\n            sb.append(\"\\n\");\r\n        }\r\n\r\n        appendTuple(sb, \"Ka\", mtl.getKa(), \"\\n\");\r\n        appendTuple(sb, \"Kd\", mtl.getKd(), \"\\n\");\r\n        appendTuple(sb, \"Ks\", mtl.getKs(), \"\\n\");\r\n        appendTuple(sb, \"Tf\", mtl.getTf(), \"\\n\");\r\n        append(sb, \"sharpness\", mtl.getSharpness(), \"\\n\");\r\n        appendTextureOptions(sb, \"map_Ka\", mtl.getMapKaOptions());\r\n        appendTextureOptions(sb, \"map_Kd\", mtl.getMapKdOptions());\r\n        appendTextureOptions(sb, \"map_Ks\", mtl.getMapKsOptions());\r\n        appendTextureOptions(sb, \"map_Ns\", mtl.getMapNsOptions());\r\n        appendTextureOptions(sb, \"map_d\", mtl.getMapDOptions());\r\n        appendTextureOptions(sb, \"bump\", mtl.getBumpOptions());\r\n        appendTextureOptions(sb, \"disp\", mtl.getDispOptions());\r\n        appendTextureOptions(sb, \"decal\", mtl.getDecalOptions());\r\n        List<TextureOptions> refls = mtl.getReflOptions();\r\n        for (TextureOptions refl : refls)\r\n        {\r\n            appendTextureOptions(sb, \"refl\", refl);\r\n        }\r\n        \r\n        // PBR parameters\r\n        append(sb, \"Pr\", mtl.getPr(), \"\\n\");\r\n        appendTextureOptions(sb, \"map_Pr\", mtl.getMapPrOptions());\r\n        append(sb, \"Pm\", mtl.getPm(), \"\\n\");\r\n        appendTextureOptions(sb, \"map_Pm\", mtl.getMapPmOptions());\r\n        append(sb, \"Ps\", mtl.getPs(), \"\\n\");\r\n        appendTextureOptions(sb, \"map_Ps\", mtl.getMapPsOptions());\r\n        append(sb, \"Pc\", mtl.getPc(), \"\\n\");\r\n        append(sb, \"Pcr\", mtl.getPcr(), \"\\n\");\r\n        appendTuple(sb, \"Ke\", mtl.getKe(), \"\\n\");\r\n        appendTextureOptions(sb, \"map_Ke\", mtl.getMapKeOptions());\r\n        append(sb, \"aniso\", mtl.getAniso(), \"\\n\");\r\n        append(sb, \"anisor\", mtl.getAnisor(), \"\\n\");\r\n        appendTextureOptions(sb, \"norm\", mtl.getNormOptions());\r\n        \r\n        return sb.toString();\r\n    }\r\n\r\n    /**\r\n     * Append the given {@link TextureOptions} to the given string builder,\r\n     * if they are not <code>null</code>\r\n     *\r\n     * @param sb The string builder\r\n     * @param key The key\r\n     * @param options The {@link TextureOptions}\r\n     */\r\n    private static void appendTextureOptions(\r\n        StringBuilder sb, String key, TextureOptions options)\r\n    {\r\n        if (options != null)\r\n        {\r\n            sb.append(key).append(\" \");\r\n            sb.append(createString(options)).append(\"\\n\");\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Create the string representation for the given {@link TextureOptions},\r\n     * as a single line that may be written to the MTL file\r\n     *\r\n     * @param options The {@link TextureOptions}\r\n     * @return The string representation\r\n     */\r\n    static String createString(TextureOptions options)\r\n    {\r\n        StringBuilder sb = new StringBuilder();\r\n        append(sb, \"-blendu\", options.isBlendu(), \" \");\r\n        append(sb, \"-blendv\", options.isBlendv(), \" \");\r\n        append(sb, \"-boost\", options.getBoost(), \" \");\r\n        appendTuple(sb, \"-mm\", options.getMm(), \" \");\r\n        appendTuple(sb, \"-o\", options.getO(), \" \");\r\n        appendTuple(sb, \"-s\", options.getS(), \" \");\r\n        appendTuple(sb, \"-t\", options.getT(), \" \");\r\n        append(sb, \"-texres\", options.getTexres(), \" \");\r\n        append(sb, \"-clamp\", options.isClamp(), \" \");\r\n        append(sb, \"-bm\", options.getBm(), \" \");\r\n        append(sb, \"-imfchan\", options.getImfchan(), \" \");\r\n        append(sb, \"-type\", options.getType(), \" \");\r\n        sb.append(options.getFileName());\r\n        return sb.toString();\r\n    }\r\n\r\n    /**\r\n     * Append the given key-value mapping to the given string builder, if\r\n     * the given value is not <code>null</code>\r\n     *\r\n     * @param sb The string builder\r\n     * @param key The key\r\n     * @param value The value\r\n     * @param separator The separator to append after the value\r\n     */\r\n    private static void append(\r\n        StringBuilder sb, String key, Object value, String separator)\r\n    {\r\n        if (value != null)\r\n        {\r\n            sb.append(key).append(\" \");\r\n            sb.append(value);\r\n            sb.append(separator);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Append the given key-value mapping to the given string builder, if\r\n     * the given value is not <code>null</code>\r\n     *\r\n     * @param sb The string builder\r\n     * @param key The key\r\n     * @param value The value\r\n     * @param separator The separator to append after the value\r\n     */\r\n    private static void append(\r\n        StringBuilder sb, String key, Boolean value, String separator)\r\n    {\r\n        if (value != null)\r\n        {\r\n            sb.append(key).append(\" \");\r\n            if (value)\r\n            {\r\n                sb.append(\"on\");\r\n            }\r\n            else\r\n            {\r\n                sb.append(\"off\");\r\n            }\r\n            sb.append(separator);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Append the given key-value mapping to the given string builder, if\r\n     * the given value is not <code>null</code>\r\n     *\r\n     * @param sb The string builder\r\n     * @param key The key\r\n     * @param value The value\r\n     * @param separator The separator to append after the value\r\n     */\r\n    private static void appendTuple(\r\n        StringBuilder sb, String key, FloatTuple value, String separator)\r\n    {\r\n        if (value != null)\r\n        {\r\n            sb.append(key).append(\" \");\r\n            sb.append(FloatTuples.createString(value));\r\n            sb.append(separator);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Private constructor to prevent instantiation\r\n     */\r\n    private MtlWriter()\r\n    {\r\n        // Private constructor to prevent instantiation\r\n    }\r\n\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/Mtls.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\npackage de.javagl.obj;\r\n\r\n/**\r\n * Methods to create {@link Mtl} instances\r\n */\r\npublic class Mtls\r\n{\r\n    /**\r\n     * Creates a new default {@link Mtl}\r\n     * \r\n     * @param name The name of the material\r\n     * @return The {@link Mtl}\r\n     */\r\n    public static Mtl create(String name)\r\n    {\r\n        return new DefaultMtl(name);\r\n    }\r\n    \r\n    /**\r\n     * Private constructor to prevent instantiation\r\n     */\r\n    private Mtls()\r\n    {\r\n        // Private constructor to prevent instantiation\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/Obj.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\npackage de.javagl.obj;\r\n\r\n/**\r\n * An in-memory representation of an OBJ file. This interface is only\r\n * a combination of a {@link ReadableObj} and a {@link WritableObj}.<br>\r\n * <br>\r\n * An {@link Obj} may be created with the {@link Objs#create()} method.\r\n * The {@link ObjReader#read(java.io.InputStream)} method may be used to \r\n * create an {@link Obj} from an OBJ file. \r\n */\r\npublic interface Obj extends ReadableObj, WritableObj\r\n{\r\n    // No additional methods\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/ObjData.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\npackage de.javagl.obj;\r\n\r\nimport java.nio.BufferOverflowException;\r\nimport java.nio.ByteBuffer;\r\nimport java.nio.ByteOrder;\r\nimport java.nio.FloatBuffer;\r\nimport java.nio.IntBuffer;\r\nimport java.nio.ShortBuffer;\r\n\r\n/**\r\n * Methods to obtain the data from {@link ReadableObj}s as plain arrays\r\n * or buffers\r\n */\r\npublic class ObjData\r\n{\r\n    \r\n    //=========================================================================\r\n    // Number of vertices\r\n\r\n    /**\r\n     * Returns an array containing the number of vertices of the faces\r\n     * of the given {@link ReadableObj}\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @return The array containing the number of vertices of the faces\r\n     */\r\n    private static int[] getNumFaceVertices(ReadableObj obj)\r\n    {\r\n        int[] numVerticesOfFaces = new int[obj.getNumFaces()];\r\n        for(int i = 0; i < obj.getNumFaces(); i++)\r\n        {\r\n            ObjFace face = obj.getFace(i);\r\n            numVerticesOfFaces[i] = face.getNumVertices();\r\n        }\r\n        return numVerticesOfFaces;\r\n    }\r\n    \r\n    /**\r\n     * Returns the sum of all numbers of vertices of all faces in the given \r\n     * {@link ReadableObj}. If the given {@link ReadableObj} only contains \r\n     * triangles, this will be the same as <code>obj.getNumFaces() * 3</code>.\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @return The number of face vertex indices\r\n     */\r\n    public static int getTotalNumFaceVertices(ReadableObj obj)\r\n    {\r\n        return sum(getNumFaceVertices(obj));\r\n    }\r\n    \r\n    /**\r\n     * Returns the sum of the elements of the given array\r\n     * \r\n     * @param array The array\r\n     * @return The sum of the elements of the given array\r\n     */\r\n    private static int sum(int[] array)\r\n    {\r\n        int sum = 0;\r\n        for (int i : array)\r\n        {\r\n            sum += i;\r\n        }\r\n        return sum;\r\n    }\r\n    \r\n    //=========================================================================\r\n    // Vertex indices\r\n    \r\n    \r\n    /**\r\n     * Returns the vertex indices from the faces of the given \r\n     * {@link ReadableObj} as an array. <br>\r\n     * <br>\r\n     * This method will compute the \r\n     * {@link #getTotalNumFaceVertices(ReadableObj) number of face vertex \r\n     * indices} and return an array with this size. If the number of vertices \r\n     * per face is known and equal for all faces, \r\n     * {@link #getFaceVertexIndices(ReadableObj,int)} may be used instead. \r\n     * \r\n     * @param obj The obj\r\n     * @return The face vertex indices\r\n     */\r\n    public static int[] getFaceVertexIndicesArray(ReadableObj obj)\r\n    {\r\n        int[] array = new int[getTotalNumFaceVertices(obj)];\r\n        getFaceVertexIndices(obj, IntBuffer.wrap(array));\r\n        return array;\r\n    }\r\n\r\n    /**\r\n     * Returns the vertex indices from the faces of the given \r\n     * {@link ReadableObj} as direct IntBuffer. The position \r\n     * of the returned buffer will be 0, and its limit and\r\n     * capacity will match the stored data.<br>\r\n     * <br>\r\n     * This method will compute the \r\n     * {@link #getTotalNumFaceVertices(ReadableObj) number of face vertex \r\n     * indices} and return a buffer with this size. If the number of vertices \r\n     * per face is known and equal for all faces, \r\n     * {@link #getFaceVertexIndices(ReadableObj,int)} may be used instead. \r\n     * \r\n     * @param obj The obj\r\n     * @return The face vertex indices\r\n     */\r\n    public static IntBuffer getFaceVertexIndices(ReadableObj obj)\r\n    {\r\n        IntBuffer buffer = createDirectIntBuffer(getTotalNumFaceVertices(obj));\r\n        getFaceVertexIndices(obj, buffer);\r\n        buffer.position(0);\r\n        return buffer;\r\n    }\r\n    \r\n    /**\r\n     * Returns the vertex indices from the faces of the given \r\n     * {@link ReadableObj} as an array. <br>\r\n     * <br>\r\n     * This method assumes that all faces have the given number of vertices\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @param numVerticesPerFace The number of vertices per face\r\n     * @return The face vertex indices\r\n     */\r\n    public static int[] getFaceVertexIndicesArray(\r\n        ReadableObj obj, int numVerticesPerFace)\r\n    {\r\n        int[] array = new int[obj.getNumFaces() * numVerticesPerFace];\r\n        getFaceVertexIndices(obj, IntBuffer.wrap(array));\r\n        return array;\r\n    }\r\n    \r\n    /**\r\n     * Returns the vertex indices from the faces of the given \r\n     * {@link ReadableObj} as direct IntBuffer. The position \r\n     * of the returned buffer will be 0, and its limit and\r\n     * capacity will match the stored data.<br>\r\n     * <br>\r\n     * This method assumes that all faces have the given number of vertices\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @param numVerticesPerFace The number of vertices per face\r\n     * @return The face vertex indices\r\n     */\r\n    public static IntBuffer getFaceVertexIndices(\r\n        ReadableObj obj, int numVerticesPerFace)\r\n    {\r\n        IntBuffer buffer = createDirectIntBuffer(\r\n            obj.getNumFaces() * numVerticesPerFace);\r\n        getFaceVertexIndices(obj, buffer);\r\n        buffer.position(0);\r\n        return buffer;\r\n    }\r\n    \r\n    /**\r\n     * Stores the vertex indices of the faces of the given \r\n     * {@link ReadableObj} in the given buffer. The position\r\n     * of the given buffer will be advanced accordingly.<br>\r\n     * <br>\r\n     * This method assumes that the given buffer is sufficiently large\r\n     * to store all indices. The required size may be computed with\r\n     * {@link #getTotalNumFaceVertices(ReadableObj)}, or, if the number \r\n     * of vertices per face is known and equal for all faces, with \r\n     * <code>n = obj.getNumFaces() * numVerticesPerFace</code>\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @param target The buffer that will store the result\r\n     * @throws BufferOverflowException If the buffer can not store the result\r\n     */\r\n    public static void getFaceVertexIndices(\r\n        ReadableObj obj, IntBuffer target)\r\n    {\r\n        for(int i = 0; i < obj.getNumFaces(); i++)\r\n        {\r\n            ObjFace face = obj.getFace(i);\r\n            for (int j=0; j<face.getNumVertices(); j++)\r\n            {\r\n                target.put(face.getVertexIndex(j));\r\n            }\r\n        }\r\n    }\r\n    \r\n    \r\n    \r\n    //=========================================================================\r\n    // TexCoord indices\r\n\r\n    /**\r\n     * Returns the texCoord indices from the faces of the given \r\n     * {@link ReadableObj} as an array. <br>\r\n     * <br>\r\n     * This method will compute the \r\n     * {@link #getTotalNumFaceVertices(ReadableObj) number of face vertices} \r\n     * and return an array with this size. If the number of vertices per\r\n     * face is known and equal for all faces, \r\n     * {@link #getFaceTexCoordIndices(ReadableObj,int)} may be used instead. \r\n     * \r\n     * @param obj The obj\r\n     * @return The face texCoord indices\r\n     */\r\n    public static int[] getFaceTexCoordIndicesArray(ReadableObj obj)\r\n    {\r\n        int[] array = new int[getTotalNumFaceVertices(obj)];\r\n        getFaceTexCoordIndices(obj, IntBuffer.wrap(array));\r\n        return array;\r\n    }\r\n    \r\n    /**\r\n     * Returns the texCoord indices from the faces of the given \r\n     * {@link ReadableObj} as a direct IntBuffer. The position \r\n     * of the returned buffer will be 0, and its limit and\r\n     * capacity will match the stored data.<br>\r\n     * <br>\r\n     * This method will compute the \r\n     * {@link #getTotalNumFaceVertices(ReadableObj) number of face vertices} \r\n     * and return a buffer with this size. If the number of vertices per\r\n     * face is known and equal for all faces, \r\n     * {@link #getFaceTexCoordIndices(ReadableObj,int)} may be used instead. \r\n     * \r\n     * @param obj The obj\r\n     * @return The face texCoord indices\r\n     */\r\n    public static IntBuffer getFaceTexCoordIndices(ReadableObj obj)\r\n    {\r\n        IntBuffer buffer = createDirectIntBuffer(getTotalNumFaceVertices(obj));\r\n        getFaceTexCoordIndices(obj, buffer);\r\n        buffer.position(0);\r\n        return buffer;\r\n    }\r\n    \r\n    \r\n    /**\r\n     * Returns the texCoord indices from the faces of the given \r\n     * {@link ReadableObj} as an array. <br>\r\n     * <br>\r\n     * This method assumes that all faces have the given number of vertices.<br>\r\n     * <br>\r\n     * This method assumes that the faces contain texture coordinate indices.\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @param numVerticesPerFace The number of vertices per face\r\n     * @return The face texCoord indices\r\n     */\r\n    public static int[] getFaceTexCoordIndicesArray(\r\n        ReadableObj obj, int numVerticesPerFace)\r\n    {\r\n        int[] array = new int[obj.getNumFaces() * numVerticesPerFace];\r\n        getFaceTexCoordIndices(obj, IntBuffer.wrap(array));\r\n        return array;\r\n    }\r\n    \r\n    \r\n    /**\r\n     * Returns the texCoord indices from the faces of the given \r\n     * {@link ReadableObj} as a direct IntBuffer. The position \r\n     * of the returned buffer will be 0, and its limit and\r\n     * capacity will match the stored data.<br>\r\n     * <br>\r\n     * This method assumes that all faces have the given number of vertices.<br>\r\n     * <br>\r\n     * This method assumes that the faces contain texture coordinate indices.\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @param numVerticesPerFace The number of vertices per face\r\n     * @return The face texCoord indices\r\n     */\r\n    public static IntBuffer getFaceTexCoordIndices(\r\n        ReadableObj obj, int numVerticesPerFace)\r\n    {\r\n        IntBuffer buffer = createDirectIntBuffer(\r\n            obj.getNumFaces() * numVerticesPerFace);\r\n        getFaceTexCoordIndices(obj, buffer);\r\n        buffer.position(0);\r\n        return buffer;\r\n    }\r\n    \r\n    \r\n    /**\r\n     * Stores the texCoord indices of the faces of the given \r\n     * {@link ReadableObj} in the given buffer. The position\r\n     * of the given buffer will be advanced accordingly.<br>\r\n     * <br>\r\n     * This method assumes that the given buffer is sufficiently large\r\n     * to store all indices. The required size may be computed with\r\n     * {@link #getTotalNumFaceVertices(ReadableObj)}, or, if the number of \r\n     * vertices per face is known and equal for all faces, with \r\n     * <code>n = obj.getNumFaces() * numVerticesPerFace</code>.<br>\r\n     * <br>\r\n     * This method assumes that the faces contain texture coordinate indices.\r\n     * \r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @param target The buffer that will store the result\r\n     * @throws BufferOverflowException If the buffer can not store the result\r\n     */\r\n    public static void getFaceTexCoordIndices(\r\n        ReadableObj obj, IntBuffer target)\r\n    {\r\n        for(int i = 0; i < obj.getNumFaces(); i++)\r\n        {\r\n            ObjFace face = obj.getFace(i);\r\n            for (int j=0; j<face.getNumVertices(); j++)\r\n            {\r\n                target.put(face.getTexCoordIndex(j));\r\n            }\r\n        }\r\n    }\r\n\r\n    \r\n    //=========================================================================\r\n    // Normal indices\r\n\r\n    /**\r\n     * Returns the normal indices from the faces of the given \r\n     * {@link ReadableObj} as an array. <br>\r\n     * <br>\r\n     * This method will compute the \r\n     * {@link #getTotalNumFaceVertices(ReadableObj) number of face vertices} \r\n     * and return an array with this size. If the number of vertices per\r\n     * face is known and equal for all faces, \r\n     * {@link #getFaceNormalIndices(ReadableObj,int)} may be used instead. \r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @return The face normal indices\r\n     */\r\n    public static int[] getFaceNormalIndicesArray(ReadableObj obj)\r\n    {\r\n        int[] array = new int[getTotalNumFaceVertices(obj)];\r\n        getFaceNormalIndices(obj, IntBuffer.wrap(array));\r\n        return array;\r\n    }\r\n\r\n    /**\r\n     * Returns the normal indices from the faces of the given \r\n     * {@link ReadableObj} as a direct IntBuffer. The position \r\n     * of the returned buffer will be 0, and its limit and\r\n     * capacity will match the stored data.<br>\r\n     * <br>\r\n     * This method will compute the \r\n     * {@link #getTotalNumFaceVertices(ReadableObj) number of face vertices} \r\n     * and return a buffer with this size. If the number of vertices per\r\n     * face is known and equal for all faces, \r\n     * {@link #getFaceNormalIndices(ReadableObj,int)} may be used instead. \r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @return The face normal indices\r\n     */\r\n    public static IntBuffer getFaceNormalIndices(ReadableObj obj)\r\n    {\r\n        IntBuffer buffer = createDirectIntBuffer(getTotalNumFaceVertices(obj));\r\n        getFaceNormalIndices(obj, buffer);\r\n        buffer.position(0);\r\n        return buffer;\r\n    }\r\n    \r\n    \r\n    /**\r\n     * Returns the normal indices from the faces of the given \r\n     * {@link ReadableObj} as an array. <br>\r\n     * <br>\r\n     * This method assumes that all faces have the given number of vertices.<br>\r\n     * <br>\r\n     * This method assumes that the faces contain normal indices.\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @param numVerticesPerFace The number of vertices per face\r\n     * @return The face normal indices\r\n     */\r\n    public static int[] getFaceNormalIndicesArray(\r\n        ReadableObj obj, int numVerticesPerFace)\r\n    {\r\n        int[] array = new int[obj.getNumFaces() * numVerticesPerFace];\r\n        getFaceNormalIndices(obj, IntBuffer.wrap(array));\r\n        return array;\r\n    }\r\n\r\n    \r\n    /**\r\n     * Returns the normal indices from the faces of the given \r\n     * {@link ReadableObj} as a direct IntBuffer. The position \r\n     * of the returned buffer will be 0, and its limit and\r\n     * capacity will match the stored data.<br>\r\n     * <br>\r\n     * This method assumes that all faces have the given number of vertices.<br>\r\n     * <br>\r\n     * This method assumes that the faces contain normal indices.\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @param numVerticesPerFace The number of vertices per face\r\n     * @return The face normal indices\r\n     */\r\n    public static IntBuffer getFaceNormalIndices(\r\n        ReadableObj obj, int numVerticesPerFace)\r\n    {\r\n        IntBuffer buffer = createDirectIntBuffer(\r\n            obj.getNumFaces() * numVerticesPerFace);\r\n        getFaceNormalIndices(obj, buffer);\r\n        buffer.position(0);\r\n        return buffer;\r\n    }\r\n    \r\n    \r\n    /**\r\n     * Stores the normal indices of the faces of the given \r\n     * {@link ReadableObj} in the given buffer. The position\r\n     * of the given buffer will be advanced accordingly.<br>\r\n     * <br>\r\n     * This method assumes that the given buffer is sufficiently large\r\n     * to store all indices. The required size may be computed with\r\n     * {@link #getTotalNumFaceVertices(ReadableObj)}, or, if the number of \r\n     * vertices per face is known and equal for all faces, with \r\n     * <code>n = obj.getNumFaces() * numVerticesPerFace</code>.<br>\r\n     * <br>\r\n     * This method assumes that the faces contain texture coordinate indices.\r\n     * \r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @param target The buffer that will store the result\r\n     * @throws BufferOverflowException If the buffer can not store the result\r\n     */\r\n    public static void getFaceNormalIndices(\r\n        ReadableObj obj, IntBuffer target)\r\n    {\r\n        for(int i = 0; i < obj.getNumFaces(); i++)\r\n        {\r\n            ObjFace face = obj.getFace(i);\r\n            for (int j=0; j<face.getNumVertices(); j++)\r\n            {\r\n                target.put(face.getNormalIndex(j));\r\n            }\r\n        }\r\n    }\r\n    \r\n    //=========================================================================\r\n    // Vertices\r\n    \r\n    /**\r\n     * Returns all vertices of the given {@link ReadableObj} as an array.\r\n     * Three consecutive entries in the resulting array are the\r\n     * x,y,z coordinates of one vertex\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @return The resulting array\r\n     */\r\n    public static float[] getVerticesArray(ReadableObj obj)\r\n    {\r\n        float[] array = new float[obj.getNumVertices() * 3];\r\n        getVertices(obj, FloatBuffer.wrap(array));\r\n        return array;\r\n    }\r\n\r\n    /**\r\n     * Returns all vertices of the given {@link ReadableObj} as a direct\r\n     * FloatBuffer.\r\n     * Three consecutive entries in the resulting buffer are the\r\n     * x,y,z coordinates of one vertex. The position \r\n     * of the returned buffer will be 0, and its limit and\r\n     * capacity will match the stored data.\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @return The resulting buffer\r\n     */\r\n    public static FloatBuffer getVertices(ReadableObj obj)\r\n    {\r\n        FloatBuffer buffer = createDirectFloatBuffer(obj.getNumVertices() * 3);\r\n        getVertices(obj, buffer);\r\n        buffer.position(0);\r\n        return buffer;\r\n    }\r\n    \r\n    /**\r\n     * Stores the vertices of the given {@link ReadableObj} in the given \r\n     * buffer. The position of the target will be increased by \r\n     * <code>obj.getNumVertices() * 3. The position\r\n     * of the given buffer will be advanced accordingly.</code>\r\n     * \r\n     * @param obj The obj\r\n     * @param target The target that will store the result\r\n     * @throws BufferOverflowException If the target can not store the result\r\n     */\r\n    public static void getVertices(\r\n        ReadableObj obj, FloatBuffer target)\r\n    {\r\n        for(int i = 0; i < obj.getNumVertices(); i++)\r\n        {\r\n            FloatTuple tuple = obj.getVertex(i);\r\n            target.put(tuple.getX());\r\n            target.put(tuple.getY());\r\n            target.put(tuple.getZ());\r\n        }\r\n    }\r\n    \r\n    \r\n    \r\n    \r\n    //=========================================================================\r\n    // TexCoords\r\n    \r\n    \r\n    /**\r\n     * Returns all texture coordinates of the given \r\n     * {@link ReadableObj} as an array.\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @param dimensions The dimensions that are assumed for the coordinates\r\n     * @return The resulting array\r\n     */\r\n    public static float[] getTexCoordsArray(ReadableObj obj, int dimensions)\r\n    {\r\n        return getTexCoordsArray(obj, dimensions, false);\r\n    }\r\n\r\n    /**\r\n     * Returns all texture coordinates of the given \r\n     * {@link ReadableObj} as an array.\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @param dimensions The dimensions that are assumed for the coordinates\r\n     * @param flipY Whether the texture coordinates should be flipped \r\n     * vertically. This means that the y-coordinates (at dimension index 1)\r\n     * will be replaced with <code>1.0f - y</code>. Most image loaders provide\r\n     * image data with the first pixel being the <i>upper left</i> pixel of\r\n     * the image. But OpenGL <code>glTexImage2D</code> calls expect the first\r\n     * pixel to be the <i>lower left</i>. Flipping the texture coordinates\r\n     * by passing <code>flipY=true</code> to this method allows to compensate\r\n     * for this mismatch.  \r\n     * @return The resulting array\r\n     */\r\n    public static float[] getTexCoordsArray(\r\n        ReadableObj obj, int dimensions, boolean flipY)\r\n    {\r\n        float[] array = new float[obj.getNumTexCoords() * dimensions];\r\n        getTexCoords(obj, FloatBuffer.wrap(array), dimensions, flipY);\r\n        return array;\r\n    }\r\n    \r\n    /**\r\n     * Returns all texture coordinates of the given \r\n     * {@link ReadableObj} as direct FloatBuffer. The position \r\n     * of the returned buffer will be 0, and its limit and\r\n     * capacity will match the stored data.\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @param dimensions The dimensions that are assumed for the coordinates\r\n     * @return The resulting buffer\r\n     */\r\n    public static FloatBuffer getTexCoords(ReadableObj obj, int dimensions)\r\n    {\r\n        return getTexCoords(obj, dimensions, false);\r\n    }\r\n    \r\n    /**\r\n     * Returns all texture coordinates of the given \r\n     * {@link ReadableObj} as direct FloatBuffer. The position \r\n     * of the returned buffer will be 0, and its limit and\r\n     * capacity will match the stored data.\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @param dimensions The dimensions that are assumed for the coordinates\r\n     * @param flipY Whether the texture coordinates should be flipped \r\n     * vertically. This means that the y-coordinates (at dimension index 1)\r\n     * will be replaced with <code>1.0f - y</code>. Most image loaders provide\r\n     * image data with the first pixel being the <i>upper left</i> pixel of\r\n     * the image. But OpenGL <code>glTexImage2D</code> calls expect the first\r\n     * pixel to be the <i>lower left</i>. Flipping the texture coordinates\r\n     * by passing <code>flipY=true</code> to this method allows to compensate\r\n     * for this mismatch.  \r\n     * @return The resulting buffer\r\n     */\r\n    public static FloatBuffer getTexCoords(\r\n        ReadableObj obj, int dimensions, boolean flipY)\r\n    {\r\n        FloatBuffer buffer = \r\n            createDirectFloatBuffer(obj.getNumTexCoords() * dimensions);\r\n        getTexCoords(obj, buffer, dimensions, flipY);\r\n        buffer.position(0);\r\n        return buffer;\r\n    }\r\n    \r\n\r\n    /**\r\n     * Stores the texture coordinates of the given {@link ReadableObj} \r\n     * in the given buffer. The position of the target will be increased by \r\n     * <code>obj.getNumTexCoords() * dimensions</code>. The position\r\n     * of the given buffer will be advanced accordingly.\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @param target The target that will store the result\r\n     * @param dimensions The dimensions that are assumed for the coordinates\r\n     * @throws BufferOverflowException If the target can not store the result\r\n     */\r\n    public static void getTexCoords(\r\n        ReadableObj obj, FloatBuffer target, int dimensions)\r\n    {\r\n        getTexCoords(obj, target, dimensions, false);\r\n    }\r\n\r\n    /**\r\n     * Stores the texture coordinates of the given {@link ReadableObj} \r\n     * in the given buffer. The position of the target will be increased by \r\n     * <code>obj.getNumTexCoords() * dimensions</code>. The position\r\n     * of the given buffer will be advanced accordingly.\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @param target The target that will store the result\r\n     * @param dimensions The dimensions that are assumed for the coordinates\r\n     * @param flipY Whether the texture coordinates should be flipped \r\n     * vertically. This means that the y-coordinates (at dimension index 1)\r\n     * will be replaced with <code>1.0f - y</code>. Most image loaders provide\r\n     * image data with the first pixel being the <i>upper left</i> pixel of\r\n     * the image. But OpenGL <code>glTexImage2D</code> calls expect the first\r\n     * pixel to be the <i>lower left</i>. Flipping the texture coordinates\r\n     * by passing <code>flipY=true</code> to this method allows to compensate\r\n     * for this mismatch.  \r\n     * @throws BufferOverflowException If the target can not store the result\r\n     */\r\n    public static void getTexCoords(\r\n        ReadableObj obj, FloatBuffer target, int dimensions, boolean flipY)\r\n    {\r\n        if (flipY) \r\n        {\r\n            for(int i = 0; i < obj.getNumTexCoords(); i++)\r\n            {\r\n                FloatTuple tuple = obj.getTexCoord(i);\r\n                for (int j=0; j<dimensions; j++)\r\n                {\r\n                    if (j == 1) \r\n                    {\r\n                        target.put(1.0f - tuple.get(j));\r\n                    }\r\n                    else\r\n                    {\r\n                        target.put(tuple.get(j));\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        else\r\n        {\r\n            for(int i = 0; i < obj.getNumTexCoords(); i++)\r\n            {\r\n                FloatTuple tuple = obj.getTexCoord(i);\r\n                for (int j=0; j<dimensions; j++)\r\n                {\r\n                    target.put(tuple.get(j));\r\n                }\r\n            }\r\n        }\r\n    }\r\n    \r\n    //=========================================================================\r\n    // Normals\r\n    \r\n    /**\r\n     * Returns all normals of the given {@link ReadableObj} as an array.\r\n     * Three consecutive entries in the resulting array are the\r\n     * x,y,z coordinates of one normal.\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @return The resulting array\r\n     */\r\n    public static float[] getNormalsArray(ReadableObj obj)\r\n    {\r\n        float[] array = new float[obj.getNumNormals() * 3];\r\n        getNormals(obj, FloatBuffer.wrap(array));\r\n        return array;\r\n    }\r\n\r\n    /**\r\n     * Returns all normals of the given {@link ReadableObj} as an array.\r\n     * Three consecutive entries in the resulting buffer are the\r\n     * x,y,z coordinates of one normal. The position \r\n     * of the returned buffer will be 0, and its limit and\r\n     * capacity will match the stored data.\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @return The resulting buffer\r\n     */\r\n    public static FloatBuffer getNormals(ReadableObj obj)\r\n    {\r\n        FloatBuffer buffer = createDirectFloatBuffer(obj.getNumNormals() * 3);\r\n        getNormals(obj, buffer);\r\n        buffer.position(0);\r\n        return buffer;\r\n    }\r\n    \r\n    /**\r\n     * Stores the normals of the given {@link ReadableObj} in the given \r\n     * buffer. The position of the target will be increased by \r\n     * <code>obj.getNumNormals() * 3</code>. The position\r\n     * of the given buffer will be advanced accordingly.\r\n     * \r\n     * @param obj The {@link ReadableObj}\r\n     * @param target The buffer that will store the result\r\n     * @throws BufferOverflowException If the target can not store the result\r\n     */\r\n    public static void getNormals(\r\n        ReadableObj obj, FloatBuffer target)\r\n    {\r\n        for(int i = 0; i < obj.getNumNormals(); i++)\r\n        {\r\n            FloatTuple tuple = obj.getNormal(i);\r\n            target.put(tuple.getX());\r\n            target.put(tuple.getY());\r\n            target.put(tuple.getZ());\r\n        }\r\n    }\r\n    \r\n    /**\r\n     * Convert the given IntBuffer to a (direct) ShortBuffer, by casting all\r\n     * elements to <code>short</code>. <br>\r\n     * <br>\r\n     * Note that these casts will be unchecked. If there is any value in the \r\n     * input buffer that is larger than the maximum value that a \r\n     * <b>signed</b> <code>short</code> can represent, then the \r\n     * <code>short</code> value will become negative. The resulting buffer \r\n     * will then still be valid for passing it to OpenGL when the index\r\n     * mode is <code>GL_UNSIGNED_SHORT</code>, because the bitwise \r\n     * representation is the same. When one of the integer values is larger \r\n     * than the value that can be represented with an <b>unsigned</b>\r\n     * <code>short</code>, then the resulting indices will be invalid\r\n     * and cause rendering artifacts.  \r\n     * \r\n     * @param intBuffer The IntBuffer\r\n     * @return The ShortBuffer\r\n     */\r\n    public static ShortBuffer convertToShortBuffer(IntBuffer intBuffer)\r\n    {\r\n        ShortBuffer shortBuffer = createDirectShortBuffer(intBuffer.capacity());\r\n        for (int i = 0; i < intBuffer.capacity(); i++)\r\n        {\r\n            shortBuffer.put(i, (short) intBuffer.get());\r\n        }\r\n        return shortBuffer;\r\n    }    \r\n    \r\n    /**\r\n     * Create a direct IntBuffer with the given size\r\n     * \r\n     * @param size The size \r\n     * @return The IntBuffer\r\n     */\r\n    private static IntBuffer createDirectIntBuffer(int size)\r\n    {\r\n        return ByteBuffer.allocateDirect(size * 4)\r\n            .order(ByteOrder.nativeOrder())\r\n            .asIntBuffer();\r\n    }\r\n    \r\n    /**\r\n     * Create a direct ShortBuffer with the given size\r\n     * \r\n     * @param size The size \r\n     * @return The ShortBuffer\r\n     */\r\n    private static ShortBuffer createDirectShortBuffer(int size)\r\n    {\r\n        return ByteBuffer.allocateDirect(size * 2)\r\n            .order(ByteOrder.nativeOrder())\r\n            .asShortBuffer();\r\n    }\r\n\r\n    /**\r\n     * Create a direct FloatBuffer with the given size\r\n     * \r\n     * @param size The size \r\n     * @return The FloatBuffer\r\n     */\r\n    private static FloatBuffer createDirectFloatBuffer(int size)\r\n    {\r\n        return ByteBuffer.allocateDirect(size * 4)\r\n            .order(ByteOrder.nativeOrder())\r\n            .asFloatBuffer();\r\n    }\r\n    \r\n    /**\r\n     * Private constructor to prevent instantiation\r\n     */\r\n    private ObjData()\r\n    {\r\n        // Private constructor to prevent instantiation\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/ObjFace.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\n\r\n\r\n/**\r\n * A single face that is stored in an OBJ file\r\n */\r\npublic interface ObjFace\r\n{\r\n    /**\r\n     * Returns the number of vertices this face consists of.\r\n     * \r\n     * @return The number of vertices this face consists of.\r\n     */\r\n    int getNumVertices();\r\n\r\n    /**\r\n     * Returns whether this face contains texture coordinate indices\r\n     * \r\n     * @return Whether this face contains texture coordinate indices\r\n     */\r\n    boolean containsTexCoordIndices();\r\n\r\n    /**\r\n     * Returns whether this face contains normal indices\r\n     * \r\n     * @return Whether this face contains normal indices\r\n     */\r\n    boolean containsNormalIndices();\r\n\r\n    /**\r\n     * Returns the index of the vertex with the given number.\r\n     * The index that is returned will be ZERO-based, in contrast\r\n     * to the ONE-based storage in the OBJ file.\r\n     * \r\n     * @param number The number of the vertex\r\n     * @return The index of the vertex.\r\n     * @throws IndexOutOfBoundsException If the given number is negative\r\n     * or not smaller than {@link #getNumVertices()}\r\n     */\r\n    int getVertexIndex(int number);\r\n\r\n    /**\r\n     * Returns the index of the texture coordinate with the given number.\r\n     * The index that is returned will be ZERO-based, in contrast\r\n     * to the ONE-based storage in the OBJ file.\r\n     * \r\n     * @param number The number of the texture coordinate\r\n     * @return The index of the texture coordinate.\r\n     * @throws IndexOutOfBoundsException If the given number is negative\r\n     * or not smaller than {@link #getNumVertices()}\r\n     */\r\n    int getTexCoordIndex(int number);\r\n\r\n    /**\r\n     * Returns the index of the normal with the given number.\r\n     * The index that is returned will be ZERO-based, in contrast\r\n     * to the ONE-based storage in the OBJ file.\r\n     * \r\n     * @param number The number of the normal\r\n     * @return The index of the normal.\r\n     * @throws IndexOutOfBoundsException If the given number is negative\r\n     * or not smaller than {@link #getNumVertices()}\r\n     */\r\n    int getNormalIndex(int number);\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/ObjFaceParser.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\n\r\nimport java.io.IOException;\r\nimport java.util.Arrays;\r\n\r\n/**\r\n * A class for reading the index data for an {@link ObjFace} from an \r\n * <code>'f'</code>-line that was read from an OBJ file\r\n */\r\nfinal class ObjFaceParser\r\n{\r\n    /**\r\n     * The initial size for the index buffers\r\n     */\r\n    private static final int INITIAL_BUFFER_SIZE = 6;\r\n    \r\n    /**\r\n     * Buffer for vertex indices\r\n     */\r\n    private int[] vertexIndexBuffer = new int[INITIAL_BUFFER_SIZE];\r\n\r\n    /**\r\n     * Buffer for texture coordinates\r\n     */\r\n    private int[] texCoordIndexBuffer = new int[INITIAL_BUFFER_SIZE];\r\n\r\n    /**\r\n     * Buffer normal indices\r\n     */\r\n    private int[] normalIndexBuffer = new int[INITIAL_BUFFER_SIZE];\r\n    \r\n    /**\r\n     * Flag whether texture coordinates have been found during the last\r\n     * call to {@link #parse(String)}\r\n     */\r\n    private boolean foundTexCoordIndices = false;\r\n\r\n    /**\r\n     * Flag whether normal indices have been found during the last\r\n     * call to {@link #parse(String)}\r\n     */\r\n    private boolean foundNormalIndices = false;\r\n    \r\n    /**\r\n     * Counter for the vertices\r\n     */\r\n    private int vertexCounter = 0;\r\n\r\n    /**\r\n     * Index in the input array\r\n     */\r\n    private int idx = 0;\r\n    \r\n    /**\r\n     * The input array to parse\r\n     */\r\n    private char[] lineData;\r\n\r\n    \r\n    /**\r\n     * Parse the given <code>'f'</code>-line that was read from an OBJ file\r\n     *   \r\n     * @param line The line\r\n     * @throws IOException If the given line can not be parsed\r\n     */\r\n    void parse(String line) throws IOException\r\n    {\r\n        parseLine(line);\r\n    }\r\n\r\n    /**\r\n     * Returns a new array containing the vertex indices that have\r\n     * been parsed during the last call to \r\n     * {@link #parse(String)}.\r\n     * \r\n     * @return The vertex indices\r\n     */\r\n    int[] getVertexIndices()\r\n    {\r\n        return Arrays.copyOf(vertexIndexBuffer, vertexCounter);\r\n    }\r\n    \r\n    /**\r\n     * Returns a new array containing the texCoord indices that have\r\n     * been parsed during the last call to \r\n     * {@link #parse(String)}, or <code>null</code> \r\n     * if no texture coordinate indices have been read\r\n     * \r\n     * @return The texCord indices\r\n     */\r\n    int[] getTexCoordIndices()\r\n    {\r\n        if (foundTexCoordIndices)\r\n        {\r\n            return Arrays.copyOf(texCoordIndexBuffer, vertexCounter);\r\n        }\r\n        return null;\r\n    }\r\n    \r\n    /**\r\n     * Returns a new array containing the normal indices that have\r\n     * been parsed during the last call to \r\n     * {@link #parse(String)}, or <code>null</code> \r\n     * if no normal indices have been read\r\n     * \r\n     * @return The normal indices\r\n     */\r\n    int[] getNormalIndices()\r\n    {\r\n        if(foundNormalIndices)\r\n        {\r\n            return Arrays.copyOf(normalIndexBuffer, vertexCounter);\r\n        }\r\n        return null;\r\n    }\r\n    \r\n    /**\r\n     * Parse the Face from the given line <br>\r\n     * f v0/vt0/vn0 ... vN/vtN/vnN <br>\r\n     *\r\n     * @param line String\r\n     * @throws IOException If the line could not be parsed\r\n     */\r\n    void parseLine(String line) throws IOException\r\n    {\r\n        foundTexCoordIndices = false;\r\n        foundNormalIndices = false;\r\n        vertexCounter = 0;\r\n        idx = 0;\r\n        lineData = line.toCharArray();\r\n\r\n\r\n        skipSpaces();\r\n        if(endOfInput())\r\n        {\r\n            // Empty line\r\n            return;\r\n        }\r\n\r\n        // Read the leading 'f' or 'F'\r\n        if(lineData[idx] != 'f' && lineData[idx] != 'F')\r\n        {\r\n            throw new IOException(\r\n                \"Expected 'f' or 'F', but found '\" + lineData[idx] +\r\n                \" in \\\"\"+line+\"\\\"\");\r\n        }\r\n        idx++;\r\n\r\n        // Read all vertex v/vt/vn triples\r\n        int count = 0;\r\n        while(true)\r\n        {\r\n            skipSpaces();\r\n            if(endOfInput())\r\n            {\r\n                break;\r\n            }\r\n            \r\n            // Read the vertex index\r\n            int vertexIndex = parseNonzeroInt();\r\n            if (vertexIndex == 0)\r\n            {\r\n                throw new IOException(\r\n                    \"Could not read vertex index in \\\"\"+line+\"\\\"\");\r\n            }\r\n            if (count >= vertexIndexBuffer.length)\r\n            {\r\n                vertexIndexBuffer = \r\n                    Arrays.copyOf(vertexIndexBuffer, count+1);\r\n                texCoordIndexBuffer = \r\n                    Arrays.copyOf(texCoordIndexBuffer, count+1);\r\n                normalIndexBuffer = \r\n                    Arrays.copyOf(normalIndexBuffer, count+1);\r\n            }\r\n            if (vertexIndex != 0)\r\n            {\r\n                vertexIndexBuffer[count] = vertexIndex;\r\n            }\r\n            vertexCounter = count + 1;\r\n\r\n            skipSpaces();\r\n            if(endOfInput())\r\n            {\r\n                break;\r\n            }\r\n\r\n            // Read the texCoord index\r\n            if(lineData[idx] == '/')\r\n            {\r\n                idx++;\r\n\r\n                skipSpaces();\r\n                if(endOfInput())\r\n                {\r\n                    throw new IOException(\r\n                        \"Unexpected end of input after '/' \" +\r\n                        \"in  \\\"\"+line+\"\\\"\");\r\n                }\r\n                \r\n                int texCoordIndex = parseNonzeroInt();\r\n\r\n                // It is not an error if texCoordIndex == 0: The indices\r\n                // may be given as \"1//2\", and thus not contain an \r\n                // texCoordIndex\r\n                \r\n                if(texCoordIndex != 0)\r\n                {\r\n                    texCoordIndexBuffer[count] = texCoordIndex;\r\n                    foundTexCoordIndices = true;\r\n                }\r\n\r\n                skipSpaces();\r\n                if(endOfInput())\r\n                {\r\n                    break;\r\n                }\r\n\r\n                // Read the normal index\r\n                if(lineData[idx] == '/')\r\n                {\r\n                    idx++;\r\n\r\n                    skipSpaces();\r\n                    if(endOfInput())\r\n                    {\r\n                        throw new IOException(\r\n                            \"Unexpected end of input after '/' \" + \r\n                            \"in  \\\"\"+line+\"\\\"\");\r\n                    }\r\n\r\n                    int normalIndex = parseNonzeroInt();\r\n                    if(normalIndex == 0)\r\n                    {\r\n                        throw new IOException(\r\n                            \"Could not read normal index from \\\"\"+line+\"\\\"\");\r\n                    }\r\n                    foundNormalIndices = true;\r\n                    if (normalIndex != 0)\r\n                    {\r\n                        normalIndexBuffer[count] = normalIndex;\r\n                    }\r\n                }\r\n            }\r\n            count++;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns whether the end of the input was reached\r\n     * \r\n     * @return 'true' iff the end of the input was reached\r\n     */\r\n    private boolean endOfInput()\r\n    {\r\n        return idx >= lineData.length;\r\n    }\r\n\r\n    /**\r\n     * Skips all space characters until the first non-space character is \r\n     * found or the end of the input is reached\r\n     */\r\n    private void skipSpaces()\r\n    {\r\n        while (!endOfInput() && lineData[idx] == ' ')\r\n        {\r\n            idx++;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns the next int in the input, or 0 if no int could be \r\n     * read\r\n     *  \r\n     * @return The next int in the input \r\n     */\r\n    private int parseNonzeroInt()\r\n    {\r\n        int parsedInt = 0;\r\n        boolean negate = false;\r\n        if (lineData[idx] == '-')\r\n        {\r\n            negate = true;\r\n            idx++;\r\n            skipSpaces();\r\n            if(endOfInput())\r\n            {\r\n                return 0;\r\n            }\r\n        }\r\n        if(lineData[idx] >= '0' && lineData[idx] <= '9')\r\n        {\r\n            parsedInt = (lineData[idx] - '0');\r\n            idx++;\r\n            while(!endOfInput() && \r\n                  lineData[idx] >= '0' && \r\n                  lineData[idx] <= '9')\r\n            {\r\n                parsedInt *= 10;\r\n                parsedInt += (lineData[idx] - '0');\r\n                idx++;\r\n            }\r\n        }\r\n        return negate ? -parsedInt : parsedInt;\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/ObjFaces.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\npackage de.javagl.obj;\r\n\r\n/**\r\n * Methods for creating {@link ObjFace} instances\r\n */\r\npublic class ObjFaces\r\n{\r\n    /**\r\n     * Create a copy of the given face.<br> \r\n     * \r\n     * @param face The input face\r\n     * @return The copy\r\n     */\r\n    static DefaultObjFace create(ObjFace face)\r\n    {\r\n        int[] v = new int[face.getNumVertices()];\r\n        int[] vt = null;\r\n        int[] vn = null;\r\n        for(int i = 0; i < face.getNumVertices(); i++)\r\n        {\r\n            v[i] = face.getVertexIndex(i);\r\n        }\r\n\r\n        if(face.containsTexCoordIndices())\r\n        {\r\n            vt = new int[face.getNumVertices()];\r\n            for(int i = 0; i < face.getNumVertices(); i++)\r\n            {\r\n                vt[i] = face.getTexCoordIndex(i);\r\n            }\r\n        }\r\n\r\n        if(face.containsNormalIndices())\r\n        {\r\n            vn = new int[face.getNumVertices()];\r\n            for(int i = 0; i < face.getNumVertices(); i++)\r\n            {\r\n                vn[i] = face.getNormalIndex(i);\r\n            }\r\n        }\r\n        \r\n        DefaultObjFace result = new DefaultObjFace(v, vt, vn);\r\n        return result;\r\n    }\r\n    \r\n    /**\r\n     * Create a copy of the given face, adding the given offsets to the\r\n     * respective indices. If the given face does not contain texture \r\n     * coordinate or normal indices, then the respective offsets will\r\n     * be ignored.<br> \r\n     * \r\n     * @param face The input face\r\n     * @param verticesOffset The offset for the vertex indices\r\n     * @param texCoordsOffset  The offset for the texture coordinate indices\r\n     * @param normalsOffset The offset for the normal indices\r\n     * @return The copy\r\n     */\r\n    static DefaultObjFace createWithOffsets(ObjFace face, \r\n        int verticesOffset, int texCoordsOffset, int normalsOffset)\r\n    {\r\n        int[] v = new int[face.getNumVertices()];\r\n        int[] vt = null;\r\n        int[] vn = null;\r\n        for(int i = 0; i < face.getNumVertices(); i++)\r\n        {\r\n            v[i] = face.getVertexIndex(i) + verticesOffset;\r\n        }\r\n\r\n        if(face.containsTexCoordIndices())\r\n        {\r\n            vt = new int[face.getNumVertices()];\r\n            for(int i = 0; i < face.getNumVertices(); i++)\r\n            {\r\n                vt[i] = face.getTexCoordIndex(i) + texCoordsOffset;\r\n            }\r\n        }\r\n\r\n        if(face.containsNormalIndices())\r\n        {\r\n            vn = new int[face.getNumVertices()];\r\n            for(int i = 0; i < face.getNumVertices(); i++)\r\n            {\r\n                vn[i] = face.getNormalIndex(i) + normalsOffset;\r\n            }\r\n        }\r\n        \r\n        DefaultObjFace result = new DefaultObjFace(v, vt, vn);\r\n        return result;\r\n    }\r\n    \r\n    \r\n    \r\n    \r\n    \r\n    /**\r\n     * Create a copy of the given face, using only the specified vertices \r\n     * of the given face.\r\n     * \r\n     * @param face The input face\r\n     * @param n The vertices to use\r\n     * @return The copy\r\n     */\r\n    static DefaultObjFace create(ObjFace face, int ... n)\r\n    {\r\n        int[] v = new int[n.length];\r\n        int[] vt = null;\r\n        int[] vn = null;\r\n        for(int i = 0; i < n.length; i++)\r\n        {\r\n            v[i] = face.getVertexIndex(n[i]);\r\n        }\r\n        if(face.containsTexCoordIndices())\r\n        {\r\n            vt = new int[n.length];\r\n            for(int i = 0; i < n.length; i++)\r\n            {\r\n                vt[i] = face.getTexCoordIndex(n[i]);\r\n            }\r\n        }\r\n\r\n        if(face.containsNormalIndices())\r\n        {\r\n            vn = new int[n.length];\r\n            for(int i = 0; i < n.length; i++)\r\n            {\r\n                vn[i] = face.getNormalIndex(n[i]);\r\n            }\r\n        }\r\n\r\n        DefaultObjFace result = new DefaultObjFace(v, vt, vn);\r\n        return result;\r\n        \r\n    }\r\n\r\n    \r\n    /**\r\n     * Create a face with the given indices. The texCoord indices and the \r\n     * normal indices may be <code>null</code>. In any case, it is assumed\r\n     * that all non-<code>null</code> arrays have equal length. References\r\n     * to the given arrays will be stored internally, so they should <b>not</b>\r\n     * be modified after they have been passed to this method.\r\n     * \r\n     * @param v The vertex indices\r\n     * @param vt The texCoord indices\r\n     * @param vn The normal indices\r\n     * @return The face\r\n     */\r\n    public static ObjFace create(int[] v, int[] vt, int[] vn)\r\n    {\r\n        return createDefault(v, vt, vn);\r\n    }\r\n    \r\n    /**\r\n     * Create a face with the given indices. The texCoord indices and the \r\n     * normal indices may be <code>null</code>. In any case, it is assumed\r\n     * that all non-<code>null</code> arrays have equal length. References\r\n     * to the given arrays will be stored internally, so they should <b>not</b>\r\n     * be modified after they have been passed to this method.\r\n     * \r\n     * @param v The vertex indices\r\n     * @param vt The texCoord indices\r\n     * @param vn The normal indices\r\n     * @return The face\r\n     */\r\n    static DefaultObjFace createDefault(int[] v, int[] vt, int[] vn)\r\n    {\r\n        DefaultObjFace result = new DefaultObjFace(v, vt, vn);\r\n        return result;\r\n    }\r\n\r\n    \r\n    /**\r\n     * Returns the string for the given face that makes up one 'f' line\r\n     * in an OBJ file\r\n     * \r\n     * @param face The face\r\n     * @return The string for the face\r\n     */\r\n    public static String createString(ObjFace face)\r\n    {\r\n        StringBuilder sb = new StringBuilder(\"f \");\r\n        for(int i = 0; i < face.getNumVertices(); i++)\r\n        {\r\n            if (i > 0)\r\n            {\r\n                sb.append(\" \");\r\n            }\r\n            sb.append(face.getVertexIndex(i) + 1);\r\n            if(face.containsTexCoordIndices() || face.containsNormalIndices())\r\n            {\r\n                sb.append(\"/\");\r\n            }\r\n            if(face.containsTexCoordIndices())\r\n            {\r\n                sb.append(face.getTexCoordIndex(i) + 1);\r\n            }\r\n            if(face.containsNormalIndices())\r\n            {\r\n                sb.append(\"/\").append(face.getNormalIndex(i) + 1);\r\n            }\r\n        }\r\n        return sb.toString();\r\n    }\r\n    \r\n    /**\r\n     * Private constructor to prevent instantiation\r\n     */\r\n    private ObjFaces()\r\n    {\r\n        // Private constructor to prevent instantiation\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/ObjGroup.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\n\r\n/**\r\n * Interface describing a single group of an OBJ file. This may either\r\n * be a geometry group that is identified by the <code>'g'</code> token, \r\n * or a group that is implied by a common material, identified by the \r\n * <code>'usemtl'</code> token.\r\n */\r\npublic interface ObjGroup\r\n{\r\n    /**\r\n     * Returns the name of this group.\r\n     * \r\n     * @return The name of this group.\r\n     */\r\n    String getName();\r\n\r\n    /**\r\n     * Returns the number of faces in this group.\r\n     * \r\n     * @return The number of faces in this group.\r\n     */\r\n    int getNumFaces();\r\n\r\n    /**\r\n     * Returns the face with the given index.\r\n     * \r\n     * @param index The index of the face\r\n     * @return The face with the given index.\r\n     * @throws IndexOutOfBoundsException If the index is negative or not\r\n     * smaller than {@link #getNumFaces()}\r\n     */\r\n    ObjFace getFace(int index);\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/ObjReader.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n *\r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\nimport java.io.BufferedReader;\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.io.InputStreamReader;\r\nimport java.io.Reader;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.Collections;\r\nimport java.util.List;\r\nimport java.util.StringTokenizer;\r\n\r\n/**\r\n * A class that may read OBJ data from a stream and\r\n * store the read data in an {@link WritableObj}.\r\n */\r\npublic class ObjReader\r\n{\r\n    /**\r\n     * Read the OBJ data from the given stream and return it as an {@link Obj}.\r\n     * The caller is responsible for closing the given stream.\r\n     *\r\n     * @param inputStream The stream to read from\r\n     * @return The {@link Obj}\r\n     * @throws IOException If an IO error occurs\r\n     */\r\n    public static Obj read(InputStream inputStream) throws IOException\r\n    {\r\n        return read(inputStream, Objs.create());\r\n    }\r\n\r\n    /**\r\n     * Read the OBJ data from the given stream and store the read\r\n     * elements in the given {@link WritableObj}.\r\n     * The caller is responsible for closing the given stream.\r\n     *\r\n     * @param <T> The output type\r\n     * @param inputStream The stream to read from\r\n     * @param output The {@link WritableObj} to store the read data\r\n     * @return The output\r\n     * @throws IOException If an IO error occurs\r\n     */\r\n    public static <T extends WritableObj> T read(\r\n        InputStream inputStream, T output)\r\n        throws IOException\r\n    {\r\n        BufferedReader reader = new BufferedReader(\r\n            new InputStreamReader(inputStream, StandardCharsets.US_ASCII));\r\n        return readImpl(reader, output);\r\n    }\r\n\r\n    /**\r\n     * Read the OBJ data from the given reader and return it as an {@link Obj}.\r\n     * The caller is responsible for closing the given reader.\r\n     *\r\n     * @param reader The reader to read from\r\n     * @return The {@link Obj}\r\n     * @throws IOException If an IO error occurs\r\n     */\r\n    public static Obj read(Reader reader) throws IOException\r\n    {\r\n        return read(reader, Objs.create());\r\n    }\r\n\r\n    /**\r\n     * Read the OBJ data from the given reader and store the read\r\n     * elements in the given {@link WritableObj}.\r\n     * The caller is responsible for closing the given reader.\r\n     *\r\n     * @param <T> The output type\r\n     * @param reader The reader to read from\r\n     * @param output The {@link WritableObj} to store the read data\r\n     * @return The output\r\n     * @throws IOException If an IO error occurs\r\n     */\r\n    public static <T extends WritableObj> T read(\r\n        Reader reader, T output)\r\n        throws IOException\r\n    {\r\n        if (reader instanceof BufferedReader)\r\n        {\r\n            return readImpl((BufferedReader)reader, output);\r\n        }\r\n        return readImpl(new BufferedReader(reader), output);\r\n\r\n    }\r\n\r\n    /**\r\n     * Read the OBJ data from the given reader and store the read\r\n     * elements in the given {@link WritableObj}.\r\n     * The caller is responsible for closing the given reader.\r\n     *\r\n     * @param <T> The output type\r\n     * @param reader The reader to read from\r\n     * @param output The {@link WritableObj} to store the read data\r\n     * @return The output\r\n     * @throws IOException If an IO error occurs\r\n     */\r\n    private static <T extends WritableObj> T readImpl(\r\n        BufferedReader reader, T output)\r\n        throws IOException\r\n    {\r\n        ObjFaceParser objFaceParser = new ObjFaceParser();\r\n\r\n        int vertexCounter = 0;\r\n        int texCoordCounter = 0;\r\n        int normalCounter = 0;\r\n        while(true)\r\n        {\r\n            String line = reader.readLine();\r\n            if(line == null)\r\n            {\r\n                break;\r\n            }\r\n\r\n            line = line.trim();\r\n\r\n            //System.out.println(\"read line: \"+line);\r\n\r\n            // Combine lines that have been broken\r\n            boolean finished = false;\r\n            while(line.endsWith(\"\\\\\"))\r\n            {\r\n                line = line.substring(0, line.length() - 2);\r\n                String nextLine = reader.readLine();\r\n                if (nextLine == null)\r\n                {\r\n                    finished = true;\r\n                    break;\r\n                }\r\n                line += \" \" + nextLine;\r\n            }\r\n            if (finished)\r\n            {\r\n                break;\r\n            }\r\n\r\n            StringTokenizer st = new StringTokenizer(line);\r\n            if(!st.hasMoreTokens())\r\n            {\r\n                continue;\r\n            }\r\n\r\n            String identifier = st.nextToken().toLowerCase();\r\n\r\n            // v: Vertex coordinates\r\n            switch (identifier) {\r\n                case \"v\":\r\n                    output.addVertex(Utils.readFloatTuple(st));\r\n                    vertexCounter++;\r\n                    break;\r\n\r\n                // vt: Texture coordinates for a vertex\r\n                case \"vt\":\r\n                    output.addTexCoord(Utils.readFloatTuple(st));\r\n                    texCoordCounter++;\r\n                    break;\r\n\r\n                // vn: Vertex normal\r\n                case \"vn\":\r\n                    output.addNormal(Utils.readFloatTuple(st));\r\n                    normalCounter++;\r\n                    break;\r\n\r\n                // mtllib: Name of the MTL file\r\n                case \"mtllib\": {\r\n                    String s = line.substring(6).trim();\r\n                    //output.setMtlFileNames(readStrings(s));\r\n                    // According to the OBJ specification, the \"mtllib\" keyword\r\n                    // may be followed by multiple file names, separated with\r\n                    // whitespaces:\r\n                    // \"When you assign a material library using the Model\r\n                    //  program, only one map library per .obj file is allowed.\r\n                    //  You can assign multiple libraries using a text editor.\"\r\n                    // However, to avoid problems with file names that contain\r\n                    // whitespaces, only ONE file name is assumed here:\r\n                    output.setMtlFileNames(Collections.singleton(s));\r\n                    break;\r\n                }\r\n\r\n                // usemtl: Material groups\r\n                case \"usemtl\":\r\n                    String materialGroupName = line.substring(6).trim();\r\n                    output.setActiveMaterialGroupName(materialGroupName);\r\n                    break;\r\n\r\n                // g: Geometry groups\r\n                case \"g\": {\r\n                    String s = line.substring(1).trim();\r\n                    String[] groupNames = readStrings(s);\r\n                    output.setActiveGroupNames(Arrays.asList(groupNames));\r\n                    break;\r\n                }\r\n\r\n                // f: A face definition\r\n                case \"f\":\r\n                    objFaceParser.parse(line);\r\n                    int[] v = objFaceParser.getVertexIndices();\r\n                    int[] vt = objFaceParser.getTexCoordIndices();\r\n                    int[] vn = objFaceParser.getNormalIndices();\r\n                    makeIndicesAbsolute(v, vertexCounter);\r\n                    makeIndicesAbsolute(vt, texCoordCounter);\r\n                    makeIndicesAbsolute(vn, normalCounter);\r\n                    output.addFace(ObjFaces.create(v, vt, vn));\r\n                    break;\r\n            }\r\n        }\r\n        return output;\r\n    }\r\n\r\n    /**\r\n     * Convert the indices in the given array to be absolute (non-negative)\r\n     * and zero-based. This means that negative values are made positive\r\n     * by adding the given count, and positive values are decreased by one.\r\n     *\r\n     * @param array The array. If this is <code>null</code>, nothing will\r\n     * be done\r\n     * @param count The count\r\n     */\r\n    private static void makeIndicesAbsolute(int[] array, int count)\r\n    {\r\n        if (array == null)\r\n        {\r\n            return;\r\n        }\r\n        for (int i=0; i<array.length; i++)\r\n        {\r\n            if (array[i] < 0)\r\n            {\r\n                array[i] = count + array[i];\r\n            }\r\n            else\r\n            {\r\n                array[i]--;\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * Read all tokens from the given input string that are separated\r\n     * by whitespaces\r\n     *\r\n     * @param input The input string\r\n     * @return The list of tokens\r\n     */\r\n    private static String[] readStrings(String input)\r\n    {\r\n        StringTokenizer st = new StringTokenizer(input);\r\n        List<String> tokens = new ArrayList<>();\r\n        while (st.hasMoreTokens())\r\n        {\r\n            tokens.add(st.nextToken());\r\n        }\r\n        return tokens.toArray(new String[tokens.size()]);\r\n    }\r\n\r\n    /**\r\n     * Private constructor to prevent instantiation\r\n     */\r\n    private ObjReader()\r\n    {\r\n        // Private constructor to prevent instantiation\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/ObjSplitter.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2017 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\npackage de.javagl.obj;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.Collections;\r\nimport java.util.List;\r\nimport java.util.logging.Level;\r\nimport java.util.logging.Logger;\r\n\r\n/**\r\n * Package-private class that can split an OBJ into multiple parts, based\r\n * on its number of vertices. Many details about the behavior of this class \r\n * are intentionally not specified.\r\n */\r\nclass ObjSplitter\r\n{\r\n    /**\r\n     * The logger used in this class\r\n     */\r\n    private static final Logger logger = \r\n        Logger.getLogger(ObjSplitter.class.getName());\r\n    \r\n    /**\r\n     * The log level\r\n     */\r\n    private static final Level level = Level.FINE;\r\n\r\n    /**\r\n     * A predicate interface, equivalent to java.util.functional.Predicate,\r\n     * but defined here in order to target Java 1.7\r\n     *\r\n     * @param <T> The parameter type\r\n     */\r\n    private interface Predicate<T>\r\n    {\r\n        /**\r\n         * Test the given object\r\n         * \r\n         * @param t The object\r\n         * @return Whether this predicate applies to the object\r\n         */\r\n        boolean test(T t);\r\n    }\r\n    \r\n    /**\r\n     * The predicate that will be used to check whether an OBJ should\r\n     * be split.\r\n     */\r\n    private final Predicate<? super ReadableObj> splitPredicate;\r\n    \r\n    /**\r\n     * Creates a new instance that splits OBJs into parts that have at most \r\n     * the given maximum number of vertices (if this is possible without\r\n     * splitting faces)\r\n     * \r\n     * @param maxNumVertices The maximum number of vertices\r\n     */\r\n    ObjSplitter(final int maxNumVertices)\r\n    {\r\n        splitPredicate = new Predicate<ReadableObj>()\r\n        {\r\n            @Override\r\n            public boolean test(ReadableObj obj)\r\n            {\r\n                return obj.getNumVertices() > maxNumVertices;\r\n            }\r\n        };\r\n    }\r\n    \r\n    /**\r\n     * Split the given OBJ into multiple parts, if it is necessary according\r\n     * to the {@link #splitPredicate}.\r\n     * \r\n     * @param obj The input OBJ\r\n     * @return The list of resulting OBJs\r\n     */\r\n    List<Obj> split(ReadableObj obj)\r\n    {\r\n        // If no splitting is necessary, just return a single OBJ that\r\n        // is a copy of the input.\r\n        if (!splitPredicate.test(obj))\r\n        {\r\n            Obj singleObj = Objs.create();\r\n            ObjUtils.add(obj, singleObj);\r\n            return Collections.singletonList(singleObj);\r\n        }\r\n\r\n        // Perform the initial split, and proceed splitting the\r\n        // parts until none of them matches the splitPredicate\r\n        List<Obj> currentObjs = splitSingle(obj);\r\n        boolean didSplit = currentObjs.size() > 1;\r\n        while (didSplit)\r\n        {\r\n            didSplit = false;\r\n            List<Obj> nextObjs = new ArrayList<>();\r\n            for (Obj currentObj : currentObjs)\r\n            {\r\n                if (splitPredicate.test(currentObj))\r\n                {\r\n                    List<Obj> parts = splitSingle(currentObj);\r\n                    nextObjs.addAll(parts);\r\n                    if (parts.size() > 1)\r\n                    {\r\n                        didSplit = true;\r\n                    }\r\n                }\r\n                else\r\n                {\r\n                    nextObjs.add(currentObj);\r\n                }\r\n            }\r\n            currentObjs = nextObjs;\r\n        }\r\n        return currentObjs;\r\n    }\r\n    \r\n    /**\r\n     * Split a single OBJ into two parts. Depending on the exact splitting\r\n     * strategy, this may not be possible in certain corner cases. In \r\n     * these cases, the returned list will contain only a single element. \r\n     * \r\n     * @param obj The input OBJ\r\n     * @return The list containing the parts\r\n     */\r\n    private static List<Obj> splitSingle(ReadableObj obj)\r\n    {\r\n        logger.log(level,\r\n            \"Splitting OBJ with \" + obj.getNumVertices() + \" vertices\");\r\n\r\n        Predicate<ObjFace> facePredicate = computeFacePredicate(obj);\r\n        List<ObjFace> faces0 = new ArrayList<>();\r\n        List<ObjFace> faces1 = new ArrayList<>();\r\n        for (int i = 0; i < obj.getNumFaces(); i++)\r\n        {\r\n            ObjFace face = obj.getFace(i);\r\n            if (facePredicate.test(face))\r\n            {\r\n                faces0.add(face);\r\n            }\r\n            else\r\n            {\r\n                faces1.add(face);\r\n            }\r\n        }\r\n        \r\n        // When there are faces that are basically equal, then geometric \r\n        // splitting is not possible. In this case, just split the list \r\n        // of faces into two halves.\r\n        if (faces0.isEmpty())\r\n        {\r\n            return split(obj, faces1);\r\n        }\r\n        else if (faces1.isEmpty())\r\n        {\r\n            return split(obj, faces0);\r\n        }\r\n        \r\n        logger.log(level,\r\n            \"Split OBJ with \" + obj.getNumFaces() + \" faces \"\r\n            + \"into \" + faces0.size() + \" and \" + faces1.size() + \" faces\");\r\n        \r\n        Obj obj0 = ObjUtils.groupToObj(obj, asGroup(faces0), null);\r\n        Obj obj1 = ObjUtils.groupToObj(obj, asGroup(faces1), null);\r\n        return Arrays.asList(obj0, obj1);\r\n    }\r\n\r\n    /**\r\n     * Split the given list of faces into two parts of approximately equal \r\n     * size, and return the {@link Obj} instances that correspond to the\r\n     * subsets of these faces in the given {@link Obj}. If the given\r\n     * list contains only a single element, then a single-element list\r\n     * will be returned.\r\n     *  \r\n     * @param obj The input OBJ\r\n     * @param allFaces The list of all faces\r\n     * @return The list containing the parts\r\n     */\r\n    private static List<Obj> split(ReadableObj obj, List<ObjFace> allFaces)\r\n    {\r\n        if (allFaces.size() <= 1)\r\n        {\r\n            Obj obj0 = ObjUtils.groupToObj(obj, asGroup(allFaces), null);\r\n            return Arrays.asList(obj0);\r\n        }\r\n        int centerIndex = (allFaces.size() + 1) / 2;\r\n        List<ObjFace> faces0 = allFaces.subList(0, centerIndex);\r\n        List<ObjFace> faces1 = allFaces.subList(centerIndex, allFaces.size());\r\n        Obj obj0 = ObjUtils.groupToObj(obj, asGroup(faces0), null);\r\n        Obj obj1 = ObjUtils.groupToObj(obj, asGroup(faces1), null);\r\n        return Arrays.asList(obj0, obj1);\r\n    }\r\n    \r\n    \r\n    /**\r\n     * Returns a new OBJ group that is a view on the given list\r\n     * \r\n     * @param faces The list of faces\r\n     * @return The OBJ group\r\n     */\r\n    private static ObjGroup asGroup(final List<? extends ObjFace> faces)\r\n    {\r\n        return new ObjGroup()\r\n        {\r\n            @Override\r\n            public String getName()\r\n            {\r\n                return \"default\";\r\n            }\r\n\r\n            @Override\r\n            public int getNumFaces()\r\n            {\r\n                return faces.size();\r\n            }\r\n            \r\n            @Override\r\n            public ObjFace getFace(int index)\r\n            {\r\n                return faces.get(index);\r\n            }\r\n        };\r\n    }\r\n    \r\n    /**\r\n     * Compute a predicate that splits the faces of the given OBJ into two\r\n     * parts. Further details are not specified.\r\n     * \r\n     * @param obj The OBJ\r\n     * @return The predicate\r\n     */\r\n    private static Predicate<ObjFace> computeFacePredicate(\r\n        final ReadableObj obj)\r\n    {\r\n        // Compute the projections of the centers of all faces along the axes\r\n        float[] centersX = computeFaceCenters(obj, 0);\r\n        float[] centersY = computeFaceCenters(obj, 1);\r\n        float[] centersZ = computeFaceCenters(obj, 2);\r\n        \r\n        // Compute the mean of the projections along the axes\r\n        final float meanX = arithmeticMean(centersX);\r\n        final float meanY = arithmeticMean(centersY);\r\n        final float meanZ = arithmeticMean(centersZ);\r\n        \r\n        // Compute the variances of the projections\r\n        float varianceX = variance(centersX, meanX);\r\n        float varianceY = variance(centersY, meanY);\r\n        float varianceZ = variance(centersZ, meanZ);\r\n        \r\n        // Create a predicate that splits the faces based on their location\r\n        // relative to the mean, along the axis with the largest variance\r\n        if (varianceX >= varianceY && varianceX >= varianceZ)\r\n        {\r\n            return new Predicate<ObjFace>()\r\n            {\r\n                @Override\r\n                public boolean test(ObjFace objFace)\r\n                {\r\n                    float faceCenterX = computeFaceCenter(obj, objFace, 0);\r\n                    return faceCenterX >= meanX;\r\n                }\r\n            };\r\n        }\r\n        if (varianceY >= varianceX && varianceY >= varianceZ)\r\n        {\r\n            return new Predicate<ObjFace>()\r\n            {\r\n                @Override\r\n                public boolean test(ObjFace objFace)\r\n                {\r\n                    float faceCenterY = computeFaceCenter(obj, objFace, 1);\r\n                    return faceCenterY >= meanY;\r\n                }\r\n            };\r\n        }\r\n        return new Predicate<ObjFace>()\r\n        {\r\n            @Override\r\n            public boolean test(ObjFace objFace)\r\n            {\r\n                float faceCenterZ = computeFaceCenter(obj, objFace, 2);\r\n                return faceCenterZ >= meanZ;\r\n            }\r\n        };\r\n        \r\n    }\r\n    \r\n    /**\r\n     * Compute the specified components of the center positions of the\r\n     * faces of the given OBJ\r\n     * \r\n     * @param obj The OBJ\r\n     * @param component The component, 0,1 or 2 for x, y or z.\r\n     * @return The face centers\r\n     */\r\n    private static float[] computeFaceCenters(ReadableObj obj, int component)\r\n    {\r\n        int n = obj.getNumFaces();\r\n        float[] result = new float[n];\r\n        for (int i = 0; i < n; i++)\r\n        {\r\n            ObjFace face = obj.getFace(i);\r\n            result[i] = computeFaceCenter(obj, face, component);\r\n        }\r\n        return result;\r\n    }    \r\n    \r\n    /**\r\n     * Compute the specified component of the center position of the given \r\n     * OBJ face\r\n     * \r\n     * @param obj The OBJ\r\n     * @param face The face\r\n     * @param component The component, 0,1 or 2 for x, y or z.\r\n     * @return The center\r\n     */\r\n    private static float computeFaceCenter(\r\n        ReadableObj obj, ObjFace face, int component)\r\n    {\r\n        float sum = 0;\r\n        int n = face.getNumVertices();\r\n        for (int i = 0; i < n; i++)\r\n        {\r\n            int vertexIndex = face.getVertexIndex(i);\r\n            FloatTuple vertex = obj.getVertex(vertexIndex);\r\n            sum += vertex.get(component);\r\n        }\r\n        return sum / n;\r\n    }\r\n    \r\n    \r\n    /**\r\n     * Returns the arithmetic mean of the given array\r\n     *\r\n     * @param array The input array\r\n     * @return The mean\r\n     */\r\n    private static float arithmeticMean(float[] array)\r\n    {\r\n        float sum = 0;\r\n        for (float value : array)\r\n        {\r\n            sum += value;\r\n        }\r\n        return sum / array.length;\r\n    }\r\n\r\n    /**\r\n     * Returns the bias-corrected sample variance of the given array.\r\n     *\r\n     * @param array The input array\r\n     * @param mean The mean\r\n     * @return The variance\r\n     */\r\n    private static float variance(float[] array, float mean)\r\n    {\r\n        float variance = 0;\r\n        for (float v : array) {\r\n            double difference = v - mean;\r\n            variance += difference * difference;\r\n        }\r\n        return variance / (array.length - 1);\r\n    }    \r\n    \r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/ObjSplitting.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2017 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\npackage de.javagl.obj;\r\n\r\nimport java.util.LinkedHashMap;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\n/**\r\n * Methods for splitting {@link ReadableObj} objects into multiple parts, \r\n * based on different criteria. \r\n */\r\npublic class ObjSplitting\r\n{\r\n    /**\r\n     * Split the given {@link ReadableObj} based on its groups. This will\r\n     * create one {@link Obj} from each (non-empty) group in the \r\n     * given input.\r\n     * \r\n     * @param obj The input {@link ReadableObj}\r\n     * @return One {@link Obj} for each non-empty group of the given input\r\n     */\r\n    public static Map<String, Obj> splitByGroups(ReadableObj obj)\r\n    {\r\n        Map<String, Obj> objs = new LinkedHashMap<>();\r\n        int numGroups = obj.getNumGroups();\r\n        for (int i = 0; i < numGroups; i++)\r\n        {\r\n            ObjGroup group = obj.getGroup(i);\r\n            if (group.getNumFaces() > 0)\r\n            {\r\n                String groupName = group.getName();\r\n                Obj groupObj = ObjUtils.groupToObj(obj, group, null);\r\n                objs.put(groupName, groupObj);\r\n            }\r\n        }\r\n        return objs;\r\n    }\r\n    \r\n    /**\r\n     * Split the given {@link ReadableObj} based on its material groups. \r\n     * This will create one {@link Obj} from each (non-empty) material \r\n     * group in the given input. <br>\r\n     * <br>\r\n     * Note that if the given OBJ does not contain any material groups\r\n     * (that is, when it does not refer to a material with a \r\n     * <code>usemtl</code> directive), then the resulting map will\r\n     * be empty. <br>\r\n     * <br>\r\n     * Faces that are not associated with any material group \r\n     * will not be contained in the output.\r\n     * \r\n     * @param obj The input {@link ReadableObj}\r\n     * @return The mapping from material group names (corresponding to the\r\n     * <code>usemtl</code> directives in the input file) to the {@link Obj} \r\n     * that represents this material group.\r\n     */\r\n    public static Map<String, Obj> splitByMaterialGroups(ReadableObj obj)\r\n    {\r\n        Map<String, Obj> objs = new LinkedHashMap<>();\r\n        int numMaterialGroups = obj.getNumMaterialGroups();\r\n        for (int i = 0; i < numMaterialGroups; i++)\r\n        {\r\n            ObjGroup materialGroup = obj.getMaterialGroup(i);\r\n            if (materialGroup.getNumFaces() > 0)\r\n            {\r\n                String materialGroupName = materialGroup.getName();\r\n                Obj materialGroupObj = \r\n                    ObjUtils.groupToObj(obj, materialGroup, null);\r\n                objs.put(materialGroupName, materialGroupObj);\r\n            }\r\n        }\r\n        return objs;\r\n    }\r\n\r\n    /**\r\n     * Split the given {@link ReadableObj} into {@link Obj} instances based\r\n     * on the given maximum number of vertices.<br> \r\n     * <br>\r\n     * Note that this method will not split any <i>faces</i> in the given\r\n     * OBJ. So although the resulting parts will usually not have more \r\n     * than the given maximum number of vertices, this cannot be guaranteed:\r\n     * When there is a face in the input OBJ that has <code>n</code> vertices, \r\n     * the resulting parts will have at most <code>maxNumVertices + n - 1</code>\r\n     * vertices. For example, if the given input contains only triangles, \r\n     * then the parts will have at most <code>maxNumVertices + 2</code>\r\n     * vertices.<br>\r\n     * <br>\r\n     * Many details about the splitting process that is used internally are\r\n     * intentionally not specified. This method is mainly intended for \r\n     * splitting a large {@link Obj} into parts that it may be rendered in \r\n     * environments that only allow a certain number of vertices (indices) \r\n     * per rendered object. For example, in order to create parts whose \r\n     * indices can be represented with an <code>unsigned short</code> value, \r\n     * this method may be called with <code>maxNumVertices=65000</code>.  \r\n     * \r\n     * @param obj The input {@link ReadableObj}\r\n     * @param maxNumVertices The maximum number of vertices\r\n     * @return One {@link Obj} for each part. \r\n     * @throws IllegalArgumentException If the given number is smaller than 3.\r\n     */\r\n    public static List<Obj> splitByMaxNumVertices(\r\n        ReadableObj obj, int maxNumVertices)\r\n    {\r\n        if (maxNumVertices < 3)\r\n        {\r\n            throw new IllegalArgumentException(\r\n                \"The given number of vertices must at least be 3\");\r\n        }\r\n        \r\n        ObjSplitter splitter = new ObjSplitter(maxNumVertices);\r\n        return splitter.split(obj);\r\n    }\r\n    \r\n    /**\r\n     * Private constructor to prevent instantiation\r\n     */\r\n    private ObjSplitting()\r\n    {\r\n        // Private constructor to prevent instantiation\r\n    }\r\n    \r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/ObjUtils.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\nimport java.util.Set;\r\n\r\n/**\r\n * Utility methods for handling {@link Obj}s.<br>\r\n * <br>\r\n * Unless otherwise noted, none of the parameters to these methods\r\n * may be <code>null</code>\r\n */\r\npublic class ObjUtils\r\n{\r\n    /**\r\n     * Convert the given {@link ReadableObj} into an {@link Obj} that has\r\n     * a structure appropriate for rendering it with OpenGL:\r\n     * <ul>\r\n     *   <li>{@link #triangulate(ReadableObj) Triangulate} it</li>\r\n     *   <li>\r\n     *     {@link #makeTexCoordsUnique(ReadableObj) \r\n     *        Make the texture coordinates unique}\r\n     *   </li>\r\n     *   <li>\r\n     *     {@link #makeNormalsUnique(ReadableObj) \r\n     *        Make the normals unique}\r\n     *   </li>\r\n     *   <li>\r\n     *     Make the result {@link #makeVertexIndexed(ReadableObj) \r\n     *     vertex-indexed}\r\n     *   </li>\r\n     * </ul>  \r\n     * \r\n     * @param input The input {@link ReadableObj} \r\n     * @return The resulting {@link Obj}\r\n     */\r\n    public static Obj convertToRenderable(ReadableObj input)\r\n    {\r\n        return convertToRenderable(input, Objs.create());\r\n    }\r\n    \r\n    /**\r\n     * Convert the given {@link ReadableObj} into an {@link Obj} that has\r\n     * a structure appropriate for rendering it with OpenGL:\r\n     * <ul>\r\n     *   <li>{@link #triangulate(ReadableObj) Triangulate} it</li>\r\n     *   <li>\r\n     *     {@link #makeTexCoordsUnique(ReadableObj) \r\n     *        Make the texture coordinates unique}\r\n     *   </li>\r\n     *   <li>\r\n     *     {@link #makeNormalsUnique(ReadableObj) \r\n     *        Make the normals unique}\r\n     *   </li>\r\n     *   <li>\r\n     *     Make the result {@link #makeVertexIndexed(ReadableObj) \r\n     *     vertex-indexed}\r\n     *   </li>\r\n     * </ul>  \r\n     * \r\n     * @param <T> The type of the output\r\n     * @param input The input {@link ReadableObj} \r\n     * @param output The output {@link WritableObj}\r\n     * @return The given output\r\n     */\r\n    public static <T extends WritableObj> T convertToRenderable(\r\n        ReadableObj input, T output)\r\n    {\r\n        Obj obj = triangulate(input);\r\n        obj = makeTexCoordsUnique(obj);\r\n        obj = makeNormalsUnique(obj);\r\n        return makeVertexIndexed(obj, output);\r\n    }\r\n    \r\n    \r\n    /**\r\n     * Triangulates the given input {@link ReadableObj} and returns the \r\n     * result.<br>\r\n     * <br>\r\n     * This method will simply subdivide faces with more than 3 vertices so \r\n     * that all faces in the output will be triangles.\r\n     * \r\n     * @param input The input {@link ReadableObj} \r\n     * @return The resulting {@link Obj}\r\n     */\r\n    public static Obj triangulate(ReadableObj input)\r\n    {\r\n        return triangulate(input, Objs.create());\r\n    }\r\n    \r\n    /**\r\n     * Triangulates the given input {@link ReadableObj} and stores the result \r\n     * in the given {@link WritableObj}.<br>\r\n     * <br>\r\n     * This method will simply subdivide faces with more than 3 vertices so \r\n     * that all faces in the output will be triangles.\r\n     * \r\n     * @param <T> The type of the output\r\n     * @param input The input {@link ReadableObj} \r\n     * @param output The output {@link WritableObj}\r\n     * @return The given output\r\n     */\r\n    public static <T extends WritableObj> T triangulate(\r\n        ReadableObj input, T output)\r\n    {\r\n        output.setMtlFileNames(input.getMtlFileNames());\r\n        \r\n        addAll(input, output);\r\n\r\n        for (int i=0; i<input.getNumFaces(); i++)\r\n        {\r\n            ObjFace face = input.getFace(i);\r\n            activateGroups(input, face, output);\r\n            if (face.getNumVertices() == 3)\r\n            {\r\n                output.addFace(face);\r\n            }\r\n            else\r\n            {\r\n                for(int j = 0; j < face.getNumVertices() - 2; j++)\r\n                {\r\n                    DefaultObjFace triangle = \r\n                        ObjFaces.create(face, 0, j + 1, j + 2);\r\n                    output.addFace(triangle);\r\n                }\r\n            }\r\n        }\r\n        return output;\r\n    }\r\n    \r\n    \r\n    /**\r\n     * Returns the given group of the given {@link ReadableObj} as a new\r\n     * {@link Obj}.<br>\r\n     * <br>\r\n     * The <code>vertexIndexMapping</code> may be <code>null</code>. If it \r\n     * is not <code>null</code>, <code>vertexIndexMapping.get(i)</code> \r\n     * afterwards stores the index that vertex <code>i</code> had in the \r\n     * input.\r\n     * \r\n     * @param input The input {@link ReadableObj}\r\n     * @param inputGroup The group of the input\r\n     * @param vertexIndexMapping The optional index mapping\r\n     * @return The resulting {@link Obj}\r\n     */\r\n    public static Obj groupToObj(\r\n        ReadableObj input, ObjGroup inputGroup, \r\n        List<Integer> vertexIndexMapping)\r\n    {\r\n        return groupToObj(input, inputGroup, vertexIndexMapping, Objs.create());\r\n    }\r\n\r\n    /**\r\n     * Stores the given group of the given {@link ReadableObj}\r\n     * in the given output {@link WritableObj}.<br>\r\n     * <br>\r\n     * The <code>vertexIndexMapping</code> may be <code>null</code>. If it \r\n     * is not <code>null</code>, <code>vertexIndexMapping.get(i)</code> \r\n     * afterwards stores the index that vertex <code>i</code> had in the \r\n     * input.\r\n     * \r\n     * @param <T> The type of the output\r\n     * @param input The input {@link ReadableObj}\r\n     * @param inputGroup The group of the input\r\n     * @param vertexIndexMapping The optional index mapping\r\n     * @param output The output {@link WritableObj}\r\n     * @return The given output\r\n     */\r\n    public static <T extends WritableObj> T groupToObj(\r\n        ReadableObj input, ObjGroup inputGroup, \r\n        List<Integer> vertexIndexMapping, T output)\r\n    {\r\n        output.setMtlFileNames(input.getMtlFileNames());\r\n\r\n        // vertexIndexMap[i] contains the index that vertex i of the \r\n        // original Obj will have in the output\r\n        int[] vertexIndexMap = new int[input.getNumVertices()];\r\n        int[] texCoordIndexMap = new int[input.getNumTexCoords()];\r\n        int[] normalIndexMap = new int[input.getNumNormals()];\r\n\r\n        Arrays.fill(vertexIndexMap, -1);\r\n        Arrays.fill(texCoordIndexMap, -1);\r\n        Arrays.fill(normalIndexMap, -1);\r\n\r\n        int vertexCounter = 0;\r\n        int texCoordCounter = 0;\r\n        int normalCounter = 0;\r\n        for(int i = 0; i < inputGroup.getNumFaces(); i++)\r\n        {\r\n            // Clone the face info from the input\r\n            ObjFace face = inputGroup.getFace(i);\r\n\r\n            DefaultObjFace resultFace = ObjFaces.create(face);\r\n            \r\n            activateGroups(input, face, output);\r\n            \r\n            // The indices of the cloned face have to be adjusted, \r\n            // so that they point to the correct vertices in the output\r\n            for(int j = 0; j < face.getNumVertices(); j++)\r\n            {\r\n                int vertexIndex = face.getVertexIndex(j);\r\n                if(vertexIndexMap[vertexIndex] == -1)\r\n                {\r\n                    vertexIndexMap[vertexIndex] = vertexCounter;\r\n                    output.addVertex(input.getVertex(vertexIndex));\r\n                    vertexCounter++;\r\n                }\r\n                resultFace.setVertexIndex(j, vertexIndexMap[vertexIndex]);\r\n            }\r\n\r\n            if(face.containsTexCoordIndices())\r\n            {\r\n                for(int j = 0; j < face.getNumVertices(); j++)\r\n                {\r\n                    int texCoordIndex = face.getTexCoordIndex(j);\r\n                    if(texCoordIndexMap[texCoordIndex] == -1)\r\n                    {\r\n                        texCoordIndexMap[texCoordIndex] = texCoordCounter;\r\n                        output.addTexCoord(input.getTexCoord(texCoordIndex));\r\n                        texCoordCounter++;\r\n                    }\r\n                    resultFace.setTexCoordIndex(\r\n                        j, texCoordIndexMap[texCoordIndex]);\r\n                }\r\n            }\r\n\r\n\r\n            if(face.containsNormalIndices())\r\n            {\r\n                for(int j = 0; j < face.getNumVertices(); j++)\r\n                {\r\n                    int normalIndex = face.getNormalIndex(j);\r\n                    if(normalIndexMap[normalIndex] == -1)\r\n                    {\r\n                        normalIndexMap[normalIndex] = normalCounter;\r\n                        output.addNormal(input.getNormal(normalIndex));\r\n                        normalCounter++;\r\n                    }\r\n                    resultFace.setNormalIndex(j, normalIndexMap[normalIndex]);\r\n                }\r\n            }\r\n\r\n            // Add the cloned face with the adjusted indices to the output\r\n            output.addFace(resultFace);\r\n        }\r\n\r\n        // Compute the vertexIndexMapping, so that vertexIndexMapping.get(i) \r\n        // afterwards stores the index that vertex i had in the input \r\n        if(vertexIndexMapping != null)\r\n        {\r\n            for(int i = 0; i < vertexCounter; i++)\r\n            {\r\n                vertexIndexMapping.add(-1);\r\n            }\r\n            for(int i = 0; i < input.getNumVertices(); i++)\r\n            {\r\n                if(vertexIndexMap[i] != -1)\r\n                {\r\n                    vertexIndexMapping.set(vertexIndexMap[i], i);\r\n                }\r\n            }\r\n        }\r\n        \r\n        return output;\r\n    }\r\n    \r\n    \r\n    /**\r\n     * Interface for accessing a property index (vertex, texture coordinate\r\n     * or normal) from a {@link ReadableObj}\r\n     */\r\n    private interface PropertyIndexAccessor\r\n    {\r\n        /**\r\n         * Returns the index of the specified property from the given \r\n         * {@link ReadableObj}\r\n         * \r\n         * @param input The {@link ReadableObj}\r\n         * @param face The face\r\n         * @param vertexNumber The number of the vertex in the face\r\n         * @return The index of the property\r\n         * @throws IndexOutOfBoundsException If the index is out of bounds\r\n         */\r\n        int getPropertyIndex(\r\n            ReadableObj input, ObjFace face, int vertexNumber);\r\n        \r\n        /**\r\n         * Returns whether the given face has indices for the property\r\n         * that may be queries with this accessor\r\n         * \r\n         * @param face The face\r\n         * @return Whether the face has the property \r\n         */\r\n        boolean hasProperty(ObjFace face);\r\n    }\r\n    \r\n\r\n    /**\r\n     * Ensures that two vertices with different texture coordinates are \r\n     * actually two different vertices with different indices.<br>\r\n     * <br>\r\n     * Two faces may reference the same vertex in the OBJ file. But different \r\n     * texture coordinates may be assigned to the same vertex in \r\n     * both faces. The vertex that requires two different properties will be \r\n     * duplicated in the output, and the indices in one face will be updated\r\n     * appropriately.<br>\r\n     * <br>\r\n     * This process solely operates on the <i>indices</i> of the properties.\r\n     * It will not check whether the <i>value</i> of two properties (with\r\n     * different indices) are actually equal.\r\n     * \r\n     * @param input The input {@link ReadableObj}\r\n     * @return The resulting {@link Obj}\r\n     */\r\n    public static Obj makeTexCoordsUnique(ReadableObj input)\r\n    {\r\n        return makeTexCoordsUnique(input, null, Objs.create());\r\n    }\r\n    \r\n    /**\r\n     * Ensures that two vertices with different texture coordinates are \r\n     * actually two different vertices with different indices.<br>\r\n     * <br>\r\n     * Two faces may reference the same vertex in the OBJ file. But different \r\n     * texture coordinates may be assigned to the same vertex in \r\n     * both faces. The vertex that requires two different properties will be \r\n     * duplicated in the output, and the indices in one face will be updated\r\n     * appropriately.<br>\r\n     * <br>\r\n     * This process solely operates on the <i>indices</i> of the properties.\r\n     * It will not check whether the <i>value</i> of two properties (with\r\n     * different indices) are actually equal.\r\n     * <br>\r\n     * The indexMapping may be <code>null</code>. Otherwise it is assumed that \r\n     * it already contains the index mapping that was obtained by a call \r\n     * to {@link #groupToObj(ReadableObj, ObjGroup, List, WritableObj)}, and \r\n     * this mapping will be updated appropriately.\r\n     * \r\n     * @param <T> The type of the output\r\n     * @param input The input {@link ReadableObj}\r\n     * @param indexMapping The optional index mapping\r\n     * @param output The output {@link WritableObj}\r\n     * @return The given output\r\n     */\r\n    public static <T extends WritableObj> T makeTexCoordsUnique(\r\n        ReadableObj input, List<Integer> indexMapping, T output)\r\n    {\r\n        PropertyIndexAccessor accessor = new PropertyIndexAccessor()\r\n        {\r\n            @Override\r\n            public int getPropertyIndex(\r\n                ReadableObj input, ObjFace face, int vertexNumber)\r\n            {\r\n                return face.getTexCoordIndex(vertexNumber);\r\n            }\r\n\r\n            @Override\r\n            public boolean hasProperty(ObjFace face)\r\n            {\r\n                return face.containsTexCoordIndices();\r\n            }\r\n        };\r\n        makePropertiesUnique(input, accessor, indexMapping, output);\r\n        return output;\r\n    }\r\n    \r\n    \r\n    /**\r\n     * Ensures that two vertices with different normals are \r\n     * actually two different vertices with different indices.<br>\r\n     * <br>\r\n     * Two faces may reference the same vertex in the OBJ file. But different \r\n     * normals may be assigned to the same vertex in \r\n     * both faces. The vertex that requires two different properties will be \r\n     * duplicated in the output, and the indices in one face will be updated\r\n     * appropriately.<br>\r\n     * <br>\r\n     * This process solely operates on the <i>indices</i> of the properties.\r\n     * It will not check whether the <i>value</i> of two properties (with\r\n     * different indices) are actually equal.\r\n     * \r\n     * @param input The input {@link ReadableObj}\r\n     * @return The resulting {@link Obj}\r\n     */\r\n    public static Obj makeNormalsUnique(ReadableObj input)\r\n    {\r\n        return makeNormalsUnique(input, null, Objs.create());\r\n    }\r\n    \r\n\r\n    /**\r\n     * Ensures that two vertices with different normals are \r\n     * actually two different vertices with different indices.<br>\r\n     * <br>\r\n     * Two faces may reference the same vertex in the OBJ file. But different \r\n     * normals may be assigned to the same vertex in \r\n     * both faces. The vertex that requires two different properties will be \r\n     * duplicated in the output, and the indices in one face will be updated\r\n     * appropriately.<br>\r\n     * <br>\r\n     * This process solely operates on the <i>indices</i> of the properties.\r\n     * It will not check whether the <i>value</i> of two properties (with\r\n     * different indices) are actually equal.\r\n     * <br>\r\n     * The indexMapping may be <code>null</code>. Otherwise it is assumed that \r\n     * it already contains the index mapping that was obtained by a call \r\n     * to {@link #groupToObj(ReadableObj, ObjGroup, List, WritableObj)}, and \r\n     * this mapping will be updated appropriately.\r\n     * \r\n     * @param <T> The type of the output\r\n     * @param input The input {@link ReadableObj}\r\n     * @param indexMapping The optional index mapping\r\n     * @param output The output {@link WritableObj}\r\n     * @return The given output\r\n     */\r\n    public static <T extends WritableObj> T makeNormalsUnique(\r\n        ReadableObj input, List<Integer> indexMapping, T output)\r\n    {\r\n        PropertyIndexAccessor accessor = new PropertyIndexAccessor()\r\n        {\r\n            @Override\r\n            public int getPropertyIndex(\r\n                ReadableObj input, ObjFace face, int vertexNumber)\r\n            {\r\n                return face.getNormalIndex(vertexNumber);\r\n            }\r\n            \r\n            @Override\r\n            public boolean hasProperty(ObjFace face)\r\n            {\r\n                return face.containsNormalIndices();\r\n            }\r\n        };\r\n        makePropertiesUnique(input, accessor, indexMapping, output);\r\n        return output;\r\n    }\r\n    \r\n\r\n    /**\r\n     * Ensures that two vertices with different properties are \r\n     * actually two different vertices with different indices.<br>\r\n     * <br>\r\n     * Two faces may reference the same vertex in the OBJ file. But different \r\n     * normals or texture coordinates may be assigned to the same vertex in \r\n     * both faces. The vertex that requires two different properties will be \r\n     * duplicated in the output, and the indices in one face will be updated\r\n     * appropriately.<br>\r\n     * <br>\r\n     * This process solely operates on the <i>indices</i> of the properties.\r\n     * It will not check whether the <i>value</i> of two properties (with\r\n     * different indices) are actually equal.\r\n     * <br>\r\n     * The indexMapping may be <code>null</code>. Otherwise it is assumed that \r\n     * it already contains the index mapping that was obtained by a call \r\n     * to {@link #groupToObj(ReadableObj, ObjGroup, List, WritableObj)}, and \r\n     * this mapping will be updated appropriately.\r\n     * \r\n     * @param input The input {@link ReadableObj}\r\n     * @param propertyIndexAccessor The accessor for the property index \r\n     * @param indexMapping The optional index mapping\r\n     * @param output The output {@link WritableObj}\r\n     */\r\n    private static void makePropertiesUnique(\r\n        ReadableObj input, PropertyIndexAccessor propertyIndexAccessor, \r\n        List<Integer> indexMapping, WritableObj output)\r\n    {\r\n        output.setMtlFileNames(input.getMtlFileNames());\r\n        addAll(input, output);\r\n        \r\n        int[] usedPropertyIndices = new int[input.getNumVertices()];\r\n        Arrays.fill(usedPropertyIndices, -1);\r\n        List<FloatTuple> extendedVertices = new ArrayList<>();\r\n\r\n        for(int i = 0; i < input.getNumFaces(); i++)\r\n        {\r\n            ObjFace inputFace = input.getFace(i);\r\n            \r\n            activateGroups(input, inputFace, output);\r\n            \r\n            ObjFace outputFace = inputFace;\r\n            \r\n            if (propertyIndexAccessor.hasProperty(inputFace))\r\n            {\r\n                DefaultObjFace extendedOutputFace = null;\r\n                \r\n                for(int j = 0; j < outputFace.getNumVertices(); j++)\r\n                {\r\n                    int vertexIndex = outputFace.getVertexIndex(j);\r\n                    int propertyIndex = \r\n                        propertyIndexAccessor.getPropertyIndex(\r\n                            input, outputFace, j);\r\n    \r\n                    // Check if the property of the vertex with the current\r\n                    // index already has been used, and it is not equal to\r\n                    // the property that it has in the current face\r\n                    if(usedPropertyIndices[vertexIndex] != -1 &&\r\n                       usedPropertyIndices[vertexIndex] != propertyIndex)  \r\n                    {\r\n                        FloatTuple vertex = input.getVertex(vertexIndex);\r\n    \r\n                        // Add the vertex which has multiple properties once\r\n                        // more to the output, and update all indices that \r\n                        // now have to point to the \"new\" vertex\r\n                        int extendedVertexIndex = \r\n                            input.getNumVertices() + extendedVertices.size();\r\n                        extendedVertices.add(vertex);\r\n                        output.addVertex(vertex);\r\n                        \r\n                        if (extendedOutputFace == null)\r\n                        {\r\n                            extendedOutputFace = ObjFaces.create(inputFace);\r\n                        }\r\n                        extendedOutputFace.setVertexIndex(\r\n                            j, extendedVertexIndex);\r\n    \r\n                        if(indexMapping != null)\r\n                        {\r\n                            int indexInObj = indexMapping.get(vertexIndex);\r\n                            indexMapping.add(indexInObj);\r\n                        }\r\n                    }\r\n                    else\r\n                    {\r\n                        usedPropertyIndices[vertexIndex] = propertyIndex;\r\n                    }\r\n                }\r\n                if (extendedOutputFace != null)\r\n                {\r\n                    outputFace = extendedOutputFace;\r\n                }\r\n            }\r\n            output.addFace(outputFace);\r\n        }\r\n    }\r\n    \r\n    \r\n    /**\r\n     * Add all vertices, texture coordinates and normals of the given \r\n     * {@link ReadableObj} to the given {@link WritableObj}\r\n     * \r\n     * @param input The {@link ReadableObj}\r\n     * @param output The {@link WritableObj}\r\n     */\r\n    private static void addAll(ReadableObj input, WritableObj output)\r\n    {\r\n        for (int i=0; i<input.getNumVertices(); i++)\r\n        {\r\n            output.addVertex(input.getVertex(i));\r\n        }\r\n        for (int i=0; i<input.getNumTexCoords(); i++)\r\n        {\r\n            output.addTexCoord(input.getTexCoord(i));\r\n        }\r\n        for (int i=0; i<input.getNumNormals(); i++)\r\n        {\r\n            output.addNormal(input.getNormal(i));\r\n        }\r\n    }\r\n    \r\n    /**\r\n     * Add all vertices, texture coordinates, normals and faces of the given\r\n     * {@link ReadableObj} to the given output {@link Obj}.<br>\r\n     * <br>\r\n     * Note that this may cause new groups or material groups to be created\r\n     * in the output OBJ. If the output OBJ contains group names or material \r\n     * group names that also exist in the input OBJ, then the elements of the\r\n     * input OBJ will be added to these existing groups.  \r\n     * \r\n     * @param input The {@link ReadableObj}\r\n     * @param output The target {@link Obj}\r\n     */\r\n    public static void add(ReadableObj input, Obj output)\r\n    {\r\n        int verticesOffset = output.getNumVertices();\r\n        for (int i=0; i<input.getNumVertices(); i++)\r\n        {\r\n            output.addVertex(input.getVertex(i));\r\n        }\r\n        \r\n        int texCoordsOffset = output.getNumTexCoords();\r\n        for (int i=0; i<input.getNumTexCoords(); i++)\r\n        {\r\n            output.addTexCoord(input.getTexCoord(i));\r\n        }\r\n        \r\n        int normalsOffset = output.getNumNormals();\r\n        for (int i=0; i<input.getNumNormals(); i++)\r\n        {\r\n            output.addNormal(input.getNormal(i));\r\n        }\r\n        \r\n        for(int i = 0; i < input.getNumFaces(); i++)\r\n        {\r\n            ObjFace inputFace = input.getFace(i);\r\n            \r\n            activateGroups(input, inputFace, output);\r\n            \r\n            DefaultObjFace outputFace = ObjFaces.createWithOffsets(\r\n                inputFace, verticesOffset, texCoordsOffset, normalsOffset);\r\n            output.addFace(outputFace);\r\n        }\r\n    }\r\n    \r\n\r\n    /**\r\n     * Converts the given {@link ReadableObj} data into data that uses the\r\n     * same indices for vertices, texture coordinates and normals, and\r\n     * returns the result.<br>\r\n     * <br>\r\n     * Note that the result may contain ambiguous texture coordinates and \r\n     * normals, unless they have been made unique in the input before with\r\n     * {@link #makeTexCoordsUnique(ReadableObj)} and \r\n     * {@link #makeNormalsUnique(ReadableObj)}, respectively.\r\n     * \r\n     * @param input The input {@link ReadableObj} \r\n     * @return The resulting {@link Obj}\r\n     */\r\n    public static Obj makeVertexIndexed(ReadableObj input)\r\n    {\r\n        return makeVertexIndexed(input, Objs.create());\r\n    }\r\n    \r\n    /**\r\n     * Converts the given {@link ReadableObj} data into data that uses the\r\n     * same indices for vertices, texture coordinates and normals, and\r\n     * stores the result in the given {@link WritableObj}.<br>\r\n     * <br>\r\n     * Note that the result may contain ambiguous texture coordinates and \r\n     * normals, unless they have been made unique in the input before with\r\n     * {@link #makeTexCoordsUnique(ReadableObj)} and \r\n     * {@link #makeNormalsUnique(ReadableObj)}, respectively.\r\n     * \r\n     * @param <T> The type of the output\r\n     * @param input The input {@link ReadableObj}\r\n     * @param output The output {@link WritableObj}\r\n     * @return The given output\r\n     */\r\n    public static <T extends WritableObj> T makeVertexIndexed(\r\n        ReadableObj input, T output)\r\n    {\r\n        output.setMtlFileNames(input.getMtlFileNames());\r\n        for (int i=0; i<input.getNumVertices(); i++)\r\n        {\r\n            output.addVertex(input.getVertex(i));\r\n        }\r\n        \r\n        boolean foundTexCoords = false;\r\n        boolean foundNormals = false;\r\n        int[] texCoordIndicesForVertexIndices = new int[input.getNumVertices()];\r\n        int[] normalIndicesForVertexIndices = new int[input.getNumVertices()];\r\n        for(int i = 0; i < input.getNumFaces(); i++)\r\n        {\r\n            ObjFace inputFace = input.getFace(i);\r\n            for(int j = 0; j < inputFace.getNumVertices(); j++)\r\n            {\r\n                int vertexIndex = inputFace.getVertexIndex(j);\r\n                if (inputFace.containsTexCoordIndices())\r\n                {\r\n                    int texCoordIndex = inputFace.getTexCoordIndex(j);\r\n                    texCoordIndicesForVertexIndices[vertexIndex] =\r\n                        texCoordIndex;\r\n                    foundTexCoords = true;\r\n                }\r\n                if (inputFace.containsNormalIndices())\r\n                {\r\n                    int normalIndex = inputFace.getNormalIndex(j);\r\n                    normalIndicesForVertexIndices[vertexIndex] =\r\n                        normalIndex;\r\n                    foundNormals = true;\r\n                }\r\n            }\r\n        }\r\n        \r\n        if (foundTexCoords)\r\n        {\r\n            for (int i=0; i<input.getNumVertices(); i++)\r\n            {\r\n                int texCoordIndex = texCoordIndicesForVertexIndices[i];\r\n                FloatTuple texCoord = input.getTexCoord(texCoordIndex);\r\n                output.addTexCoord(texCoord);\r\n            }\r\n        }\r\n        \r\n        if (foundNormals)\r\n        {\r\n            for (int i=0; i<input.getNumVertices(); i++)\r\n            {\r\n                int normalIndex = normalIndicesForVertexIndices[i];\r\n                FloatTuple normal = input.getNormal(normalIndex);\r\n                output.addNormal(normal);\r\n            }\r\n        }\r\n        \r\n        for(int i = 0; i < input.getNumFaces(); i++)\r\n        {\r\n            ObjFace inputFace = input.getFace(i);\r\n            \r\n            activateGroups(input, inputFace, output);\r\n            \r\n            DefaultObjFace outputFace = ObjFaces.create(inputFace);\r\n            if (inputFace.containsTexCoordIndices())\r\n            {\r\n                for (int j=0; j<inputFace.getNumVertices(); j++)\r\n                {\r\n                    outputFace.setTexCoordIndex(j, \r\n                        outputFace.getVertexIndex(j));\r\n                }\r\n            }\r\n            if (inputFace.containsNormalIndices())\r\n            {\r\n                for (int j=0; j<inputFace.getNumVertices(); j++)\r\n                {\r\n                    outputFace.setNormalIndex(j, \r\n                        outputFace.getVertexIndex(j));\r\n                }\r\n            }\r\n            output.addFace(outputFace);\r\n        }\r\n        return output;\r\n    }\r\n    \r\n    \r\n    /**\r\n     * Set the active group names and material group name in the given\r\n     * output based on the group names and material group name that the\r\n     * given face activated in the input\r\n     * \r\n     * @param input The input {@link ReadableObj} \r\n     * @param face The {@link ObjFace} to perform the activation for\r\n     * @param output The output {@link WritableObj} \r\n     */\r\n    private static void activateGroups(\r\n        ReadableObj input, ObjFace face, WritableObj output)\r\n    {\r\n        Set<String> activatedGroupNames = \r\n            input.getActivatedGroupNames(face);\r\n        if (activatedGroupNames != null)\r\n        {\r\n            output.setActiveGroupNames(\r\n                activatedGroupNames);\r\n        }\r\n        String activatedMaterialGroupName = \r\n            input.getActivatedMaterialGroupName(face);\r\n        if (activatedMaterialGroupName != null)\r\n        {\r\n            output.setActiveMaterialGroupName(\r\n                activatedMaterialGroupName);\r\n        }\r\n    }\r\n    \r\n    \r\n    /**\r\n     * Create a multi-line, formatted string containing information about\r\n     * the given {@link ReadableObj}. This method is solely intended for \r\n     * debugging. It should not be considered as part of the public API. \r\n     * The exact output is not specified.\r\n     *  \r\n     * @param obj The {@link ReadableObj}\r\n     * @return The info string\r\n     */\r\n    public static String createInfoString(ReadableObj obj)\r\n    {\r\n        StringBuilder sb = new StringBuilder();\r\n        sb.append(\"Obj:\"+\"\\n\");\r\n        sb.append(\"    mtlFileNames     : \"+obj.getMtlFileNames()+\"\\n\");\r\n        sb.append(\"    numVertices      : \"+obj.getNumVertices()+\"\\n\");\r\n        sb.append(\"    numTexCoords     : \"+obj.getNumTexCoords()+\"\\n\");\r\n        sb.append(\"    numNormals       : \"+obj.getNumNormals()+\"\\n\");\r\n        sb.append(\"    numFaces         : \"+obj.getNumFaces()+\"\\n\");\r\n        sb.append(\"    numGroups        : \"+obj.getNumGroups()+\"\\n\");\r\n        for (int i=0; i<obj.getNumGroups(); i++)\r\n        {\r\n            ObjGroup objGroup = obj.getGroup(i);\r\n            sb.append(\"        Group \"+i+\":\"+\"\\n\");\r\n            sb.append(\"            name    : \"+objGroup.getName()+\"\\n\");\r\n            sb.append(\"            numFaces: \"+objGroup.getNumFaces()+\"\\n\");\r\n        }\r\n        sb.append(\"    numMaterialGroups: \"+obj.getNumMaterialGroups()+\"\\n\");\r\n        for (int i=0; i<obj.getNumMaterialGroups(); i++)\r\n        {\r\n            ObjGroup objGroup = obj.getMaterialGroup(i);\r\n            sb.append(\"        MaterialGroup \"+i+\":\"+\"\\n\");\r\n            sb.append(\"            name    : \"+objGroup.getName()+\"\\n\");\r\n            sb.append(\"            numFaces: \"+objGroup.getNumFaces()+\"\\n\");\r\n        }\r\n        return sb.toString();\r\n    }\r\n\r\n\r\n\r\n    /**\r\n     * Private constructor to prevent instantiation.\r\n     */\r\n    private ObjUtils()\r\n    {\r\n        // Private constructor to prevent instantiation.\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/ObjWriter.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\nimport java.io.IOException;\r\nimport java.io.OutputStream;\r\nimport java.io.OutputStreamWriter;\r\nimport java.io.Writer;\r\nimport java.util.Collections;\r\nimport java.util.List;\r\nimport java.util.Set;\r\n\r\n/**\r\n * A class that may write an {@link ReadableObj} to a stream.\r\n */\r\npublic class ObjWriter\r\n{\r\n    /**\r\n     * Writes the given {@link ReadableObj} to the given stream. The caller\r\n     * is responsible for closing the stream.\r\n     * \r\n     * @param input The {@link ReadableObj} to write.\r\n     * @param outputStream The stream to write to.\r\n     * @throws IOException If an IO error occurs.\r\n     */\r\n    public static void write(ReadableObj input, OutputStream outputStream) \r\n        throws IOException\r\n    {\r\n        OutputStreamWriter outputStreamWriter = \r\n            new OutputStreamWriter(outputStream);\r\n        write(input, outputStreamWriter);\r\n    }\r\n    \r\n    /**\r\n     * Writes the given {@link ReadableObj} to the given writer. The caller\r\n     * is responsible for closing the writer.\r\n     * \r\n     * @param input The {@link ReadableObj} to write.\r\n     * @param writer The writer to write to.\r\n     * @throws IOException If an IO error occurs.\r\n     */\r\n    public static void write(ReadableObj input, Writer writer) \r\n        throws IOException\r\n    {\r\n        // Write the mtl file name\r\n        List<String> mtlFileNames = input.getMtlFileNames();\r\n        if (!mtlFileNames.isEmpty())\r\n        {\r\n            writer.write(\"mtllib \");\r\n            for (int i=0; i<mtlFileNames.size(); i++)\r\n            {\r\n                if (i > 0)\r\n                {\r\n                    writer.write(\" \");\r\n                }\r\n                writer.write(mtlFileNames.get(i));\r\n            }\r\n            writer.write(\"\\n\");\r\n        }\r\n        \r\n        // Write the vertex- texture coordinate and normal data\r\n        for(int i = 0; i < input.getNumVertices(); i++)\r\n        {\r\n            FloatTuple vertex = input.getVertex(i);\r\n            writer.write(\r\n                \"v \"+FloatTuples.createString(vertex) + \"\\n\");\r\n        }\r\n        for(int i = 0; i < input.getNumTexCoords(); i++)\r\n        {\r\n            FloatTuple texCoord = input.getTexCoord(i);\r\n            writer.write(\r\n                \"vt \"+FloatTuples.createString(texCoord) + \"\\n\");\r\n        }\r\n        for(int i = 0; i < input.getNumNormals(); i++)\r\n        {\r\n            FloatTuple normal = input.getNormal(i);\r\n            writer.write(\r\n                \"vn \"+FloatTuples.createString(normal) + \"\\n\");\r\n        }\r\n\r\n        boolean skipWritingDefaultGroup = true; \r\n        for(int i = 0; i < input.getNumFaces(); i++)\r\n        {\r\n            ObjFace face = input.getFace(i);\r\n            \r\n            Set<String> activatedGroupNames = \r\n                input.getActivatedGroupNames(face);\r\n            if (activatedGroupNames != null)\r\n            {\r\n                boolean isDefaultGroup = \r\n                    activatedGroupNames.equals(\r\n                        Collections.singleton(\"default\"));\r\n                if (!skipWritingDefaultGroup || !isDefaultGroup)\r\n                {\r\n                    writer.write(\"g \");\r\n                    for (String activatedGroupName : activatedGroupNames)\r\n                    {\r\n                        writer.write(activatedGroupName);\r\n                        writer.write(\" \");\r\n                    }\r\n                    writer.write(\"\\n\");\r\n                }\r\n                skipWritingDefaultGroup = false;\r\n            }\r\n                \r\n            String activatedMaterialGroupName =\r\n                input.getActivatedMaterialGroupName(face);\r\n            if (activatedMaterialGroupName != null)\r\n            {\r\n                writer.write(\r\n                    \"usemtl \" + activatedMaterialGroupName + \"\\n\");\r\n            }\r\n            String faceString = ObjFaces.createString(face);\r\n            writer.write(faceString + \"\\n\");\r\n        }\r\n        writer.flush();\r\n    }\r\n    \r\n    /**\r\n     * Private constructor to prevent instantiation\r\n     */\r\n    private ObjWriter()\r\n    {\r\n        // Private constructor to prevent instantiation\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/Objs.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\npackage de.javagl.obj;\r\n\r\nimport java.nio.FloatBuffer;\r\nimport java.nio.IntBuffer;\r\n\r\n/**\r\n * Methods to create {@link Obj} instances\r\n */\r\npublic class Objs\r\n{\r\n    /**\r\n     * Creates a new default {@link Obj}\r\n     * \r\n     * @return The {@link Obj}\r\n     */\r\n    public static Obj create()\r\n    {\r\n        return new DefaultObj();\r\n    }\r\n    \r\n    /**\r\n     * Create an {@link Obj} from the given (single-) indexed triangle data.<br>\r\n     * <br>\r\n     * This method will not perform any sanity checks. It silently assumes\r\n     * that the given indices are valid indices for the given buffers.\r\n     * <br>\r\n     * The texture coordinates and normals may be <code>null</code>.<br>\r\n     * <br>\r\n     * The buffers that are not <code>null</code> will be accessed using\r\n     * the <b>absolute</b> access methods, up to their capacity. This means \r\n     * that the position of the given buffers will not be affected. \r\n     * \r\n     * @param indices The indices. Three consecutive elements of this buffer\r\n     * are assumed to form one triangle.\r\n     * @param vertices The vertices\r\n     * @param texCoords The texture coordinates, assumed to be 2D.\r\n     * @param normals The normals\r\n     * @return The {@link Obj}\r\n     * @throws NullPointerException If the indices or vertices are\r\n     * <code>null</code>.\r\n     */\r\n    public static Obj createFromIndexedTriangleData(\r\n        IntBuffer indices, \r\n        FloatBuffer vertices, \r\n        FloatBuffer texCoords, \r\n        FloatBuffer normals)\r\n    {\r\n        int numTriangles = indices.capacity() / 3;\r\n        int numVertices = vertices.capacity() / 3;\r\n\r\n        Obj obj = Objs.create();\r\n        \r\n        for (int i=0; i<numVertices; i++)\r\n        {\r\n            float x = vertices.get(i * 3 + 0);\r\n            float y = vertices.get(i * 3 + 1);\r\n            float z = vertices.get(i * 3 + 2);\r\n            obj.addVertex(x, y, z);\r\n        }\r\n        \r\n        if (texCoords != null)\r\n        {\r\n            int numTexCoords = texCoords.capacity() / 2;\r\n            for (int i=0; i<numTexCoords; i++)\r\n            {\r\n                float x = texCoords.get(i * 2 + 0);\r\n                float y = texCoords.get(i * 2 + 1);\r\n                obj.addTexCoord(x, y);\r\n            }\r\n        }\r\n        if (normals != null)\r\n        {\r\n            int numNormals = normals.capacity() / 3;\r\n            for (int i=0; i<numNormals; i++)\r\n            {\r\n                float x = normals.get(i * 3 + 0);\r\n                float y = normals.get(i * 3 + 1);\r\n                float z = normals.get(i * 3 + 2);\r\n                obj.addNormal(x, y, z);\r\n            }\r\n        }\r\n        \r\n        for (int i=0; i<numTriangles; i++)\r\n        {\r\n            int i0 = indices.get(i * 3 + 0);\r\n            int i1 = indices.get(i * 3 + 1);\r\n            int i2 = indices.get(i * 3 + 2);\r\n\r\n            int[] v = { i0, i1, i2 };\r\n            int[] vt = null;\r\n            int[] vn = null;\r\n            if (texCoords != null)\r\n            {\r\n                vt = v;\r\n            }\r\n            if (normals != null)\r\n            {\r\n                vn = v;\r\n            }\r\n            obj.addFace(v, vt, vn);\r\n        }\r\n        return obj;\r\n    }\r\n    \r\n    /**\r\n     * Private constructor to prevent instantiation\r\n     */\r\n    private Objs()\r\n    {\r\n        // Private constructor to prevent instantiation\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/ReadableObj.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\n\r\nimport java.util.List;\r\nimport java.util.Set;\r\n\r\n/**\r\n * Interface for classes providing part of the data that may be stored in \r\n * an OBJ file. The data represented by this interface is:<br>\r\n * <ul>\r\n *   <li>Vertices</li>\r\n *   <li>Texture coordinates</li>\r\n *   <li>Normals</li>\r\n *   <li>Faces</li>\r\n *   <li>Groups</li>\r\n *   <li>Material groups</li>\r\n * </ul>\r\n */\r\npublic interface ReadableObj\r\n{\r\n    /**\r\n     * Returns the number of vertices in the Obj.\r\n     * \r\n     * @return The number of vertices in the Obj.\r\n     */\r\n    int getNumVertices();\r\n\r\n    /**\r\n     * Returns the vertex with the given index. Note that the index\r\n     * is <b>0</b>-based, in contrast to the <b>1</b>-based indices of the\r\n     * actual OBJ file.\r\n     * \r\n     * @param index The index of the vertex\r\n     * @return The vertex with the given index\r\n     * @throws IndexOutOfBoundsException If the index is negative or not\r\n     * smaller than {@link #getNumVertices()}\r\n     */\r\n    FloatTuple getVertex(int index);\r\n\r\n    \r\n    /**\r\n     * Returns the number of texture coordinates in the Obj.\r\n     * \r\n     * @return The number of texture coordinates in the Obj.\r\n     */\r\n    int getNumTexCoords();\r\n\r\n    /**\r\n     * Returns the texture coordinate with the given index. Note that the \r\n     * is <b>0</b>-based, in contrast to the <b>1</b>-based indices of the\r\n     * actual OBJ file.\r\n     * \r\n     * @param index The index of the texture coordinate\r\n     * @return The texture coordinate with the given index\r\n     * @throws IndexOutOfBoundsException If the index is negative or not\r\n     * smaller than {@link #getNumTexCoords()}\r\n     */\r\n    FloatTuple getTexCoord(int index);\r\n\r\n    \r\n    /**\r\n     * Returns the number of normals in the Obj.\r\n     * \r\n     * @return The number of normals in the Obj.\r\n     */\r\n    int getNumNormals();\r\n\r\n    /**\r\n     * Returns the normal with the given index. Note that the index\r\n     * is <b>0</b>-based, in contrast to the <b>1</b>-based indices of the\r\n     * actual OBJ file.\r\n     * \r\n     * @param index The index of the normal\r\n     * @return The normal with the given index\r\n     * @throws IndexOutOfBoundsException If the index is negative or not\r\n     * smaller than {@link #getNumNormals()}\r\n     */\r\n    FloatTuple getNormal(int index);\r\n    \r\n    \r\n    /**\r\n     * Returns the number of faces in the Obj.\r\n     * \r\n     * @return The number of faces in the Obj.\r\n     */\r\n    int getNumFaces();\r\n\r\n    /**\r\n     * Returns the face with the given index. \r\n     * \r\n     * @param index The index of the face.\r\n     * @return The face with the given index\r\n     * @throws IndexOutOfBoundsException If the index is negative or not\r\n     * smaller than {@link #getNumFaces()}\r\n     */\r\n    ObjFace getFace(int index);\r\n\r\n    /**\r\n     * Returns an unmodifiable set containing the names of the groups that \r\n     * are activated with the given face. If the groups that are \r\n     * activated with the given face are the same as for the previous face, \r\n     * then <code>null</code> will be returned.\r\n     *  \r\n     * @param face The face\r\n     * @return The names of the groups that are activated with the\r\n     * given face\r\n     */\r\n    Set<String> getActivatedGroupNames(ObjFace face);\r\n\r\n    /**\r\n     * Returns the name of the material group that is activated with the\r\n     * given face. If the material group that is activated with the given\r\n     * face is the same as for the previous face, then <code>null</code>\r\n     * will be returned.\r\n     *  \r\n     * @param face The face\r\n     * @return The name of the material group that is activated with the\r\n     * given face\r\n     */\r\n    String getActivatedMaterialGroupName(ObjFace face);\r\n    \r\n    /**\r\n     * Returns the number of groups in this Obj.\r\n     *\r\n     * @return The number of groups in this Obj.\r\n     */\r\n    int getNumGroups();\r\n\r\n    /**\r\n     * Returns the group with the given index.\r\n     * \r\n     * @param index The index of the group.\r\n     * @return The group with the given index.\r\n     * @throws IndexOutOfBoundsException If the index is negative or not\r\n     * smaller than {@link #getNumGroups()}\r\n     */\r\n    ObjGroup getGroup(int index);\r\n\r\n    /**\r\n     * Returns the group with the given name, or <code>null</code> if\r\n     * there is no such group in this Obj.\r\n     * \r\n     * @param name The name of the group.\r\n     * @return The group with the given name.\r\n     */\r\n    ObjGroup getGroup(String name);\r\n\r\n    \r\n    \r\n    /**\r\n     * Returns the number of material groups in this Obj.\r\n     *\r\n     * @return The number of material groups in this Obj.\r\n     */\r\n    int getNumMaterialGroups();\r\n\r\n    /**\r\n     * Returns the material group with the given index.\r\n     * \r\n     * @param index The index of the material group.\r\n     * @return The material group with the given index.\r\n     * @throws IndexOutOfBoundsException If the index is negative or not\r\n     * smaller than {@link #getNumMaterialGroups()}\r\n     */\r\n    ObjGroup getMaterialGroup(int index);\r\n\r\n    /**\r\n     * Returns the material group with the given name, or <code>null</code> if\r\n     * there is no such group in this Obj.\r\n     * \r\n     * @param name The name of the material group.\r\n     * @return The material group with the given name.\r\n     */\r\n    ObjGroup getMaterialGroup(String name);\r\n\r\n    \r\n    /**\r\n     * Returns an unmodifiable list containing the names of the MTL file \r\n     * that are associated with this OBJ, as they have been read from \r\n     * the <code>mtllib</code> line. \r\n     * This may be an empty list, if no MTL file names have been read.\r\n     * \r\n     * @return The names of the MTL files.\r\n     */\r\n    List<String> getMtlFileNames();\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/TextureOptions.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n *\r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\npackage de.javagl.obj;\r\n\r\n/**\r\n * Interface for the texture options that are part of a map definition\r\n * of an {@link Mtl}. For details about the semantics of the properties\r\n * in this interface, refer to the MTL specification.\r\n */\r\npublic interface TextureOptions\r\n{\r\n    /**\r\n     * Returns the file name of the texture\r\n     *\r\n     * @return The file name\r\n     */\r\n    String getFileName();\r\n\r\n    /**\r\n     * Set the file name of the texture\r\n     *\r\n     * @param fileName The file name\r\n     */\r\n    void setFileName(String fileName);\r\n\r\n    /**\r\n     * Returns the horizontal texture blending state (<code>-blendu</code>),\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return Whether horizontal blending is enabled\r\n     */\r\n    Boolean isBlendu();\r\n\r\n    /**\r\n     * Set the horizontal texture blending state\r\n     *\r\n     * @param blendu The blending state\r\n     */\r\n    void setBlendu(Boolean blendu);\r\n\r\n    /**\r\n     * Returns the vertical texture blending state (<code>-blendv</code>),\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return Whether vertical blending is enabled\r\n     */\r\n    Boolean isBlendv();\r\n\r\n    /**\r\n     * Set the vertical texture blending state\r\n     *\r\n     * @param blendv The blending state\r\n     */\r\n    void setBlendv(Boolean blendv);\r\n\r\n    /**\r\n     * Returns the color correction state (<code>-cc</code>),\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The color correction state\r\n     */\r\n    Boolean isCc();\r\n\r\n    /**\r\n     * Set the color correction state\r\n     *\r\n     * @param cc The color correction state\r\n     */\r\n    void setCc(Boolean cc);\r\n\r\n    /**\r\n     * Returns the boost mip-map sharpness value (<code>-boost</code>),\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The boost mip-map sharpness.\r\n     */\r\n    Float getBoost();\r\n\r\n    /**\r\n     * Set the mip-map boost value\r\n     *\r\n     * @param boost The boost value\r\n     */\r\n    void setBoost(Float boost);\r\n\r\n    /**\r\n     * Returns the texture map modifier (<code>-mm</code>),\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The modified texture map contrast.\r\n     */\r\n    FloatTuple getMm();\r\n\r\n    /**\r\n     * Set the texture map modifier values\r\n     *\r\n     * @param base The base\r\n     * @param gain The gain\r\n     */\r\n    void setMm(Float base, Float gain);\r\n\r\n    /**\r\n     * Returns the origin offset (<code>-o</code>),\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The origin offset.\r\n     */\r\n    FloatTuple getO();\r\n\r\n    /**\r\n     * Set the origin offset\r\n     *\r\n     * @param u The u component\r\n     * @param v The v component\r\n     * @param w The w component\r\n     */\r\n    void setO(Float u, Float v, Float w);\r\n\r\n    /**\r\n     * Returns the scale (<code>-s</code>),\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The scale.\r\n     */\r\n    FloatTuple getS();\r\n\r\n    /**\r\n     * Set the scale\r\n     *\r\n     * @param u The u component\r\n     * @param v The v component\r\n     * @param w The w component\r\n     */\r\n    void setS(Float u, Float v, Float w);\r\n\r\n    /**\r\n     * Returns the turbulence (<code>-t</code>),\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The turbulence.\r\n     */\r\n    FloatTuple getT();\r\n\r\n    /**\r\n     * Set the turbulence\r\n     *\r\n     * @param u The u component\r\n     * @param v The v component\r\n     * @param w The w component\r\n     */\r\n    void setT(Float u, Float v, Float w);\r\n\r\n    /**\r\n     * Returns the texture resolution (<code>-texres</code>),\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The texture resolution.\r\n     */\r\n    Float getTexres();\r\n\r\n    /**\r\n     * Set the texture resolution\r\n     *\r\n     * @param texres The texture resolution\r\n     */\r\n    void setTexres(Float texres);\r\n\r\n    /**\r\n     * Returns the clamping state (<code>-clamp</code>),\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return Whether or not clamping is enabled.\r\n     */\r\n    Boolean isClamp();\r\n\r\n    /**\r\n     * Set the clamping state\r\n     *\r\n     * @param clamp The clamping state\r\n     */\r\n    void setClamp(Boolean clamp);\r\n\r\n    /**\r\n     * Returns the bump multiplier (<code>-bm</code>),\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The bump multiplier.\r\n     */\r\n    Float getBm();\r\n\r\n    /**\r\n     * Set the bump multiplier\r\n     *\r\n     * @param bm The bump multiplier\r\n     */\r\n    void setBm(Float bm);\r\n\r\n    /**\r\n     * Returns the IMF channel to use (<code>-imfchan</code>),\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The IMF channel to use.\r\n     */\r\n    String getImfchan();\r\n\r\n    /**\r\n     * Set the IMF channel\r\n     *\r\n     * @param imfchan The IMF channel\r\n     */\r\n    void setImfchan(String imfchan);\r\n\r\n    /**\r\n     * Returns the type of texture map (<code>-type</code>),\r\n     * or <code>null</code> if it was not specified\r\n     *\r\n     * @return The type of texture map\r\n     */\r\n    String getType();\r\n\r\n    /**\r\n     * Set the type\r\n     *\r\n     * @param type The type\r\n     */\r\n    void setType(String type);\r\n\r\n}\r\n\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/Utils.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n *\r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\npackage de.javagl.obj;\r\n\r\nimport java.io.IOException;\r\nimport java.util.Queue;\r\nimport java.util.StringTokenizer;\r\n\r\n/**\r\n * Utility methods for reading and parsing\r\n */\r\nclass Utils\r\n{\r\n    /**\r\n     * Reads a float tuple from the given StringTokenizer\r\n     *\r\n     * @param st The StringTokenizer\r\n     * @return The FloatTuple\r\n     * @throws IOException If the tuple can not be read\r\n     */\r\n    static FloatTuple readFloatTuple(StringTokenizer st)\r\n        throws IOException\r\n    {\r\n        float x = parseFloat(st.nextToken());\r\n        if (st.hasMoreTokens())\r\n        {\r\n            float y = parseFloat(st.nextToken());\r\n\r\n            if (st.hasMoreTokens())\r\n            {\r\n                float z = parseFloat(st.nextToken());\r\n\r\n                if (st.hasMoreTokens())\r\n                {\r\n                    float w = parseFloat(st.nextToken());\r\n                    return FloatTuples.create(x,y,z,w);\r\n                }\r\n                return FloatTuples.create(x,y,z);\r\n            }\r\n            return FloatTuples.create(x,y);\r\n        }\r\n        return FloatTuples.create(x);\r\n    }\r\n\r\n    /**\r\n     * Parse a float from the given string, wrapping number format\r\n     * exceptions into an IOException\r\n     *\r\n     * @param s The string\r\n     * @return The float\r\n     * @throws IOException If the string does not contain a valid float value\r\n     */\r\n    static float parseFloat(String s) throws IOException\r\n    {\r\n        try\r\n        {\r\n            return Float.parseFloat(s);\r\n        }\r\n        catch (NumberFormatException e)\r\n        {\r\n            throw new IOException(e);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Returns whether the given string can be parsed into a float value\r\n     *\r\n     * @param s The string\r\n     * @return Whether the string is a float value. If the given string is\r\n     * <code>null</code>, then <code>false</code> is returned.\r\n     */\r\n    private static boolean isFloat(String s)\r\n    {\r\n        if (s == null)\r\n        {\r\n            return false;\r\n        }\r\n        try\r\n        {\r\n            Float.parseFloat(s);\r\n            return true;\r\n        }\r\n        catch (NumberFormatException e)\r\n        {\r\n            return false;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Parse up to <code>max</code> float values from the given tokens.\r\n     * If there are fewer than <code>max</code> tokens, then the\r\n     * resulting values will be <code>null</code>. The parsing will\r\n     * stop when a value is encountered that is not a float value.\r\n     *\r\n     * @param tokens The input tokens\r\n     * @param max The maximum number of tokens to process\r\n     * @return The array containing the parsed values\r\n     */\r\n    static Float[] parseFloats(Queue<String> tokens, int max)\r\n    {\r\n        Float[] result = new Float[max];\r\n        for (int i = 0; i < max; i++)\r\n        {\r\n            String token = tokens.poll();\r\n            if (Utils.isFloat(token))\r\n            {\r\n                float value = Float.parseFloat(token);\r\n                result[i] = value;\r\n            }\r\n        }\r\n        return result;\r\n    }\r\n\r\n    /**\r\n     * Parse a boolean value from the given string, converting\r\n     * <code>\"true\"<code> and <code>\"on\"<code> to <code>true</code>,\r\n     * and anything else to <code>false</code>.\r\n     *\r\n     * @param s The string\r\n     * @return The boolean value\r\n     */\r\n    static boolean parseBoolean(String s)\r\n    {\r\n        if (\"true\".equalsIgnoreCase(s))\r\n        {\r\n            return true;\r\n        }\r\n        if (\"on\".equalsIgnoreCase(s))\r\n        {\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n\r\n    /**\r\n     * Parse an int from the given string, wrapping number format\r\n     * exceptions into an IOException\r\n     *\r\n     * @param s The string\r\n     * @return The int\r\n     * @throws IOException If the string does not contain a valid int value\r\n     */\r\n    static int parseInt(String s) throws IOException\r\n    {\r\n        try\r\n        {\r\n            return Integer.parseInt(s);\r\n        }\r\n        catch (NumberFormatException e)\r\n        {\r\n            throw new IOException(e);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Creates a {@link FloatTuple} from the given RGB values, treating\r\n     * optional values as described in the MTL specification: If\r\n     * the <code>r</code> component is <code>null</code>, then\r\n     * <code>null</code> is returned. If the <code>g</code> or\r\n     * <code>b</code> component is null, then the <code>r</code>\r\n     * component will be used instead.\r\n     *\r\n     * @param r The r-component\r\n     * @param g The g-component\r\n     * @param b The b-component\r\n     * @return The {@link FloatTuple}\r\n     */\r\n    static FloatTuple createRgbTuple(Float r, Float g, Float b)\r\n    {\r\n        if (r == null)\r\n        {\r\n            return null;\r\n        }\r\n        float fr = r;\r\n        float fg = r;\r\n        float fb = r;\r\n        if (g != null)\r\n        {\r\n            fg = g;\r\n        }\r\n        if (b != null)\r\n        {\r\n            fb = b;\r\n        }\r\n        return FloatTuples.create(fr, fg, fb);\r\n    }\r\n\r\n    /**\r\n     * Creates a {@link FloatTuple} from the given UVW values, treating\r\n     * optional values as described in the MTL specification: If\r\n     * the <code>u</code> component is <code>null</code>, then\r\n     * <code>null</code> is returned. If the <code>v</code> or\r\n     * <code>w</code> component is null, then the given default value\r\n     * will be used.\r\n     *\r\n     * @param u The u-component\r\n     * @param v The v-component\r\n     * @param w The w-component\r\n     * @param defaultValue The default value for v and w\r\n     * @return The {@link FloatTuple}\r\n     */\r\n    static FloatTuple createUvwTuple(\r\n        Float u, Float v, Float w, float defaultValue)\r\n    {\r\n        if (u == null)\r\n        {\r\n            return null;\r\n        }\r\n        float fu = u;\r\n        float fv = (v == null ? defaultValue : v);\r\n        float fw = (w == null ? defaultValue : w);\r\n        return FloatTuples.create(fu, fv, fw);\r\n    }\r\n\r\n\r\n    /**\r\n     * Private constructor to prevent instantiation\r\n     */\r\n    private Utils()\r\n    {\r\n        // Private constructor to prevent instantiation\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/WritableObj.java",
    "content": "/*\r\n * www.javagl.de - Obj\r\n *\r\n * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de\r\n * \r\n * Permission is hereby granted, free of charge, to any person\r\n * obtaining a copy of this software and associated documentation\r\n * files (the \"Software\"), to deal in the Software without\r\n * restriction, including without limitation the rights to use,\r\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the\r\n * Software is furnished to do so, subject to the following\r\n * conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be\r\n * included in all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\r\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r\n * OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\npackage de.javagl.obj;\r\n\r\nimport java.util.Collection;\r\n\r\n/**\r\n * Interface for all classes that may receive the data that is read\r\n * from an OBJ file by an {@link ObjReader}\r\n */\r\npublic interface WritableObj\r\n{\r\n    /**\r\n     * Add the given vertex\r\n     * \r\n     * @param vertex The vertex to add.\r\n     * @throws NullPointerException If the vertex is <code>null</code>\r\n     */\r\n    void addVertex(FloatTuple vertex);\r\n\r\n    /**\r\n     * Add the given vertex \r\n     * \r\n     * @param x The x-coordinate\r\n     * @param y The y-coordinate\r\n     * @param z The z-coordinate\r\n     */\r\n    void addVertex(float x, float y, float z);\r\n\r\n    /**\r\n     * Add the given texture coordinate\r\n     * \r\n     * @param texCoord The texture coordinate to add.\r\n     * @throws NullPointerException If the  texture coordinate is \r\n     * <code>null</code>\r\n     */\r\n    void addTexCoord(FloatTuple texCoord);\r\n\r\n    /**\r\n     * Add the given texture coordinate \r\n     * \r\n     * @param x The x-coordinate\r\n     */\r\n    void addTexCoord(float x);\r\n    \r\n    /**\r\n     * Add the given texture coordinate \r\n     * \r\n     * @param x The x-coordinate\r\n     * @param y The y-coordinate\r\n     */\r\n    void addTexCoord(float x, float y);\r\n    \r\n    /**\r\n     * Add the given texture coordinate \r\n     * \r\n     * @param x The x-coordinate\r\n     * @param y The y-coordinate\r\n     * @param z The z-coordinate\r\n     */\r\n    void addTexCoord(float x, float y, float z);\r\n    \r\n    /**\r\n     * Add the given normal\r\n     * \r\n     * @param normal The normal to add.\r\n     * @throws NullPointerException If the normal is <code>null</code>\r\n     */\r\n    void addNormal(FloatTuple normal);\r\n\r\n    /**\r\n     * Add the given normal \r\n     * \r\n     * @param x The x-coordinate\r\n     * @param y The y-coordinate\r\n     * @param z The z-coordinate\r\n     */\r\n    void addNormal(float x, float y, float z);\r\n    \r\n    /**\r\n     * Set the groups with the given names to be active right now. Faces that \r\n     * are added subsequently will be added to all active groups, creating\r\n     * these groups if necessary. If the given collection is <code>null</code>,\r\n     * then this call will have no effect. If the given collection is empty,\r\n     * then the default group (named \"default\") will be activated.\r\n     * \r\n     * @param groupNames The group names\r\n     * @throws NullPointerException If the given collection contains \r\n     * <code>null</code> elements\r\n     */\r\n    void setActiveGroupNames(Collection<? extends String> groupNames);\r\n    \r\n    /**\r\n     * Set the material group with the given names to be active right now \r\n     * Faces that are added subsequently will be added to the active \r\n     * material group, creating this material group if necessary. If\r\n     * the given name is <code>null</code>, then this call will have no\r\n     * effect.\r\n     * \r\n     * @param materialGroupName The material group name\r\n     */\r\n    void setActiveMaterialGroupName(String materialGroupName);\r\n   \r\n    /**\r\n     * Add the given face. \r\n     * The indices in the given face are absolute (non-negative) and \r\n     * <b>0</b>-based. \r\n     * The implementation is free to store a <b>reference</b> to the given \r\n     * face.  \r\n     * \r\n     * @param face The face to add.\r\n     * @throws NullPointerException If the face is <code>null</code>\r\n     */\r\n    void addFace(ObjFace face);\r\n    \r\n    /**\r\n     * Add the specified face with the given vertex indices, but without \r\n     * texture- or normal indices.\r\n     * The given indices are absolute (non-negative) and <b>0</b>-based. \r\n     * The implementation is free to store a <b>reference</b> to the given \r\n     * array. So the array should not be modified after this method has \r\n     * been called. \r\n     * \r\n     * @param v The vertex indices\r\n     * @throws IllegalArgumentException If one of the given indices is\r\n     * negative or not smaller than the number of vertices that have been\r\n     * added until now.\r\n     */\r\n    void addFace(int ... v);\r\n\r\n    /**\r\n     * Add the specified face with the given vertex and texture coordinate\r\n     * indices, but without normal indices.\r\n     * The given indices are absolute (non-negative) and <b>0</b>-based. \r\n     * The implementation is free to store a <b>reference</b> to the given \r\n     * array. So the array should not be modified after this method has \r\n     * been called. \r\n     * \r\n     * @param v The vertex- and texture coordinate indices\r\n     * @throws IllegalArgumentException If one of the given indices is\r\n     * negative or not smaller than the number of vertices or texture \r\n     * coordinates that have been added until now.\r\n     */\r\n    void addFaceWithTexCoords(int ... v);\r\n\r\n    /**\r\n     * Add the specified face with the given vertex and normal indices, \r\n     * but without texture coordinate indices.\r\n     * The given indices are absolute (non-negative) and <b>0</b>-based. \r\n     * The implementation is free to store a <b>reference</b> to the given \r\n     * array. So the array should not be modified after this method has \r\n     * been called. \r\n     * \r\n     * @param v The vertex- and normal indices\r\n     * @throws IllegalArgumentException If one of the given indices is\r\n     * negative or not smaller than the number of vertices or normals \r\n     * that have been added until now.\r\n     */\r\n    void addFaceWithNormals(int ... v);\r\n\r\n    /**\r\n     * Add the specified face with the given vertex, texture coordinate\r\n     * and normal indices.\r\n     * The given indices are absolute (non-negative) and <b>0</b>-based. \r\n     * The implementation is free to store a <b>reference</b> to the given \r\n     * array. So the array should not be modified after this method has \r\n     * been called. \r\n     * \r\n     * @param v The vertex- texture coordinate and normal indices\r\n     * @throws IllegalArgumentException If one of the given indices is\r\n     * negative or not smaller than the number of vertices, texture\r\n     * coordinates or normals that have been added until now.\r\n     */\r\n    void addFaceWithAll(int ... v);\r\n    \r\n    /**\r\n     * Add the specified face. \r\n     * The given indices are absolute (non-negative) and <b>0</b>-based. \r\n     * The implementation is free to store a <b>reference</b> to the given \r\n     * arrays. So the arrays should not be modified after this method has \r\n     * been called. \r\n     * \r\n     * @param v The vertex indices\r\n     * @param vt The texture coordinate indices. May be <code>null</code>.\r\n     * @param vn The normal indices. May be <code>null</code>\r\n     * @throws NullPointerException If the vertex indices array is \r\n     * <code>null</code>\r\n     * @throws IllegalArgumentException If one of the given indices is\r\n     * negative or not smaller than the number of corresponding vertices,\r\n     * texture coordinates or normals that have been added until now,\r\n     * respectively.\r\n     * @throws IllegalArgumentException If the given (non-null) arrays \r\n     * have different lengths\r\n     */\r\n    void addFace(int[] v, int[] vt, int[] vn);\r\n    \r\n    /**\r\n     * Set the given MTL file names. A copy of the given\r\n     * collection will be stored.\r\n     * \r\n     * @param mtlFileNames The names of the MTL file\r\n     */\r\n    void setMtlFileNames(Collection<? extends String> mtlFileNames);\r\n    \r\n}\r\n"
  },
  {
    "path": "src/main/java/de/javagl/obj/package-info.java",
    "content": "/**\r\n * Classes for reading and writing Wavefront OBJ and MTL files.\r\n * <p>\r\n * {@link de.javagl.obj.Obj} objects may be created with\r\n * {@link de.javagl.obj.Objs#create()} or by reading an OBJ file with\r\n * {@link de.javagl.obj.ObjReader#read(java.io.InputStream)}.\r\n * {@link de.javagl.obj.Obj} objects may be written as OBJ files with\r\n * {@link de.javagl.obj.ObjWriter#write(ReadableObj, java.io.OutputStream)}.\r\n * <br>\r\n */\r\npackage de.javagl.obj;\r\n\r\n"
  },
  {
    "path": "src/test/java/de/javagl/obj/TestMtlReader.java",
    "content": "package de.javagl.obj;\r\n\r\nimport static org.junit.Assert.assertEquals;\r\n\r\nimport java.io.IOException;\r\nimport java.util.Arrays;\r\nimport java.util.LinkedList;\r\nimport java.util.List;\r\n\r\nimport org.junit.Test;\r\n\r\n\r\n@SuppressWarnings(\"javadoc\")\r\npublic class TestMtlReader\r\n{\r\n    /**\r\n     * An epsilon for floating-point comparisons\r\n     */\r\n    private static final float FLOAT_ERROR = 1e-6f;\r\n\r\n    @Test\r\n    public void readMtl()\r\n        throws IOException\r\n    {\r\n        List<Mtl> mtls = MtlReader.read(getClass().getResourceAsStream(\r\n            \"/twoMaterialsA.mtl\"));\r\n\r\n        assertEquals(2, mtls.size());\r\n        assertEquals(new DefaultFloatTuple(1,1,1), mtls.get(1).getKa());\r\n        assertEquals(new DefaultFloatTuple(1,1,1), mtls.get(1).getKd());\r\n        assertEquals(new DefaultFloatTuple(1,1,1), mtls.get(1).getKs());\r\n        assertEquals(0, mtls.get(1).getNs(), FLOAT_ERROR);\r\n        assertEquals(0.5f, mtls.get(1).getD(), FLOAT_ERROR);\r\n        assertEquals(\"texture.png\", mtls.get(1).getMapKd());\r\n    }\r\n\r\n    @Test\r\n    public void readMtlWithWhitespace()\r\n        throws IOException\r\n    {\r\n        List<Mtl> mtls = MtlReader.read(getClass().getResourceAsStream(\r\n            \"/mtlWithWhitespace.mtl\"));\r\n\r\n        assertEquals(1, mtls.size());\r\n\r\n        Mtl mtl = mtls.get(0);\r\n        assertEquals(\"material0\", mtl.getName());\r\n        assertEquals(new DefaultFloatTuple(1,0,0), mtl.getKa());\r\n        assertEquals(new DefaultFloatTuple(1,1,0), mtl.getKd());\r\n        assertEquals(new DefaultFloatTuple(1,1,1), mtl.getKs());\r\n        assertEquals(500, mtl.getNs(), FLOAT_ERROR);\r\n        assertEquals(1.0f, mtl.getD(), FLOAT_ERROR);\r\n        assertEquals(\"texture.png\", mtl.getMapKd());\r\n    }\r\n\r\n    @Test\r\n    public void readMtlWithBrokenLines()\r\n        throws IOException\r\n    {\r\n        List<Mtl> mtls = MtlReader.read(getClass().getResourceAsStream(\r\n            \"/mtlWithBrokenLines.mtl\"));\r\n\r\n        assertEquals(1, mtls.size());\r\n\r\n        Mtl mtl = mtls.get(0);\r\n        assertEquals(\"material0\", mtl.getName());\r\n        assertEquals(new DefaultFloatTuple(1,0,0), mtl.getKa());\r\n        assertEquals(new DefaultFloatTuple(1,1,0), mtl.getKd());\r\n        assertEquals(new DefaultFloatTuple(1,1,1), mtl.getKs());\r\n        assertEquals(500, mtl.getNs(), FLOAT_ERROR);\r\n        assertEquals(123.0f, mtl.getD(), FLOAT_ERROR);\r\n        assertEquals(\"texture.png\", mtl.getMapKd());\r\n    }\r\n\r\n    @Test\r\n    public void readTextureOptionsWithAllOptions()\r\n        throws IOException\r\n    {\r\n\r\n        String[] tokens = new String[]\r\n        {\r\n            \"-blendu\", \"off\",\r\n            \"-blendv\", \"off\",\r\n            \"-boost\", \"0.4\",\r\n            \"-cc\", \"on\",\r\n            \"-mm\", \"0.2\", \"0.33\",\r\n            \"-o\", \"0.01\", \"0.02\", \"0.03\",\r\n            \"-s\", \"0.04\", \"0.05\", \"0.06\",\r\n            \"-t\", \"0.07\", \"0.08\", \"0.09\",\r\n            \"-texres\", \".44\",\r\n            \"-clamp\", \"on\",\r\n            \"-bm\", \"3.45\",\r\n            \"-imfchan\", \"g\",\r\n            \"-type\", \"sphere\",\r\n            \"texture.png\"\r\n        };\r\n\r\n        TextureOptions options = MtlReader.readTextureOptions(\r\n                new LinkedList<>(Arrays.asList(tokens)));\r\n\r\n        assertEquals(Boolean.FALSE, options.isBlendu());\r\n        assertEquals(Boolean.FALSE, options.isBlendv());\r\n\r\n        assertEquals(0.4f, options.getBoost(), FLOAT_ERROR);\r\n\r\n        assertEquals(Boolean.TRUE, options.isCc());\r\n\r\n        assertEquals(0.2f, options.getMm().get(0), FLOAT_ERROR);\r\n        assertEquals(0.33f, options.getMm().get(1), FLOAT_ERROR);\r\n\r\n        assertEquals(0.01f, options.getO().getX(), FLOAT_ERROR);\r\n        assertEquals(0.02f, options.getO().getY(), FLOAT_ERROR);\r\n        assertEquals(0.03f, options.getO().getZ(), FLOAT_ERROR);\r\n\r\n        assertEquals(0.04f, options.getS().getX(), FLOAT_ERROR);\r\n        assertEquals(0.05f, options.getS().getY(), FLOAT_ERROR);\r\n        assertEquals(0.06f, options.getS().getZ(), FLOAT_ERROR);\r\n\r\n        assertEquals(0.07f, options.getT().getX(), FLOAT_ERROR);\r\n        assertEquals(0.08f, options.getT().getY(), FLOAT_ERROR);\r\n        assertEquals(0.09f, options.getT().getZ(), FLOAT_ERROR);\r\n\r\n        assertEquals(0.44f, options.getTexres(), FLOAT_ERROR);\r\n\r\n        assertEquals(Boolean.TRUE, options.isClamp());\r\n\r\n        assertEquals(3.45f, options.getBm(), FLOAT_ERROR);\r\n\r\n        assertEquals(\"g\", options.getImfchan());\r\n\r\n        assertEquals(\"sphere\", options.getType());\r\n    }\r\n\r\n\r\n    @Test\r\n    public void readTextureOptionsWithSingleOriginOffsetValue()\r\n        throws IOException\r\n    {\r\n        String[] tokens = new String[]\r\n        {\r\n            \"-o\", \"0.1\", \"texture.png\"\r\n        };\r\n        TextureOptions options = MtlReader.readTextureOptions(\r\n                new LinkedList<>(Arrays.asList(tokens)));\r\n\r\n        assertEquals(0.1f, options.getO().getX(), FLOAT_ERROR);\r\n        assertEquals(0.0f, options.getO().getY(), FLOAT_ERROR);\r\n        assertEquals(0.0f, options.getO().getZ(), FLOAT_ERROR);\r\n    }\r\n\r\n    @Test\r\n    public void readTextureOptionsWithDoubleOriginOffsetValue()\r\n        throws Exception\r\n    {\r\n        String[] tokens = new String[]\r\n        {\r\n            \"-o\", \"0.1\", \"0.2\", \"texture.png\"\r\n        };\r\n        TextureOptions options = MtlReader.readTextureOptions(\r\n                new LinkedList<>(Arrays.asList(tokens)));\r\n\r\n        assertEquals(0.1f, options.getO().getX(), FLOAT_ERROR);\r\n        assertEquals(0.2f, options.getO().getY(), FLOAT_ERROR);\r\n        assertEquals(0.0f, options.getO().getZ(), FLOAT_ERROR);\r\n    }\r\n\r\n    @Test\r\n    public void readMtlWithPbrProperties()\r\n            throws IOException {\r\n        List<Mtl> mtls = MtlReader.read(getClass().getResourceAsStream(\r\n            \"/mtlWithPbrProperties.mtl\"));\r\n\r\n        assertEquals(1, mtls.size());\r\n\r\n        Mtl mtl = mtls.get(0);\r\n        assertEquals(\"Material.001\", mtl.getName());\r\n        assertEquals(0.5, mtl.getPr(), FLOAT_ERROR);\r\n        assertEquals(0.7, mtl.getPm(), FLOAT_ERROR);\r\n        assertEquals(0.1, mtl.getPs(), FLOAT_ERROR);\r\n        assertEquals(0.4, mtl.getPc(), FLOAT_ERROR);\r\n        assertEquals(0.03, mtl.getPcr(), FLOAT_ERROR);\r\n        assertEquals(0.001, mtl.getAniso(), FLOAT_ERROR);\r\n        assertEquals(0.01, mtl.getAnisor(), FLOAT_ERROR);\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/test/java/de/javagl/obj/TestMtlWriter.java",
    "content": "package de.javagl.obj;\r\n\r\nimport static org.junit.Assert.assertEquals;\r\n\r\nimport java.io.ByteArrayInputStream;\r\nimport java.io.ByteArrayOutputStream;\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.util.List;\r\nimport java.util.Scanner;\r\n\r\nimport org.junit.Test;\r\n\r\n\r\n@SuppressWarnings(\"javadoc\")\r\npublic class TestMtlWriter\r\n{\r\n    @Test\r\n    public void writeMtl()\r\n        throws IOException\r\n    {\r\n        String inputString = readResourceAsString(\r\n            \"/twoMaterialsA.mtl\");\r\n        List<Mtl> mtls = MtlReader.read(\r\n            new ByteArrayInputStream(inputString.getBytes()));\r\n\r\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\r\n        MtlWriter.write(mtls, baos);\r\n        String outputString = new String(baos.toByteArray());\r\n\r\n        //System.out.println(outputString);\r\n        assertEquals(inputString, outputString);\r\n    }\r\n\r\n    @Test\r\n    public void writeComplexMtl()\r\n        throws IOException\r\n    {\r\n        String inputString = readResourceAsString(\r\n            \"/complexMaterial.mtl\");\r\n        List<Mtl> mtls = MtlReader.read(\r\n            new ByteArrayInputStream(inputString.getBytes()));\r\n\r\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\r\n        MtlWriter.write(mtls, baos);\r\n        String outputString = new String(baos.toByteArray());\r\n\r\n        //System.out.println(outputString);\r\n        assertEquals(inputString, outputString);\r\n    }\r\n\r\n    @Test\r\n    public void writePbrMtl()\r\n        throws IOException\r\n    {\r\n        String inputString = readResourceAsString(\r\n            \"/pbrMaterial.mtl\");\r\n        List<Mtl> mtls = MtlReader.read(\r\n            new ByteArrayInputStream(inputString.getBytes()));\r\n\r\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\r\n        MtlWriter.write(mtls, baos);\r\n        String outputString = new String(baos.toByteArray());\r\n\r\n        System.out.println(outputString);\r\n        assertEquals(inputString, outputString);\r\n    }\r\n\r\n\r\n    private static String readResourceAsString(String name)\r\n    {\r\n        InputStream inputStream =\r\n            TestObjWriter.class.getResourceAsStream(name);\r\n        String string = readAsString(inputStream);\r\n        string = string.replaceAll(\"\\r\\n\", \"\\n\");\r\n        return string;\r\n    }\r\n\r\n    private static String readAsString(InputStream inputStream)\r\n    {\r\n        try (Scanner scanner = new Scanner(inputStream))\r\n        {\r\n            scanner.useDelimiter(\"\\\\A\");\r\n            String string = scanner.next();\r\n            return string;\r\n        }\r\n    }\r\n\r\n\r\n}"
  },
  {
    "path": "src/test/java/de/javagl/obj/TestObjData.java",
    "content": "package de.javagl.obj;\r\n\r\nimport static org.junit.Assert.*;\r\n\r\nimport java.io.IOException;\r\n\r\nimport org.junit.Test;\r\n\r\n@SuppressWarnings(\"javadoc\")\r\npublic class TestObjData\r\n{\r\n    @Test\r\n    public void testGetVertices() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/squareTextured.obj\"));\r\n        obj = ObjUtils.convertToRenderable(obj);\r\n        \r\n        assertEquals(2, obj.getNumFaces());\r\n        assertEquals(4, obj.getNumVertices());\r\n        assertEquals(4, obj.getNumTexCoords());\r\n        assertEquals(0, obj.getNumNormals());\r\n        assertEquals(1, obj.getNumGroups()); // default\r\n        assertEquals(0, obj.getNumMaterialGroups());\r\n        \r\n        float[] actualVertices = ObjData.getVerticesArray(obj);\r\n        float[] expectedVertices = \r\n        {\r\n            0.0f, 0.0f, 0.0f,\r\n            4.0f, 0.0f, 0.0f,\r\n            4.0f, 4.0f, 0.0f,\r\n            0.0f, 4.0f, 0.0f\r\n        };\r\n        assertArrayEquals(expectedVertices, actualVertices, 0.0f);\r\n    }\r\n    \r\n    @Test\r\n    public void testGetTexCoords() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/squareTextured.obj\"));\r\n        obj = ObjUtils.convertToRenderable(obj);\r\n        \r\n        assertEquals(2, obj.getNumFaces());\r\n        assertEquals(4, obj.getNumVertices());\r\n        assertEquals(4, obj.getNumTexCoords());\r\n        assertEquals(0, obj.getNumNormals());\r\n        assertEquals(1, obj.getNumGroups()); // default\r\n        assertEquals(0, obj.getNumMaterialGroups());\r\n        \r\n        float[] actualTexCoords = ObjData.getTexCoordsArray(obj, 2);\r\n        float[] expectedTexCoords = \r\n        {\r\n            0.0f, 0.0f,\r\n            1.0f, 0.0f,\r\n            1.0f, 1.0f,\r\n            0.0f, 1.0f\r\n        };\r\n        assertArrayEquals(expectedTexCoords, actualTexCoords, 0.0f);\r\n    }\r\n    \r\n    @Test\r\n    public void testGetTexCoordsFlipped() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/squareTextured.obj\"));\r\n        \r\n        obj = ObjUtils.convertToRenderable(obj);\r\n        \r\n        assertEquals(2, obj.getNumFaces());\r\n        assertEquals(4, obj.getNumVertices());\r\n        assertEquals(4, obj.getNumTexCoords());\r\n        assertEquals(0, obj.getNumNormals());\r\n        assertEquals(1, obj.getNumGroups()); // default\r\n        assertEquals(0, obj.getNumMaterialGroups());\r\n        \r\n        float[] actualTexCoords = ObjData.getTexCoordsArray(obj, 2, true);\r\n        float[] expectedTexCoords = \r\n        {\r\n            0.0f, 1.0f,\r\n            1.0f, 1.0f,\r\n            1.0f, 0.0f,\r\n            0.0f, 0.0f\r\n        };\r\n        assertArrayEquals(expectedTexCoords, actualTexCoords, 0.0f);\r\n    }\r\n    \r\n\r\n}\r\n"
  },
  {
    "path": "src/test/java/de/javagl/obj/TestObjReader.java",
    "content": "package de.javagl.obj;\r\n\r\nimport static org.junit.Assert.assertEquals;\r\n\r\nimport java.io.IOException;\r\nimport java.util.List;\r\n\r\nimport org.junit.Test;\r\n\r\n\r\n@SuppressWarnings(\"javadoc\")\r\npublic class TestObjReader\r\n{\r\n    @Test\r\n    public void readSquare() \r\n        throws IOException\r\n    {\r\n         Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/square.obj\"));\r\n        \r\n        assertEquals(1, obj.getNumFaces());\r\n        assertEquals(4, obj.getNumVertices());\r\n        assertEquals(0, obj.getNumTexCoords());\r\n        assertEquals(0, obj.getNumNormals());\r\n        assertEquals(1, obj.getNumGroups());\r\n        assertEquals(0, obj.getNumMaterialGroups());\r\n    }\r\n    \r\n    @Test\r\n    public void readSquareAndTriangle() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/squareAndTriangle.obj\"));\r\n        \r\n        assertEquals(2, obj.getNumFaces());\r\n        assertEquals(5, obj.getNumVertices());\r\n        assertEquals(0, obj.getNumTexCoords());\r\n        assertEquals(0, obj.getNumNormals());\r\n        assertEquals(1, obj.getNumGroups());\r\n        assertEquals(0, obj.getNumMaterialGroups());\r\n    }\r\n\r\n    @Test\r\n    public void readSquareAndTriangleInTwoGroups() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/squareAndTriangleInTwoGroups.obj\"));\r\n        \r\n        assertEquals(2, obj.getNumFaces());\r\n        assertEquals(5, obj.getNumVertices());\r\n        assertEquals(0, obj.getNumTexCoords());\r\n        assertEquals(0, obj.getNumNormals());\r\n        assertEquals(3, obj.getNumGroups()); // 2 + default\r\n        assertEquals(0, obj.getNumMaterialGroups());\r\n    }\r\n    \r\n    @Test\r\n    public void readFourTrianglesInMixedGroups() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/fourTrianglesInMixedGroups.obj\"));\r\n        \r\n        assertEquals(4, obj.getNumFaces());\r\n        assertEquals(6, obj.getNumVertices());\r\n        assertEquals(0, obj.getNumTexCoords());\r\n        assertEquals(0, obj.getNumNormals());\r\n        assertEquals(3, obj.getNumGroups()); // 2 + default\r\n        assertEquals(4, obj.getNumMaterialGroups());\r\n        assertEquals(\"twoMaterialsA.mtl\", obj.getMtlFileNames().get(0));\r\n    }\r\n    \r\n    @Test\r\n    public void readTwoTrianglesOneInDefaultGroup() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/twoTrianglesOneInDefaultGroup.obj\"));\r\n        \r\n        assertEquals(2, obj.getNumFaces());\r\n        assertEquals(4, obj.getNumVertices());\r\n        assertEquals(0, obj.getNumTexCoords());\r\n        assertEquals(0, obj.getNumNormals());\r\n        assertEquals(2, obj.getNumGroups()); // 1 + default\r\n        assertEquals(0, obj.getNumMaterialGroups());\r\n    }\r\n    \r\n    \r\n    @Test\r\n    public void readTwoTrianglesSharedInThreeGroups() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/twoTrianglesSharedInThreeGroups.obj\"));\r\n        \r\n        assertEquals(2, obj.getNumFaces());\r\n        assertEquals(4, obj.getNumVertices());\r\n        assertEquals(0, obj.getNumTexCoords());\r\n        assertEquals(0, obj.getNumNormals());\r\n        assertEquals(4, obj.getNumGroups()); // 3 + default\r\n        assertEquals(0, obj.getNumMaterialGroups());\r\n    }\r\n    \r\n    \r\n    @Test\r\n    public void readSquareAndTriangleWithRelativeIndices() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/squareAndTriangleWithRelativeIndices.obj\"));\r\n        \r\n        assertEquals(2, obj.getNumFaces());\r\n        assertEquals(5, obj.getNumVertices());\r\n        assertEquals(0, obj.getNumTexCoords());\r\n        assertEquals(0, obj.getNumNormals());\r\n        assertEquals(1, obj.getNumGroups()); // default\r\n        assertEquals(0, obj.getNumMaterialGroups());\r\n    }\r\n    \r\n    \r\n    @Test\r\n    public void readMtls() \r\n        throws IOException\r\n    {\r\n        List<Mtl> mtls = MtlReader.read(getClass().getResourceAsStream(\r\n            \"/twoMaterialsA.mtl\"));\r\n        \r\n        assertEquals(2, mtls.size());\r\n        Mtl mtl0 = mtls.get(0);\r\n        assertEquals(\"material0\", mtl0.getName());\r\n        assertEquals(new DefaultFloatTuple(1,0,0), mtl0.getKa());\r\n        assertEquals(new DefaultFloatTuple(1,0,0), mtl0.getKd());\r\n        assertEquals(new DefaultFloatTuple(1,1,1), mtl0.getKs());\r\n        assertEquals(500, mtl0.getNs(), 1e-6);\r\n\r\n    }\r\n    \r\n}"
  },
  {
    "path": "src/test/java/de/javagl/obj/TestObjSplitting.java",
    "content": "package de.javagl.obj;\r\n\r\nimport static org.junit.Assert.*;\r\n\r\nimport java.io.IOException;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\nimport org.junit.Test;\r\n\r\n@SuppressWarnings(\"javadoc\")\r\npublic class TestObjSplitting\r\n{\r\n    @Test\r\n    public void testSplitByGroup() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/squareAndTriangleInTwoGroups.obj\"));\r\n        \r\n        Map<String, Obj> objs = ObjSplitting.splitByGroups(obj);\r\n        assertEquals(2, objs.size());\r\n    }\r\n\r\n    @Test\r\n    public void testSplitByGroupOnlyDefault() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/square.obj\"));\r\n        \r\n        Map<String, Obj> objs = ObjSplitting.splitByGroups(obj);\r\n        assertEquals(1, objs.size());\r\n    }\r\n    \r\n    @Test\r\n    public void testSplitByMaterialGroup() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/fourTrianglesInMixedGroups.obj\"));\r\n        \r\n        Map<String, Obj> objs = ObjSplitting.splitByMaterialGroups(obj);\r\n        assertEquals(4, objs.size());\r\n    }\r\n    \r\n    @Test\r\n    public void testSplitByMaterialGroupWithoutMaterial() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/square.obj\"));\r\n        \r\n        Map<String, Obj> objs = ObjSplitting.splitByMaterialGroups(obj);\r\n        \r\n        // There are no material groups in the \"square.obj\" \r\n        assertEquals(0, objs.size());\r\n    }\r\n   \r\n    @Test\r\n    public void testSplitByMaterialGroupForPartialGroups() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/fourTrianglesPartiallyInMaterialGroups.obj\"));\r\n        \r\n        Map<String, Obj> objs = ObjSplitting.splitByMaterialGroups(obj);\r\n\r\n        // Only two of the triangles are in material groups \r\n        assertEquals(2, objs.size());\r\n    }\r\n    \r\n    @Test\r\n    public void testSplitByNumVertices() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/fourTrianglesInMixedGroups.obj\"));\r\n        \r\n        int maxNumVertices = 4;\r\n        List<Obj> objs = \r\n            ObjSplitting.splitByMaxNumVertices(obj, maxNumVertices);\r\n        \r\n        assertEquals(2, objs.size());\r\n        for (ReadableObj r : objs)\r\n        {\r\n            assertTrue(r.getNumVertices() <= maxNumVertices);\r\n        }\r\n    }\r\n    \r\n    @Test\r\n    public void testSplitByNumVerticesForInvalidInput() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/square.obj\"));\r\n        \r\n        int maxNumVertices = 3;\r\n        List<Obj> objs = \r\n            ObjSplitting.splitByMaxNumVertices(obj, maxNumVertices);\r\n        assertEquals(1, objs.size());\r\n        for (ReadableObj r : objs)\r\n        {\r\n            // This is larger than the maximum, but an OBJ containing\r\n            // a square cannot be split otherwise:\r\n            assertEquals(4, r.getNumVertices());\r\n        }\r\n    }\r\n    \r\n    \r\n   \r\n}"
  },
  {
    "path": "src/test/java/de/javagl/obj/TestObjUtilsAdd.java",
    "content": "package de.javagl.obj;\r\n\r\nimport static org.junit.Assert.assertEquals;\r\n\r\nimport java.io.IOException;\r\n\r\nimport org.junit.Test;\r\n\r\n@SuppressWarnings(\"javadoc\")\r\npublic class TestObjUtilsAdd\r\n{\r\n    @Test\r\n    public void testAdd() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/twoTrianglesOneInDefaultGroup.obj\"));\r\n        \r\n        assertEquals(2, obj.getNumFaces());\r\n        assertEquals(4, obj.getNumVertices());\r\n        assertEquals(0, obj.getNumTexCoords());\r\n        assertEquals(0, obj.getNumNormals());\r\n        assertEquals(2, obj.getNumGroups()); // 1 + default\r\n        assertEquals(0, obj.getNumMaterialGroups());\r\n        \r\n        Obj combinedObj = Objs.create();\r\n        ObjUtils.add(obj, combinedObj);\r\n        ObjUtils.add(obj, combinedObj);\r\n        \r\n        assertEquals(4, combinedObj.getNumFaces());\r\n        assertEquals(8, combinedObj.getNumVertices());\r\n        assertEquals(0, combinedObj.getNumTexCoords());\r\n        assertEquals(0, combinedObj.getNumNormals());\r\n        assertEquals(2, combinedObj.getNumGroups()); // Still only two groups!\r\n        assertEquals(0, combinedObj.getNumMaterialGroups());\r\n    }\r\n    \r\n    @Test\r\n    public void testAddDifferent() \r\n        throws IOException\r\n    {\r\n        Obj obj0 = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/twoTrianglesOneInDefaultGroup.obj\"));\r\n        \r\n        Obj obj1 = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/squareAndTriangleInTwoGroups.obj\"));\r\n        \r\n        assertEquals(2, obj0.getNumFaces());\r\n        assertEquals(4, obj0.getNumVertices());\r\n        assertEquals(0, obj0.getNumTexCoords());\r\n        assertEquals(0, obj0.getNumNormals());\r\n        assertEquals(2, obj0.getNumGroups()); // 1 + default\r\n        assertEquals(0, obj0.getNumMaterialGroups());\r\n\r\n        assertEquals(2, obj1.getNumFaces());\r\n        assertEquals(5, obj1.getNumVertices());\r\n        assertEquals(0, obj1.getNumTexCoords());\r\n        assertEquals(0, obj1.getNumNormals());\r\n        assertEquals(3, obj1.getNumGroups()); // 2 + default \r\n        assertEquals(0, obj1.getNumMaterialGroups());\r\n        \r\n        Obj combinedObj = Objs.create();\r\n        ObjUtils.add(obj0, combinedObj);\r\n        ObjUtils.add(obj1, combinedObj);\r\n        \r\n        assertEquals(4, combinedObj.getNumFaces());\r\n        assertEquals(9, combinedObj.getNumVertices());\r\n        assertEquals(0, combinedObj.getNumTexCoords());\r\n        assertEquals(0, combinedObj.getNumNormals());\r\n        // The default group, \"group0\" and \"group1\"\r\n        assertEquals(3, combinedObj.getNumGroups()); \r\n        assertEquals(0, combinedObj.getNumMaterialGroups());\r\n    }\r\n    \r\n    \r\n    @Test\r\n    public void testAddWithMaterialGroups() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/fourTrianglesInMixedGroups.obj\"));\r\n        \r\n        assertEquals(4, obj.getNumFaces());\r\n        assertEquals(6, obj.getNumVertices());\r\n        assertEquals(0, obj.getNumTexCoords());\r\n        assertEquals(0, obj.getNumNormals());\r\n        assertEquals(3, obj.getNumGroups()); // 2 + (empty) default\r\n        assertEquals(4, obj.getNumMaterialGroups()); // 4 groups\r\n        \r\n        Obj combinedObj = Objs.create();\r\n        ObjUtils.add(obj, combinedObj);\r\n        ObjUtils.add(obj, combinedObj);\r\n        \r\n        assertEquals(8, combinedObj.getNumFaces());\r\n        assertEquals(12, combinedObj.getNumVertices());\r\n        assertEquals(0, combinedObj.getNumTexCoords());\r\n        assertEquals(0, combinedObj.getNumNormals());\r\n        assertEquals(3, combinedObj.getNumGroups());\r\n        assertEquals(4, combinedObj.getNumMaterialGroups()); // Still 4\r\n    }\r\n   \r\n    \r\n}"
  },
  {
    "path": "src/test/java/de/javagl/obj/TestObjUtilsGroupToObj.java",
    "content": "package de.javagl.obj;\r\n\r\nimport static org.junit.Assert.assertEquals;\r\n\r\nimport java.io.IOException;\r\n\r\nimport org.junit.Test;\r\n\r\n\r\n@SuppressWarnings(\"javadoc\")\r\npublic class TestObjUtilsGroupToObj\r\n{\r\n    @Test\r\n    public void testGroupToObj() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/squareAndTriangleInTwoGroups.obj\"));\r\n        \r\n        assertEquals(2, obj.getNumFaces());\r\n        assertEquals(5, obj.getNumVertices());\r\n        assertEquals(0, obj.getNumTexCoords());\r\n        assertEquals(0, obj.getNumNormals());\r\n        assertEquals(3, obj.getNumGroups()); // 2 + default\r\n        assertEquals(0, obj.getNumMaterialGroups());\r\n\r\n        Obj groupObj0 = ObjUtils.groupToObj(obj, obj.getGroup(\"group0\"), null);\r\n\r\n        Obj groupObj1 = ObjUtils.groupToObj(obj, obj.getGroup(\"group1\"), null);\r\n\r\n        assertEquals(1, groupObj0.getNumFaces());\r\n        assertEquals(4, groupObj0.getNumVertices());\r\n        assertEquals(0, groupObj0.getNumTexCoords());\r\n        assertEquals(0, groupObj0.getNumNormals());\r\n        assertEquals(2, groupObj0.getNumGroups());\r\n        assertEquals(0, groupObj0.getNumMaterialGroups());\r\n        \r\n        assertEquals(1, groupObj1.getNumFaces());\r\n        assertEquals(3, groupObj1.getNumVertices());\r\n        assertEquals(0, groupObj1.getNumTexCoords());\r\n        assertEquals(0, groupObj1.getNumNormals());\r\n        assertEquals(2, groupObj1.getNumGroups());\r\n        assertEquals(0, groupObj1.getNumMaterialGroups());\r\n    }\r\n    \r\n    \r\n}"
  },
  {
    "path": "src/test/java/de/javagl/obj/TestObjUtilsMakeTexCoordsUnique.java",
    "content": "package de.javagl.obj;\r\n\r\nimport static org.junit.Assert.assertEquals;\r\n\r\nimport java.io.IOException;\r\n\r\nimport org.junit.Test;\r\n\r\n\r\n@SuppressWarnings(\"javadoc\")\r\npublic class TestObjUtilsMakeTexCoordsUnique\r\n{\r\n    @Test\r\n    public void testMakeTexCoordsUnique() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/twoTrianglesWithAmbiguousTexCoords.obj\"));\r\n        \r\n        Obj unique = ObjUtils.makeTexCoordsUnique(obj);\r\n        \r\n        assertEquals(2, unique.getNumFaces());\r\n        assertEquals(5, unique.getNumVertices());\r\n        assertEquals(4, unique.getNumTexCoords());\r\n        assertEquals(0, unique.getNumNormals());\r\n        assertEquals(1, unique.getNumGroups());\r\n        assertEquals(0, unique.getNumMaterialGroups());\r\n    }\r\n    \r\n}"
  },
  {
    "path": "src/test/java/de/javagl/obj/TestObjUtilsTriangulate.java",
    "content": "package de.javagl.obj;\r\n\r\nimport static org.junit.Assert.assertEquals;\r\n\r\nimport java.io.IOException;\r\n\r\nimport org.junit.Test;\r\n\r\n\r\n@SuppressWarnings(\"javadoc\")\r\npublic class TestObjUtilsTriangulate\r\n{\r\n    @Test\r\n    public void testTriangulateSquareAndTriangle() \r\n        throws IOException\r\n    {\r\n        Obj obj = ObjReader.read(getClass().getResourceAsStream(\r\n            \"/squareAndTriangle.obj\"));\r\n        \r\n        assertEquals(2, obj.getNumFaces());\r\n        assertEquals(5, obj.getNumVertices());\r\n        assertEquals(0, obj.getNumTexCoords());\r\n        assertEquals(0, obj.getNumNormals());\r\n        assertEquals(1, obj.getNumGroups());\r\n        assertEquals(0, obj.getNumMaterialGroups());\r\n        \r\n        Obj triangulatedObj = ObjUtils.triangulate(obj);\r\n\r\n        assertEquals(3, triangulatedObj.getNumFaces());\r\n        assertEquals(5, triangulatedObj.getNumVertices());\r\n        assertEquals(0, triangulatedObj.getNumTexCoords());\r\n        assertEquals(0, triangulatedObj.getNumNormals());\r\n        assertEquals(1, triangulatedObj.getNumGroups());\r\n        assertEquals(0, triangulatedObj.getNumMaterialGroups());\r\n    }\r\n    \r\n}"
  },
  {
    "path": "src/test/java/de/javagl/obj/TestObjWriter.java",
    "content": "package de.javagl.obj;\r\n\r\nimport java.io.ByteArrayInputStream;\r\nimport java.io.ByteArrayOutputStream;\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.util.Scanner;\r\n\r\nimport static org.junit.Assert.*;\r\nimport org.junit.Test;\r\n\r\n\r\n@SuppressWarnings(\"javadoc\")\r\npublic class TestObjWriter\r\n{\r\n    @Test\r\n    public void readWriteSquare() \r\n        throws IOException\r\n    {\r\n        String inputString = readResourceAsString(\r\n            \"/square.obj\");\r\n        Obj obj = ObjReader.read(\r\n            new ByteArrayInputStream(inputString.getBytes()));\r\n\r\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\r\n        ObjWriter.write(obj, baos);\r\n        String outputString = new String(baos.toByteArray());\r\n        \r\n        //System.out.println(outputString);\r\n        assertEquals(inputString, outputString);\r\n    }\r\n    \r\n    @Test\r\n    public void readWriteSquareAndTriangle() \r\n        throws IOException\r\n    {\r\n        String inputString = readResourceAsString(\r\n            \"/squareAndTriangle.obj\");\r\n        Obj obj = ObjReader.read(\r\n            new ByteArrayInputStream(inputString.getBytes()));\r\n        \r\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\r\n        ObjWriter.write(obj, baos);\r\n        String outputString = new String(baos.toByteArray());\r\n        \r\n        //System.out.println(outputString);\r\n        assertEquals(inputString, outputString);\r\n    }\r\n\r\n    @Test\r\n    public void readWriteSquareAndTriangleInTwoGroups() \r\n        throws IOException\r\n    {\r\n        String inputString = readResourceAsString(\r\n            \"/squareAndTriangleInTwoGroups.obj\");\r\n        Obj obj = ObjReader.read(\r\n            new ByteArrayInputStream(inputString.getBytes()));\r\n        \r\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\r\n        ObjWriter.write(obj, baos);\r\n        String outputString = new String(baos.toByteArray());\r\n        \r\n        //System.out.println(outputString);\r\n        assertEquals(inputString, outputString);\r\n    }\r\n    \r\n    @Test\r\n    public void readWriteFourTrianglesInMixedGroups() \r\n        throws IOException\r\n    {\r\n        String inputString = readResourceAsString(\r\n            \"/fourTrianglesInMixedGroups.obj\");\r\n        Obj obj = ObjReader.read(\r\n            new ByteArrayInputStream(inputString.getBytes()));\r\n        \r\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\r\n        ObjWriter.write(obj, baos);\r\n        String outputString = new String(baos.toByteArray());\r\n        \r\n        //System.out.println(outputString);\r\n        assertEquals(inputString, outputString);\r\n    }\r\n    \r\n    @Test\r\n    public void readWriteTwoTrianglesOneInDefaultGroup() \r\n        throws IOException\r\n    {\r\n        String inputString = readResourceAsString(\r\n            \"/twoTrianglesOneInDefaultGroup.obj\");\r\n        Obj obj = ObjReader.read(\r\n            new ByteArrayInputStream(inputString.getBytes()));\r\n        \r\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\r\n        ObjWriter.write(obj, baos);\r\n        String outputString = new String(baos.toByteArray());\r\n        \r\n        //System.out.println(outputString);\r\n        assertEquals(inputString, outputString);\r\n    }\r\n    \r\n    \r\n    @Test\r\n    public void readTwoTrianglesSharedInThreeGroups() \r\n        throws IOException\r\n    {\r\n        String inputString = readResourceAsString(\r\n            \"/twoTrianglesSharedInThreeGroups.obj\");\r\n        Obj obj = ObjReader.read(\r\n            new ByteArrayInputStream(inputString.getBytes()));\r\n        \r\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\r\n        ObjWriter.write(obj, baos);\r\n        String outputString = new String(baos.toByteArray());\r\n        \r\n        //System.out.println(outputString);\r\n        assertEquals(inputString, outputString);\r\n    }\r\n    \r\n    private static String readResourceAsString(String name)\r\n    {\r\n        InputStream inputStream = \r\n            TestObjWriter.class.getResourceAsStream(name);\r\n        String string = readAsString(inputStream);\r\n        string = string.replaceAll(\"\\r\\n\", \"\\n\");\r\n        return string;\r\n    }\r\n    \r\n    private static String readAsString(InputStream inputStream) \r\n    {\r\n        try (Scanner scanner = new Scanner(inputStream))\r\n        {\r\n            scanner.useDelimiter(\"\\\\A\");\r\n            String string = scanner.next();\r\n            return string;\r\n        }\r\n    }    \r\n    \r\n    \r\n}"
  },
  {
    "path": "src/test/java/de/javagl/obj/TestObjsCreate.java",
    "content": "package de.javagl.obj;\r\n\r\nimport static org.junit.Assert.assertEquals;\r\n\r\nimport java.io.IOException;\r\nimport java.nio.FloatBuffer;\r\nimport java.nio.IntBuffer;\r\n\r\nimport org.junit.Test;\r\n\r\n@SuppressWarnings(\"javadoc\")\r\npublic class TestObjsCreate\r\n{\r\n    @Test\r\n    public void createFromIndexedTriangleData() \r\n        throws IOException\r\n    {\r\n        float[] vertices =\r\n        {\r\n            0,0,0,\r\n            1,0,0,\r\n            1,1,0,\r\n            0,1,0,\r\n        };\r\n        float[] texCoords =\r\n        {\r\n            0,0,\r\n            1,0,\r\n            1,1,\r\n            0,1,\r\n        };\r\n        float[] normals =\r\n        {\r\n            0,0,1,\r\n            0,0,1,\r\n            0,0,1,\r\n            0,0,1,\r\n        };\r\n        int[] indices =\r\n        {   \r\n            0,1,3,\r\n            0,1,2\r\n        };\r\n        \r\n        Obj obj = Objs.createFromIndexedTriangleData(\r\n            IntBuffer.wrap(indices), \r\n            FloatBuffer.wrap(vertices),\r\n            FloatBuffer.wrap(texCoords),\r\n            FloatBuffer.wrap(normals));\r\n        \r\n        assertEquals(2, obj.getNumFaces());\r\n        assertEquals(4, obj.getNumVertices());\r\n        assertEquals(4, obj.getNumTexCoords());\r\n        assertEquals(4, obj.getNumNormals());\r\n        assertEquals(1, obj.getNumGroups());\r\n        assertEquals(0, obj.getNumMaterialGroups());\r\n    }\r\n    \r\n       \r\n}"
  },
  {
    "path": "src/test/resources/complexMaterial.mtl",
    "content": "newmtl material0\r\nillum 3\r\nNs 500.0\r\nNi 2.3\r\nd 1.0\r\nKa 1.0 1.0 1.0\r\nKd 1.0 0.0 1.0\r\nKs 1.0 1.0 1.0\r\nTf 0.0 1.0 0.0\r\nsharpness 100.0\r\nmap_Ka -blendu off -blendv on -mm 2.3 4.5 -clamp on ambient.png\r\nmap_Kd -texres 512.0 diffuse.png\r\nmap_Ks -o 0.1 0.2 0.3 -s 1.1 1.2 1.3 -t 0.1 0.2 0.3 specularReflectivity.png\r\nmap_Ns -o 0.1 0.2 0.3 -s 1.1 1.2 1.3 specularExponent.png\r\nmap_d -mm 0.2 0.8 opacity.png\r\nbump -imfchan -bm bump.png\r\ndisp displacement.png\r\ndecal decal.png\r\nrefl -type cube_top cubeTop.png\r\nrefl -type cube_bottom cubeBottom.png\r\nrefl -type cube_front cubeFront.png\r\nrefl -type cube_back cubeBack.png\r\nrefl -type cube_left cubeLeft.png\r\nrefl -type cube_right cubeRight.png\r\n"
  },
  {
    "path": "src/test/resources/fourTrianglesInMixedGroups.obj",
    "content": "mtllib twoMaterialsA.mtl\r\nv 0.0 0.0 0.0\r\nv 4.0 0.0 0.0\r\nv 4.0 4.0 0.0\r\nv 0.0 4.0 0.0\r\nv 8.0 0.0 0.0\r\nv 8.0 4.0 0.0\r\ng group0 \r\nusemtl material0\r\nf 1 2 3\r\nusemtl material1\r\nf 1 3 4\r\ng group1 \r\nusemtl material2\r\nf 2 5 6\r\nusemtl material3\r\nf 2 6 3\r\n"
  },
  {
    "path": "src/test/resources/fourTrianglesPartiallyInMaterialGroups.obj",
    "content": "mtllib twoMaterialsA.mtl\r\nv 0.0 0.0 0.0\r\nv 4.0 0.0 0.0\r\nv 4.0 4.0 0.0\r\nv 0.0 4.0 0.0\r\nv 8.0 0.0 0.0\r\nv 8.0 4.0 0.0\r\nf 1 2 3\r\nf 1 3 4\r\ng group1 \r\nusemtl material0\r\nf 2 5 6\r\nusemtl material1\r\nf 2 6 3\r\n"
  },
  {
    "path": "src/test/resources/mtlWithBrokenLines.mtl",
    "content": "# Some lines are broken with a backslash\r\nnewmtl material0    \r\nKa 1.0 \\\r\n   0.0 \\\r\n   0.0\r\nKd 1.0 1.0 0.0\r\nKs 1.0 1.0 1.0\r\nNs 500.0\r\nmap_Kd texture.png\r\nd \\\r\n   123.0"
  },
  {
    "path": "src/test/resources/mtlWithPbrProperties.mtl",
    "content": "# www.blender.org\n\nnewmtl Material.001\nKd 0.800000 0.304983 0.243147\nKs 0.500000 0.500000 0.500000\nKe 0.000000 0.000000 0.000000\nNi 1.450000\nd 1.000000\nillum 2\nPr 0.500000\nPm 0.700000\nPs 0.100000\nPc 0.400000\nPcr 0.030000\naniso 0.001000\nanisor 0.010000\n"
  },
  {
    "path": "src/test/resources/mtlWithWhitespace.mtl",
    "content": "# Some lines have leading and trailing whitespace,\r\n# in form of space characters or tabs\r\n    newmtl material0    \r\n    Ka 1.0 0.0 0.0    \r\nKd 1.0 1.0 0.0\r\n    Ks 1.0 1.0 1.0\t    \r\n    Ns 500.0    \r\n\tmap_Kd texture.png\r\nd 1.0\r\n"
  },
  {
    "path": "src/test/resources/pbrMaterial.mtl",
    "content": "newmtl material1\nPr 0.12\nmap_Pr roughness.png\nPm 0.23\nmap_Pm metallic.png\nPs 0.34\nmap_Ps sheen.png\nPc 0.11\nPcr 0.222\nKe 0.2 0.3 0.4\nmap_Ke emissive.png\naniso 0.2\nanisor 0.3\nnorm normals.png\n"
  },
  {
    "path": "src/test/resources/square.obj",
    "content": "v 0.0 0.0 0.0\r\nv 4.0 0.0 0.0\r\nv 4.0 4.0 0.0\r\nv 0.0 4.0 0.0\r\nf 1 2 3 4\r\n"
  },
  {
    "path": "src/test/resources/squareAndTriangle.obj",
    "content": "v 0.0 0.0 0.0\r\nv 4.0 0.0 0.0\r\nv 4.0 4.0 0.0\r\nv 0.0 4.0 0.0\r\nv 2.0 6.0 0.0\r\nf 1 2 3 4\r\nf 3 4 5\r\n"
  },
  {
    "path": "src/test/resources/squareAndTriangleInTwoGroups.obj",
    "content": "v 0.0 0.0 0.0\r\nv 4.0 0.0 0.0\r\nv 4.0 4.0 0.0\r\nv 0.0 4.0 0.0\r\nv 2.0 6.0 0.0\r\ng group0 \r\nf 1 2 3 4\r\ng group1 \r\nf 3 4 5\r\n"
  },
  {
    "path": "src/test/resources/squareAndTriangleWithRelativeIndices.obj",
    "content": "v 0.0 0.0 0.0\r\nv 4.0 0.0 0.0\r\nv 4.0 4.0 0.0\r\nv 0.0 4.0 0.0\r\nv 2.0 6.0 0.0\r\nf 1 -4 -3 4\r\nf 3 -2 5\r\n"
  },
  {
    "path": "src/test/resources/squareTextured.obj",
    "content": "v 0.0 0.0 0.0\r\nv 4.0 0.0 0.0\r\nv 4.0 4.0 0.0\r\nv 0.0 4.0 0.0\r\nvt 0.0 0.0\r\nvt 1.0 0.0\r\nvt 1.0 1.0\r\nvt 0.0 1.0\r\nf 1/1 2/2 3/3 4/4\r\n"
  },
  {
    "path": "src/test/resources/twoMaterialsA.mtl",
    "content": "newmtl material0\r\nNs 500.0\r\nd 1.0\r\nKa 1.0 0.0 0.0\r\nKd 1.0 0.0 0.0\r\nKs 1.0 1.0 1.0\r\nnewmtl material1\r\nNs 0.0\r\nd 0.5\r\nKa 1.0 1.0 1.0\r\nKd 1.0 1.0 1.0\r\nKs 1.0 1.0 1.0\r\nmap_Kd texture.png\r\n"
  },
  {
    "path": "src/test/resources/twoMaterialsB.mtl",
    "content": "newmtl material2\r\nKa 1 0 1 \r\nKd 1 0 1\r\nKs 1 1 1\r\nNs 200\r\n \r\nnewmtl material3\r\nKa 0 1 1\r\nKd 0 1 1\r\nKs 0 1 1\r\nNs 100\r\nd 0.25\r\n\r\n\r\n"
  },
  {
    "path": "src/test/resources/twoTrianglesOneInDefaultGroup.obj",
    "content": "v 0.0 0.0 0.0\r\nv 4.0 0.0 0.0\r\nv 4.0 4.0 0.0\r\nv 0.0 4.0 0.0\r\nf 1 2 4\r\ng group0 \r\nf 2 3 4\r\n"
  },
  {
    "path": "src/test/resources/twoTrianglesSharedInThreeGroups.obj",
    "content": "v 0.0 0.0 0.0\r\nv 4.0 0.0 0.0\r\nv 4.0 4.0 0.0\r\nv 0.0 4.0 0.0\r\ng group0 group1 \r\nf 1 2 4\r\ng group1 group2 \r\nf 2 3 4\r\n"
  },
  {
    "path": "src/test/resources/twoTrianglesWithAmbiguousTexCoords.obj",
    "content": "v 0 0 0\r\nv 4 0 0\r\nv 4 4 0\r\nv 0 4 0\r\n\r\nvt 0 0\r\nvt 1 0\r\nvt 1 1\r\nvt 0 1\r\n\r\nf 1/1 2/2 4/4 \r\nf 2/1 3/3 4/4 \r\n"
  }
]