Repository: javagl/Obj Branch: master Commit: da3e9878c667 Files: 63 Total size: 313.0 KB Directory structure: gitextract_1k6agy0g/ ├── .gitignore ├── LICENSE.txt ├── README.md ├── pom.xml └── src/ ├── main/ │ └── java/ │ └── de/ │ └── javagl/ │ └── obj/ │ ├── AbstractWritableObj.java │ ├── BasicWritableObj.java │ ├── DefaultFloatTuple.java │ ├── DefaultMtl.java │ ├── DefaultObj.java │ ├── DefaultObjFace.java │ ├── DefaultObjGroup.java │ ├── DefaultTextureOptions.java │ ├── FloatTuple.java │ ├── FloatTuples.java │ ├── Mtl.java │ ├── MtlReader.java │ ├── MtlWriter.java │ ├── Mtls.java │ ├── Obj.java │ ├── ObjData.java │ ├── ObjFace.java │ ├── ObjFaceParser.java │ ├── ObjFaces.java │ ├── ObjGroup.java │ ├── ObjReader.java │ ├── ObjSplitter.java │ ├── ObjSplitting.java │ ├── ObjUtils.java │ ├── ObjWriter.java │ ├── Objs.java │ ├── ReadableObj.java │ ├── TextureOptions.java │ ├── Utils.java │ ├── WritableObj.java │ └── package-info.java └── test/ ├── java/ │ └── de/ │ └── javagl/ │ └── obj/ │ ├── TestMtlReader.java │ ├── TestMtlWriter.java │ ├── TestObjData.java │ ├── TestObjReader.java │ ├── TestObjSplitting.java │ ├── TestObjUtilsAdd.java │ ├── TestObjUtilsGroupToObj.java │ ├── TestObjUtilsMakeTexCoordsUnique.java │ ├── TestObjUtilsTriangulate.java │ ├── TestObjWriter.java │ └── TestObjsCreate.java └── resources/ ├── complexMaterial.mtl ├── fourTrianglesInMixedGroups.obj ├── fourTrianglesPartiallyInMaterialGroups.obj ├── mtlWithBrokenLines.mtl ├── mtlWithPbrProperties.mtl ├── mtlWithWhitespace.mtl ├── pbrMaterial.mtl ├── square.obj ├── squareAndTriangle.obj ├── squareAndTriangleInTwoGroups.obj ├── squareAndTriangleWithRelativeIndices.obj ├── squareTextured.obj ├── twoMaterialsA.mtl ├── twoMaterialsB.mtl ├── twoTrianglesOneInDefaultGroup.obj ├── twoTrianglesSharedInThreeGroups.obj └── twoTrianglesWithAmbiguousTexCoords.obj ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ target/ .classpath .project .settings ================================================ FILE: LICENSE.txt ================================================ www.javagl.de - Obj Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Obj - a simple Wavefront OBJ file loader and writer Maven dependency: de.javagl obj 0.4.0 Direct 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) # Samples Samples showing how to use this library are available in the [ObjSamples project](https://github.com/javagl/ObjSamples). # Overview This is a simple loader and writer for Wavefront `.OBJ` files. The elements that are currently supported are - Vertices - Texture coordinates - Normals - Faces (with positive or negative indices) - Groups - Material groups - MTL files The `Obj` interface is basically an in-memory representation of an OBJ file. It combines a `ReadableObj`, which provides the contents of the OBJ file, and a `WritableObj`, which may receive elements like vertices and faces in order to build an OBJ in memory. The `ObjReader` class may either create a new `Obj` object directly from an input stream, or pass the elements that are read from the input stream to a `WritableObj`. The `ObjWriter` class offers a method to write a `ReadableObj` object to an output stream. The `ObjData` class offers various methods to obtain the data that is stored in a `ReadableObj` as plain arrays or *direct* buffers. The `ObjUtils` class offers basic utility methods for general operations on the OBJ data. ## Rendering OBJ data with OpenGL The `ObjUtils` class contains methods that aim at preparing the OBJ so that it may easily be rendered with OpenGL. These methods may... - convert a single group of an OBJ into a new OBJ - triangulate OBJ data - make sure that texture coordinates or normal coordinates are unique for each vertex - convert an OBJ to an OBJ that is uses the same index sets for vertices, texture coordinates and normals The latter operations are also summarized in one dedicated method, namely the `ObjUtils.convertToRenderable` method: InputStream inputStream = ...; Obj obj = ObjUtils.convertToRenderable( ObjReader.read(inputStream)); IntBuffer indices = ObjData.getFaceVertexIndices(obj); FloatBuffer vertices = ObjData.getVertices(obj); FloatBuffer texCoords = ObjData.getTexCoords(obj, 2); FloatBuffer normals = ObjData.getNormals(obj); These buffers may directly be used as the data for vertex buffer objects (VBO) in OpenGL. ### Extracting material groups An OBJ may contain multiple material definitions. When such an OBJ should be rendered with OpenGL, this usually means that there will be one shader for each material - or at least, different textures may have to be used for different parts of the objects. This library offers methods to extract the parts of the OBJ that have the same material. In the OBJ format, these groups consist of the triangles that follow one `usemtl` directive. When such an OBJ file is read, the resulting material groups may be obtained from the `ReadableObj` object, and each of them can be converted into a new `Obj` object using the `ObjUtils#groupToObj` method. The `ObjSplitting` class contains a convenience method for this: Obj obj = ObjReader.read(...); Map mtlObjs = ObjSplitting.splitByMaterialGroups(obj);; Each of these `Obj` objects may then be converted into a renderable OBJ, using the `ObjUtils.convertToRenderable` method as described above, and then be rendered with the appropriate shader for the respective material. ### Limiting the number of vertices per OBJ In certain environments, the number of vertices that may be involved in one rendering call is limited. Particularly, in WebGL or OpenGL ES 2.0, the indices that are used for indexed draw calls may only be of the type `GL_UNSIGNED_SHORT`, which means that no object may have more than 65k vertices. In these cases, larger OBJ files have to be split into multiple parts. Additionally, the index buffers that are passed to the rendering API may not contain (4-byte) `int` elements, but only (2-byte) `short` elements. The `ObjSplitting` class contains a method that allows splitting an OBJ into multiple parts, each having only a maximum number of vertices. Additionally, the `ObjData` class contains methods for converting an `IntBuffer` into a `ShortBuffer`. So in order to split a large OBJ into multiple parts, and render each part with WebGL or OpenGL ES 2.0, the following code can be used: Obj largeObj = ObjReader.read(...); Obj renderableObj = ObjUtils.convertToRenderable(largeObj); if (renderableObj.getNumVertices() > 65000) { // If this has to be rendered with OpenGL ES 2.0, then // the object may not contain more than 65k vertices! // Split it into multiple parts: List renderableParts = ObjSplitting.splitByMaxNumVertices(renderableObj, 65000); for (Obj renderablePart : renderableParts) { // Obtain the indices as a "short" buffer that may // be used for OpenGL rendering with the index // type GL_UNSIGNED_SHORT ShortBuffer indices = ObjData.convertToShortBuffer( ObjData.getFaceVertexIndices(renderablePart)); ... sendToRenderer(indices, ...); } } ... --- # Change log **0.4.1-SNAPSHOT** - ... **0.4.0** (2023-03-04) - Updated MTL handling to support additional options. This includes the options that have been part of the original MTL specification, as well as PBR (Physically Based Rendering) options. - A new interface `TextureOptions` has been introduced. For texture maps, the `Mtl` interface now has methods `getMap...Options()` and `setMap...Options(...)`. This `TextureOptions` object contains the parameters that are common for all texture maps, like blending states, offsets, or scales, as described in the MTL specification. - **API change**: The `Mtl#get...` methods will now return `null` when no information was parsed from the input file. Code that originally called one of these methods, like ``` float d = mtl.getD(); ``` should now check whether these values are not `null`, as in ``` float d = 1.0f; // The default value for the opacity if (mtl.getD() != null) { d = mtl.getD(); } ``` This will not affect clients that only read or write the `Mtl` with the `MtlReader` or `MtlWriter`, because these classes will handle the `null`-cases internally. **0.3.0** (2018-01-12) - Added `ObjSplitting` class for splitting OBJs - Added `ObjData#convertToShortBuffer` method - Added `ObjUtils#add` method for combining OBJs **0.2.1** (2015-10-26) - Bugfix: Made `AbstractWritableObj#addNormal(FloatTuple)` non-final - Added methods in `ObjReader` and `MtlReader` that accept a `Reader` instead of an `InputStream`, and methods in `ObjWriter` and `MtlWriter` that accept a `Writer` instead of an `OutputStream` - Added implementation of `ObjUtils.convertToRenderable` that receives a `WritableObj` **0.2.0** (2015-10-05) : - Initial public release on GitHub and Maven Central ================================================ FILE: pom.xml ================================================ org.sonatype.oss oss-parent 9 4.0.0 de.javagl obj 0.4.1-SNAPSHOT obj https://github.com/javagl Marco Hutter javagl@javagl.de developer scm:git:git@github.com:javagl/Obj.git scm:git:git@github.com:javagl/Obj.git git@github.com:javagl/Obj.git MIT https://github.com/javagl/Obj/blob/master/LICENSE.txt repo UTF-8 maven-compiler-plugin 2.3.2 1.8 1.8 org.apache.maven.plugins maven-source-plugin 2.1.2 attach-sources jar org.apache.maven.plugins maven-javadoc-plugin 2.10.1 attach-javadocs jar junit junit 4.13.1 test A simple Wavefront OBJ file loader and writer ================================================ FILE: src/main/java/de/javagl/obj/AbstractWritableObj.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.util.Collection; import java.util.Objects; /** * Abstract base implementation of a {@link WritableObj}.
*
* The final implementations of the methods in this class all delegate * to the following (non-final) methods, which may be overridden by * implementors: * */ public class AbstractWritableObj implements WritableObj { /** * Default constructor */ protected AbstractWritableObj() { // Default constructor } @Override public final void addVertex(float x, float y, float z) { addVertex(FloatTuples.create(x, y, z)); } @Override public void addVertex(FloatTuple vertex) { // Empty default implementation } @Override public final void addTexCoord(float x) { addTexCoord(FloatTuples.create(x)); } @Override public final void addTexCoord(float x, float y) { addTexCoord(FloatTuples.create(x, y)); } @Override public final void addTexCoord(float x, float y, float z) { addTexCoord(FloatTuples.create(x, y, z)); } @Override public void addTexCoord(FloatTuple texCoord) { // Empty default implementation } @Override public void addNormal(FloatTuple normal) { // Empty default implementation } @Override public final void addNormal(float x, float y, float z) { addNormal(FloatTuples.create(x, y, z)); } @Override public void setActiveGroupNames( Collection groupNames) { // Empty default implementation } @Override public void setActiveMaterialGroupName(String materialGroupName) { // Empty default implementation } @Override public void addFace(ObjFace face) { // Empty default implementation } @Override public final void addFace(int ... v) { addFace(v, null, null); } @Override public final void addFaceWithTexCoords(int... v) { addFace(v, v, null); } @Override public final void addFaceWithNormals(int... v) { addFace(v, null, v); } @Override public final void addFaceWithAll(int... v) { addFace(v, v, v); } @Override public final void addFace(int[] v, int[] vt, int[] vn) { Objects.requireNonNull(v, "The vertex indices are null"); addFace(ObjFaces.create(v, vt, vn)); } @Override public void setMtlFileNames(Collection mtlFileNames) { // Empty default implementation } } ================================================ FILE: src/main/java/de/javagl/obj/BasicWritableObj.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.util.Collection; import java.util.Objects; import java.util.function.Consumer; /** * Basic implementation of a {@link WritableObj} that delegates all calls * to consumer callbacks.
*
* The consumers for the elements of an OBJ are null by default, * causing the respective elements to be ignored. The callbacks may be set * individually. For example, in order to print all vertices and faces that * are read from an OBJ file, the following may be used: *

 * BasicWritableObj obj = new BasicWritableObj();
 * obj.setVertexConsumer(t -> System.out.println(t));
 * obj.setFaceConsumer(t -> System.out.println(t));
 * ObjReader.read(inputStream, obj);
 * 
*/ public class BasicWritableObj implements WritableObj { /** * The vertex consumer */ private Consumer vertexConsumer; /** * The texture coordinate consumer */ private Consumer texCoordConsumer; /** * The normal consumer */ private Consumer normalConsumer; /** * The face consumer */ private Consumer faceConsumer; /** * The consumer for group names */ private Consumer> groupNamesConsumer; /** * The consumer for material group names */ private Consumer materialGroupNameConsumer; /** * The consumer for MTL file names */ private Consumer> mtlFileNamesConsumer; /** * Default constructor */ public BasicWritableObj() { // Default constructor } /** * Set the vertex consumer * * @param vertexConsumer The consumer */ public void setVertexConsumer(Consumer vertexConsumer) { this.vertexConsumer = vertexConsumer; } /** * Set the texture coordinate consumer * * @param texCoordConsumer The consumer */ public void setTexCoordConsumer( Consumer texCoordConsumer) { this.texCoordConsumer = texCoordConsumer; } /** * Set the normal consumer * * @param normalConsumer The consumer */ public void setNormalConsumer(Consumer normalConsumer) { this.normalConsumer = normalConsumer; } /** * Set the face consumer * * @param faceConsumer The consumer */ public void setFaceConsumer(Consumer faceConsumer) { this.faceConsumer = faceConsumer; } /** * Set the group names consumer * * @param groupNamesConsumer The consumer */ public void setGroupNamesConsumer( Consumer> groupNamesConsumer) { this.groupNamesConsumer = groupNamesConsumer; } /** * Set the material group name consumer * * @param materialGroupNameConsumer The consumer */ public void setMaterialGroupNameConsumer( Consumer materialGroupNameConsumer) { this.materialGroupNameConsumer = materialGroupNameConsumer; } /** * Set the MTL file names consumer * * @param mtlFileNamesConsumer The consumer */ public void setMtlFileNamesConsumer( Consumer> mtlFileNamesConsumer) { this.mtlFileNamesConsumer = mtlFileNamesConsumer; } @Override public final void addVertex(FloatTuple vertex) { if (vertexConsumer != null) { vertexConsumer.accept(vertex); } } @Override public final void addVertex(float x, float y, float z) { addVertex(FloatTuples.create(x, y, z)); } @Override public final void addTexCoord(FloatTuple texCoord) { if (texCoordConsumer != null) { texCoordConsumer.accept(texCoord); } } @Override public final void addTexCoord(float x) { addTexCoord(FloatTuples.create(x)); } @Override public final void addTexCoord(float x, float y) { addTexCoord(FloatTuples.create(x, y)); } @Override public final void addTexCoord(float x, float y, float z) { addTexCoord(FloatTuples.create(x, y, z)); } @Override public final void addNormal(FloatTuple normal) { if (normalConsumer != null) { normalConsumer.accept(normal); } } @Override public final void addNormal(float x, float y, float z) { addNormal(FloatTuples.create(x, y, z)); } @Override public final void setActiveGroupNames( Collection groupNames) { if (groupNamesConsumer != null) { groupNamesConsumer.accept(groupNames); } } @Override public final void setActiveMaterialGroupName(String materialGroupName) { if (materialGroupNameConsumer != null) { materialGroupNameConsumer.accept(materialGroupName); } } @Override public final void addFace(ObjFace face) { if (faceConsumer != null) { faceConsumer.accept(face); } } @Override public final void addFace(int ... v) { addFace(v, null, null); } @Override public final void addFaceWithTexCoords(int... v) { addFace(v, v, null); } @Override public final void addFaceWithNormals(int... v) { addFace(v, null, v); } @Override public final void addFaceWithAll(int... v) { addFace(v, v, v); } @Override public final void addFace(int[] v, int[] vt, int[] vn) { Objects.requireNonNull(v, "The vertex indices are null"); if (faceConsumer != null) { addFace(ObjFaces.create(v, vt, vn)); } } @Override public final void setMtlFileNames(Collection mtlFileNames) { if (mtlFileNamesConsumer != null) { mtlFileNamesConsumer.accept(mtlFileNames); } } } ================================================ FILE: src/main/java/de/javagl/obj/DefaultFloatTuple.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.util.Arrays; /** * Default implementation of a {@link FloatTuple} */ final class DefaultFloatTuple implements FloatTuple { /** * The values of this tuple */ private final float[] values; /** * Creates a new DefaultFloatTuple with the given values * * @param values The values */ DefaultFloatTuple(float[] values) { this.values = values; } /** * Creates a new DefaultFloatTuple with the given values * * @param x The x value * @param y The y value * @param z The z value * @param w The w value */ DefaultFloatTuple(float x, float y, float z, float w) { this(new float[]{x,y,z,w}); } /** * Creates a new DefaultFloatTuple with the given values * * @param x The x value * @param y The y value * @param z The z value */ DefaultFloatTuple(float x, float y, float z) { this(new float[]{x,y,z}); } /** * Creates a new DefaultFloatTuple with the given values * * @param x The x value * @param y The y value */ DefaultFloatTuple(float x, float y) { this(new float[]{x,y}); } /** * Creates a new DefaultFloatTuple with the given value * * @param x The x value */ DefaultFloatTuple(float x) { this(new float[]{x}); } /** * Copy constructor. * * @param other The other FloatTuple */ DefaultFloatTuple(FloatTuple other) { this(getValues(other)); } /** * Returns the values of the given {@link FloatTuple} as an array * * @param f The {@link FloatTuple} * @return The values */ private static float[] getValues(FloatTuple f) { if (f instanceof DefaultFloatTuple) { DefaultFloatTuple other = (DefaultFloatTuple)f; return other.values.clone(); } float[] values = new float[f.getDimensions()]; for (int i=0; i reflOptions; // PBR Parameters: /** * The roughness of this material */ private Float pr; /** * The roughness map texture options */ private TextureOptions mapPrOptions; /** * The metallic part of this material */ private Float pm; /** * The metallic map texture options */ private TextureOptions mapPmOptions; /** * The sheen part of this material */ private Float ps; /** * The sheen map texture options */ private TextureOptions mapPsOptions; /** * The clearcoat thickness of this material */ private Float pc; /** * The clearcoat roughness of this material */ private Float pcr; /** * The emissive part of this material */ private FloatTuple ke; /** * The emissive map texture options */ private TextureOptions mapKeOptions; /** * The anisotropy of this material */ private Float aniso; /** * The anisotropy rotation of this material */ private Float anisor; /** * The normal map texture options */ private TextureOptions normOptions; /** * Creates a new material with the given name * * @param name The name of this material */ DefaultMtl(String name) { this.name = name; this.reflOptions = new ArrayList<>(); } @Override public String getName() { return name; } @Override public Integer getIllum() { return illum; } @Override public void setIllum(Integer illum) { this.illum = illum; } @Override public Float getNi() { return ni; } @Override public void setNi(Float ni) { this.ni = ni; } @Override public FloatTuple getTf() { return tf; } @Override public void setTf(Float r, Float g, Float b) { this.tf = Utils.createRgbTuple(r, g, b); } @Override public Float getSharpness() { return sharpness; } @Override public void setSharpness(Float sharpness) { this.sharpness = sharpness; } @Override public FloatTuple getKa() { return ka; } @Override public void setKa(Float r, Float g, Float b) { this.ka = Utils.createRgbTuple(r, g, b); } @Override public String getMapKa() { if (mapKaOptions == null) { return null; } return mapKaOptions.getFileName(); } @Override public void setMapKa(String mapKa) { if (mapKaOptions == null) { mapKaOptions = new DefaultTextureOptions(); } mapKaOptions.setFileName(mapKa); } @Override public TextureOptions getMapKaOptions() { return mapKaOptions; } @Override public void setMapKaOptions(TextureOptions options) { this.mapKaOptions = options; } @Override public FloatTuple getKd() { return kd; } @Override public void setKd(Float r, Float g, Float b) { this.kd = Utils.createRgbTuple(r, g, b); } @Override public String getMapKd() { if (mapKdOptions == null) { return null; } return mapKdOptions.getFileName(); } @Override public void setMapKd(String mapKd) { if (mapKdOptions == null) { mapKdOptions = new DefaultTextureOptions(); } mapKdOptions.setFileName(mapKd); } @Override public TextureOptions getMapKdOptions() { return mapKdOptions; } @Override public void setMapKdOptions(TextureOptions options) { this.mapKdOptions = options; } @Override public FloatTuple getKs() { return ks; } @Override public void setKs(Float r, Float g, Float b) { this.ks = Utils.createRgbTuple(r, g, b); } @Override public String getMapKs() { if (mapKsOptions == null) { return null; } return mapKsOptions.getFileName(); } @Override public void setMapKs(String mapKs) { if (mapKsOptions == null) { mapKsOptions = new DefaultTextureOptions(); } mapKsOptions.setFileName(mapKs); } @Override public TextureOptions getMapKsOptions() { return mapKsOptions; } @Override public void setMapKsOptions(TextureOptions options) { this.mapKsOptions = options; } @Override public Float getNs() { return ns; } @Override public void setNs(Float ns) { this.ns = ns; } @Override public String getMapNs() { if (mapNsOptions == null) { return null; } return mapNsOptions.getFileName(); } @Override public void setMapNs(String mapNs) { if (mapNsOptions == null) { mapNsOptions = new DefaultTextureOptions(); } mapNsOptions.setFileName(mapNs); } @Override public TextureOptions getMapNsOptions() { return mapNsOptions; } @Override public void setMapNsOptions(TextureOptions options) { this.mapNsOptions = options; } @Override public Float getD() { return d; } @Override public void setD(Float d) { this.d = d; } @Override public Boolean isHalo() { return halo; } @Override public void setHalo(Boolean halo) { this.halo = halo; } @Override public String getMapD() { if (mapDOptions == null) { return null; } return mapDOptions.getFileName(); } @Override public void setMapD(String mapD) { if (mapDOptions == null) { mapDOptions = new DefaultTextureOptions(); } mapDOptions.setFileName(mapD); } @Override public TextureOptions getMapDOptions() { return mapDOptions; } @Override public void setMapDOptions(TextureOptions options) { this.mapDOptions = options; } @Override public String getBump() { if (bumpOptions == null) { return null; } return bumpOptions.getFileName(); } @Override public void setBump(String bump) { if (bumpOptions == null) { bumpOptions = new DefaultTextureOptions(); } bumpOptions.setFileName(bump); } @Override public TextureOptions getBumpOptions() { return bumpOptions; } @Override public void setBumpOptions(TextureOptions options) { this.bumpOptions = options; } @Override public String getDisp() { if (dispOptions == null) { return null; } return dispOptions.getFileName(); } @Override public void setDisp(String disp) { if (dispOptions == null) { dispOptions = new DefaultTextureOptions(); } dispOptions.setFileName(disp); } @Override public TextureOptions getDispOptions() { return dispOptions; } @Override public void setDispOptions(TextureOptions options) { this.dispOptions = options; } @Override public String getDecal() { if (decalOptions == null) { return null; } return decalOptions.getFileName(); } @Override public void setDecal(String decal) { if (decalOptions == null) { decalOptions = new DefaultTextureOptions(); } decalOptions.setFileName(decal); } @Override public TextureOptions getDecalOptions() { return decalOptions; } @Override public void setDecalOptions(TextureOptions options) { this.decalOptions = options; } @Override public List getReflOptions() { return reflOptions; } // PRB parameters @Override public Float getPr() { return pr; } @Override public void setPr(Float pr) { this.pr = pr; } @Override public String getMapPr() { if (mapPrOptions == null) { return null; } return mapPrOptions.getFileName(); } @Override public void setMapPr(String mapPr) { if (mapPrOptions == null) { mapPrOptions = new DefaultTextureOptions(); } mapPrOptions.setFileName(mapPr); } @Override public TextureOptions getMapPrOptions() { return mapPrOptions; } @Override public void setMapPrOptions(TextureOptions options) { this.mapPrOptions = options; } @Override public Float getPm() { return pm; } @Override public void setPm(Float pm) { this.pm = pm; } @Override public String getMapPm() { if (mapPmOptions == null) { return null; } return mapPmOptions.getFileName(); } @Override public void setMapPm(String mapPm) { if (mapPmOptions == null) { mapPmOptions = new DefaultTextureOptions(); } mapPmOptions.setFileName(mapPm); } @Override public TextureOptions getMapPmOptions() { return mapPmOptions; } @Override public void setMapPmOptions(TextureOptions options) { this.mapPmOptions = options; } @Override public Float getPs() { return ps; } @Override public void setPs(Float ps) { this.ps = ps; } @Override public String getMapPs() { if (mapPsOptions == null) { return null; } return mapPsOptions.getFileName(); } @Override public void setMapPs(String mapPs) { if (mapPsOptions == null) { mapPsOptions = new DefaultTextureOptions(); } mapPsOptions.setFileName(mapPs); } @Override public TextureOptions getMapPsOptions() { return mapPsOptions; } @Override public void setMapPsOptions(TextureOptions options) { this.mapPsOptions = options; } @Override public Float getPc() { return pc; } @Override public void setPc(Float pc) { this.pc = pc; } @Override public Float getPcr() { return pcr; } @Override public void setPcr(Float pcr) { this.pcr = pcr; } @Override public FloatTuple getKe() { return ke; } @Override public void setKe(Float r, Float g, Float b) { this.ke = Utils.createRgbTuple(r, g, b); } @Override public String getMapKe() { if (mapKeOptions == null) { return null; } return mapKeOptions.getFileName(); } @Override public void setMapKe(String mapKe) { if (mapKeOptions == null) { mapKeOptions = new DefaultTextureOptions(); } mapKeOptions.setFileName(mapKe); } @Override public TextureOptions getMapKeOptions() { return mapKeOptions; } @Override public void setMapKeOptions(TextureOptions options) { this.mapKeOptions = options; } @Override public Float getAniso() { return aniso; } @Override public void setAniso(Float aniso) { this.aniso = aniso; } @Override public Float getAnisor() { return anisor; } @Override public void setAnisor(Float anisor) { this.anisor = anisor; } @Override public String getNorm() { if (normOptions == null) { return null; } return normOptions.getFileName(); } @Override public void setNorm(String norm) { if (normOptions == null) { normOptions = new DefaultTextureOptions(); } normOptions.setFileName(norm); } @Override public TextureOptions getNormOptions() { return normOptions; } @Override public void setNormOptions(TextureOptions options) { this.normOptions = options; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Mtl"); sb.append("["); sb.append("name=").append(getName()); sb.append("]"); return sb.toString(); } } ================================================ FILE: src/main/java/de/javagl/obj/DefaultObj.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; /** * Default implementation of an {@link Obj} */ final class DefaultObj implements Obj { /** * The vertices in this Obj */ private final List vertices; /** * The texture coordinates in this Obj. */ private final List texCoords; /** * The normals in this Obj */ private final List normals; /** * The faces in this Obj. */ private final List faces; /** * The groups in this Obj. */ private final List groups; /** * The material groups in this Obj. */ private final List materialGroups; /** * Maps a group name to a group */ private final Map groupMap; /** * Maps a material name to a material group */ private final Map materialGroupMap; /** * The names of the MTL files for this Obj. */ private List mtlFileNames = Collections.emptyList(); /** * A map from the faces to the names of the groups that started * at this face */ private final Map> startedGroupNames; /** * A map from the faces to the name of the material group that started * at this face */ private final Map startedMaterialGroupNames; /** * The names for the groups that should be used for faces that are * added subsequently */ private Set nextActiveGroupNames = null; /** * The name for the material group that should be used for faces that are * added subsequently */ private String nextActiveMaterialGroupName = null; /** * The groups that are currently active, and to which faces will be * added */ private List activeGroups = null; /** * The names of the groups that faces are currently added to */ private Set activeGroupNames = null; /** * The material group that is currently active, and to which faces will be * added */ private DefaultObjGroup activeMaterialGroup = null; /** * The name of the material group that is currently active */ private String activeMaterialGroupName = null; /** * Creates a new, empty DefaultObj. */ DefaultObj() { vertices = new ArrayList<>(); normals = new ArrayList<>(); texCoords = new ArrayList<>(); faces = new ArrayList<>(); groups = new ArrayList<>(); materialGroups = new ArrayList<>(); groupMap = new LinkedHashMap<>(); materialGroupMap = new LinkedHashMap<>(); startedGroupNames = new HashMap<>(); startedMaterialGroupNames = new HashMap<>(); setActiveGroupNames(Arrays.asList("default")); getGroupInternal("default"); } @Override public int getNumVertices() { return vertices.size(); } @Override public FloatTuple getVertex(int index) { return vertices.get(index); } @Override public int getNumTexCoords() { return texCoords.size(); } @Override public FloatTuple getTexCoord(int index) { return texCoords.get(index); } @Override public int getNumNormals() { return normals.size(); } @Override public FloatTuple getNormal(int index) { return normals.get(index); } @Override public int getNumFaces() { return faces.size(); } @Override public ObjFace getFace(int index) { return faces.get(index); } @Override public Set getActivatedGroupNames(ObjFace face) { return startedGroupNames.get(face); } @Override public String getActivatedMaterialGroupName(ObjFace face) { return startedMaterialGroupNames.get(face); } @Override public int getNumGroups() { return groups.size(); } @Override public ObjGroup getGroup(int index) { return groups.get(index); } @Override public ObjGroup getGroup(String name) { return groupMap.get(name); } @Override public int getNumMaterialGroups() { return materialGroups.size(); } @Override public ObjGroup getMaterialGroup(int index) { return materialGroups.get(index); } @Override public ObjGroup getMaterialGroup(String name) { return materialGroupMap.get(name); } @Override public List getMtlFileNames() { return mtlFileNames; } @Override public void addVertex(FloatTuple vertex) { Objects.requireNonNull(vertex, "The vertex is null"); vertices.add(vertex); } @Override public void addVertex(float x, float y, float z) { vertices.add(new DefaultFloatTuple(x, y, z)); } @Override public void addTexCoord(FloatTuple texCoord) { Objects.requireNonNull(texCoord, "The texCoord is null"); texCoords.add(texCoord); } @Override public void addTexCoord(float x) { texCoords.add(new DefaultFloatTuple(x)); } @Override public void addTexCoord(float x, float y) { texCoords.add(new DefaultFloatTuple(x, y)); } @Override public void addTexCoord(float x, float y, float z) { texCoords.add(new DefaultFloatTuple(x, y, z)); } @Override public void addNormal(FloatTuple normal) { Objects.requireNonNull(normal, "The normal is null"); normals.add(normal); } @Override public void addNormal(float x, float y, float z) { normals.add(new DefaultFloatTuple(x, y, z)); } @Override public void setActiveGroupNames(Collection groupNames) { if (groupNames == null) { return; } if (groupNames.size() == 0) { groupNames = Arrays.asList("default"); } else if (groupNames.contains(null)) { throw new NullPointerException("The groupNames contains null"); } nextActiveGroupNames = Collections.unmodifiableSet(new LinkedHashSet(groupNames)); } @Override public void setActiveMaterialGroupName(String materialGroupName) { if (materialGroupName == null) { return; } nextActiveMaterialGroupName = materialGroupName; } @Override public void addFace(ObjFace face) { if (face == null) { throw new NullPointerException("The face is null"); } if (nextActiveGroupNames != null) { activeGroups = getGroupsInternal(nextActiveGroupNames); if (!nextActiveGroupNames.equals(activeGroupNames)) { startedGroupNames.put(face, nextActiveGroupNames); } activeGroupNames = nextActiveGroupNames; nextActiveGroupNames = null; } if (nextActiveMaterialGroupName != null) { activeMaterialGroup = getMaterialGroupInternal(nextActiveMaterialGroupName); if (!nextActiveMaterialGroupName.equals(activeMaterialGroupName)) { startedMaterialGroupNames.put(face, nextActiveMaterialGroupName); } activeMaterialGroupName = nextActiveMaterialGroupName; nextActiveMaterialGroupName = null; } faces.add(face); if (activeMaterialGroup != null) { activeMaterialGroup.addFace(face); } for (DefaultObjGroup group : activeGroups) { group.addFace(face); } } @Override public void addFace(int ... v) { addFace(v, null, null); } @Override public void addFaceWithTexCoords(int... v) { addFace(v, v, null); } @Override public void addFaceWithNormals(int... v) { addFace(v, null, v); } @Override public void addFaceWithAll(int... v) { addFace(v, v, v); } @Override public void addFace(int[] v, int[] vt, int[] vn) { Objects.requireNonNull(v, "The vertex indices are null"); checkIndices(v, getNumVertices(), "Vertex"); checkIndices(vt, getNumTexCoords(), "TexCoord"); checkIndices(vn, getNumNormals(), "Normal"); DefaultObjFace face = new DefaultObjFace(v, vt, vn); addFace(face); } @Override public void setMtlFileNames(Collection mtlFileNames) { this.mtlFileNames = Collections.unmodifiableList( new ArrayList(mtlFileNames)); } @Override public String toString() { return "Obj[" + "#vertices="+ vertices.size() + "," + "#texCoords=" + texCoords.size() + "," + "#normals=" + normals.size() + "," + "#faces=" + faces.size() + "," + "#groups=" + groups.size() + "," + "#materialGroups=" + materialGroups.size() + "," + "mtlFileNames=" + mtlFileNames + "]"; } /** * Returns a set containing all groups with the given names. If the * groups with the given names do not exist, they are created and * added to this Obj. * * @param groupNames The group names * @return The groups */ private List getGroupsInternal( Collection groupNames) { List groups = new ArrayList<>(groupNames.size()); for (String groupName : groupNames) { DefaultObjGroup group = getGroupInternal(groupName); groups.add(group); } return groups; } /** * Returns the group with the given names. If the group with the given * name does not exist, it is created and added to this Obj. * * @param groupName The group name * @return The group */ private DefaultObjGroup getGroupInternal(String groupName) { DefaultObjGroup group = groupMap.get(groupName); if (group == null) { group = new DefaultObjGroup(groupName); groupMap.put(groupName, group); groups.add(group); } return group; } /** * Returns the material group with the given names. If the material group * with the given name does not exist, it is created and added to this Obj. * * @param materialGroupName The material group name * @return The material group */ private DefaultObjGroup getMaterialGroupInternal(String materialGroupName) { DefaultObjGroup group = materialGroupMap.get(materialGroupName); if (group == null) { group = new DefaultObjGroup(materialGroupName); materialGroupMap.put(materialGroupName, group); materialGroups.add(group); } return group; } /** * If the given indices are null, then this method will * do nothing. Otherwise, it will check whether the given indices * are valid, and throw an IllegalArgumentException if not. They * are valid when they are all not negative, and all smaller than * the given maximum. * * @param indices The indices * @param max The maximum index, exclusive * @param name The name of the index set * @throws IllegalArgumentException If the given indices are not valid */ private static void checkIndices(int[] indices, int max, String name) { if (indices == null) { return; } for (int index : indices) { if (index < 0) { throw new IllegalArgumentException( name + " index is negative: " + index); } if (index >= max) { throw new IllegalArgumentException( name + " index is " + index + ", but must be smaller than " + max); } } } } ================================================ FILE: src/main/java/de/javagl/obj/DefaultObjFace.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; /** * Default implementation of an ObjFace */ final class DefaultObjFace implements ObjFace { /** * The vertex indices of this face */ private final int[] vertexIndices; /** * The texture coordinate indices of this face */ private final int[] texCoordIndices; /** * The normal indices of this face */ private final int[] normalIndices; /** * Creates a face from the given parameters. References to the * given objects will be stored. * * @param vertexIndices The vertex indices * @param texCoordIndices The texture coordinate indices * @param normalIndices The normal indices */ DefaultObjFace( int[] vertexIndices, int[] texCoordIndices, int[] normalIndices) { this.vertexIndices = vertexIndices; this.texCoordIndices = texCoordIndices; this.normalIndices = normalIndices; } @Override public boolean containsTexCoordIndices() { return texCoordIndices != null; } @Override public boolean containsNormalIndices() { return normalIndices != null; } @Override public int getVertexIndex(int number) { return this.vertexIndices[number]; } @Override public int getTexCoordIndex(int number) { return this.texCoordIndices[number]; } @Override public int getNormalIndex(int number) { return this.normalIndices[number]; } /** * Set the specified index to the given value * * @param n The index to set * @param index The value of the index */ void setVertexIndex(int n, int index) { vertexIndices[n] = index; } /** * Set the specified index to the given value * * @param n The index to set * @param index The value of the index */ void setNormalIndex(int n, int index) { normalIndices[n] = index; } /** * Set the specified index to the given value * * @param n The index to set * @param index The value of the index */ void setTexCoordIndex(int n, int index) { texCoordIndices[n] = index; } @Override public int getNumVertices() { return this.vertexIndices.length; } @Override public String toString() { String result = "ObjFace["; for(int i = 0; i < getNumVertices(); i++) { result += vertexIndices[i]; if(texCoordIndices != null || normalIndices != null) { result += "/"; } if(texCoordIndices != null) { result += texCoordIndices[i]; } if(normalIndices != null) { result += "/" + normalIndices[i]; } if(i < getNumVertices() - 1) { result += " "; } } result += "]"; return result; } } ================================================ FILE: src/main/java/de/javagl/obj/DefaultObjGroup.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.util.ArrayList; import java.util.List; /** * Default implementation of an ObjGroup */ final class DefaultObjGroup implements ObjGroup { /** * The name of this group. */ private String name; /** * The faces in this group */ private List faces; /** * Creates a new ObjGroup with the given name * * @param name The name of this ObjGroup */ DefaultObjGroup(String name) { this.name = name; faces = new ArrayList<>(); } @Override public String getName() { return name; } /** * Add the given face to this group * * @param face The face to add */ void addFace(ObjFace face) { faces.add(face); } @Override public int getNumFaces() { return faces.size(); } @Override public ObjFace getFace(int index) { return faces.get(index); } @Override public String toString() { return "ObjGroup[name=" + name + ",#faces=" + faces.size() + "]"; } } ================================================ FILE: src/main/java/de/javagl/obj/DefaultTextureOptions.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.util.Objects; /** * Default implementation of {@link TextureOptions} */ final class DefaultTextureOptions implements TextureOptions { /** * The file name */ private String fileName; /** * The horizontal blending state */ private Boolean blendu; /** * The vertical blending state */ private Boolean blendv; /** * The color correction state */ private Boolean cc; /** * The mip-map boost value */ private Float boost; /** * The map modifiers */ private FloatTuple mm; /** * The origin offset */ private FloatTuple o; /** * The scale */ private FloatTuple s; /** * The turbulence */ private FloatTuple t; /** * The texture resolution */ private Float texres; /** * The clamping state */ private Boolean clamp; /** * The bump multiplier */ private Float bm; /** * The IMF channel */ private String imfchan; /** * The type */ private String type; /** * Default constructor */ DefaultTextureOptions() { // Default constructor } @Override public String getFileName() { return fileName; } @Override public void setFileName(String fileName) { this.fileName = fileName; } @Override public Boolean isBlendu() { return blendu; } @Override public void setBlendu(Boolean blendu) { this.blendu = blendu; } @Override public Boolean isBlendv() { return blendv; } @Override public void setBlendv(Boolean blendv) { this.blendv = blendv; } @Override public Float getBoost() { return boost; } @Override public Boolean isCc() { return cc; } @Override public void setCc(Boolean cc) { this.cc = cc; } @Override public void setBoost(Float boost) { this.boost = boost; } @Override public FloatTuple getMm() { return mm; } @Override public void setMm(Float base, Float gain) { if (base == null && gain == null) { this.mm = null; } float baseValue = (base == null ? 0.0f : base); float gainValue = (gain == null ? 1.0f : gain); this.mm = FloatTuples.create(baseValue, gainValue); } @Override public FloatTuple getO() { return o; } @Override public void setO(Float u, Float v, Float w) { this.o = Utils.createUvwTuple(u, v, w, 0.0f); } @Override public FloatTuple getS() { return s; } @Override public void setS(Float u, Float v, Float w) { this.s = Utils.createUvwTuple(u, v, w, 1.0f); } @Override public FloatTuple getT() { return t; } @Override public void setT(Float u, Float v, Float w) { this.t = Utils.createUvwTuple(u, v, w, 0.0f); } @Override public Float getTexres() { return texres; } @Override public void setTexres(Float texres) { this.texres = texres; } @Override public Boolean isClamp() { return clamp; } @Override public void setClamp(Boolean clamp) { this.clamp = clamp; } @Override public Float getBm() { return bm; } @Override public void setBm(Float bm) { this.bm = bm; } @Override public String getImfchan() { return imfchan; } @Override public void setImfchan(String imfchan) { this.imfchan = imfchan; } @Override public String getType() { return type; } @Override public void setType(String type) { this.type = type; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("TextureOptions"); sb.append("["); sb.append(MtlWriter.createString(this)); sb.append("]"); return sb.toString(); } @Override public int hashCode() { return Objects.hash(blendu, blendv, cc, bm, boost, clamp, fileName, imfchan, mm, o, s, t, texres, type); } @Override public boolean equals(Object object) { if (this == object) { return true; } if (object == null) { return false; } if (!(object instanceof TextureOptions)) { return false; } TextureOptions other = (TextureOptions) object; return Objects.equals(isBlendu(), other.isBlendu()) && Objects.equals(isBlendv(), other.isBlendv()) && Objects.equals(isCc(), other.isCc()) && Objects.equals(getBm(), other.getBm()) && Objects.equals(getBoost(), other.getBoost()) && Objects.equals(isClamp(), other.isClamp()) && Objects.equals(getFileName(), other.getFileName()) && Objects.equals(getImfchan(), other.getImfchan()) && Objects.equals(getMm(), other.getMm()) && Objects.equals(getO(), other.getO()) && Objects.equals(getS(), other.getS()) && Objects.equals(getT(), other.getT()) && Objects.equals(getTexres(), other.getTexres()) && Objects.equals(getType(), other.getType()); } } ================================================ FILE: src/main/java/de/javagl/obj/FloatTuple.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; /** * Interface for tuples consisting of float values */ public interface FloatTuple { /** * Return the x-component of this tuple * * @return The x-component of this tuple * @throws IndexOutOfBoundsException If this tuple has less than 1 * dimension */ float getX(); /** * Return the y-component of this tuple * * @return The y-component of this tuple * @throws IndexOutOfBoundsException If this tuple has less than 2 * dimensions */ float getY(); /** * Return the z-component of this tuple * * @return The z-component of this tuple * @throws IndexOutOfBoundsException If this tuple has less than 3 * dimensions */ float getZ(); /** * Return the w-component of this tuple * * @return The w-component of this tuple * @throws IndexOutOfBoundsException If this tuple has less than 4 * dimensions */ float getW(); /** * Return the specified component of this tuple * * @param index The index of the component * @return The specified component of this tuple * @throws IndexOutOfBoundsException If the given index is negative * or not smaller than the {@link #getDimensions() dimensions} */ float get(int index); /** * Return the dimensions of this tuple * * @return The dimensions of this tuple */ int getDimensions(); } ================================================ FILE: src/main/java/de/javagl/obj/FloatTuples.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; /** * Methods to create {@link FloatTuple} instances */ public class FloatTuples { /** * Create a copy of the given {@link FloatTuple} * * @param other The other tuple * @return The {@link FloatTuple} */ public static FloatTuple copy(FloatTuple other) { return new DefaultFloatTuple(other); } /** * Create a new {@link FloatTuple} with the given coordinate * * @param x The x-coordinate * @return The {@link FloatTuple} */ public static FloatTuple create(float x) { return new DefaultFloatTuple(x); } /** * Create a new {@link FloatTuple} with the given coordinates * * @param x The x-coordinate * @param y The y-coordinate * @return The {@link FloatTuple} */ public static FloatTuple create(float x, float y) { return new DefaultFloatTuple(x, y); } /** * Create a new {@link FloatTuple} with the given coordinates * * @param x The x-coordinate * @param y The y-coordinate * @param z The z-coordinate * @return The {@link FloatTuple} */ public static FloatTuple create(float x, float y, float z) { return new DefaultFloatTuple(x, y, z); } /** * Create a new {@link FloatTuple} with the given coordinates * * @param x The x-coordinate * @param y The y-coordinate * @param z The z-coordinate * @param w The w-coordinate * @return The {@link FloatTuple} */ public static FloatTuple create(float x, float y, float z, float w) { return new DefaultFloatTuple(x, y, z, w); } /** * Returns the string for the given tuple that is used for representing * the given tuple in an OBJ file * * @param tuple The tuple * @return The string for the given tuple */ public static String createString(FloatTuple tuple) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < tuple.getDimensions(); i++) { if (i > 0) { sb.append(" "); } sb.append(tuple.get(i)); } return sb.toString(); } /** * Private constructor to prevent instantiation */ private FloatTuples() { // Private constructor to prevent instantiation } } ================================================ FILE: src/main/java/de/javagl/obj/Mtl.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.util.List; /** * An in-memory representation of an MTL file. For details about the * semantics of the properties in this interface, refer to the MTL * specification. */ public interface Mtl { /** * Return the name of the material * * @return The name of the material */ String getName(); /** * Returns the illumination mode (-illum) of the material, * or null if it was not specified * * @return The illumination mode of the material. */ Integer getIllum(); /** * Set the illumination mode of the material * * @param illum The illumination mode of the material. */ void setIllum(Integer illum); /** * Returns the optical density, also known as index of refraction, of * the material, or null if it was not specified * * @return The optical density */ Float getNi(); /** * Set the optical density of the material * * @param ni The optical density */ void setNi(Float ni); /** * Returns the transmission filter of the material, * or null if it was not specified * * @return The transmission filter */ FloatTuple getTf(); /** * Set the transmission filter of this material * * @param r The red component * @param g The green component * @param b The blue component */ void setTf(Float r, Float g, Float b); /** * Returns the sharpness of reflections from the reflection map, * or null if it was not specified * * @return The sharpness */ Float getSharpness(); /** * Set the sharpness of reflections * * @param sharpness The sharpness */ void setSharpness(Float sharpness); //-------------------------------------------------------------------------- // Ambient /** * Returns the ambient component of the material, * or null if it was not specified * * @return The ambient component of the material */ FloatTuple getKa(); /** * Set the ambient part of this material * * @param r The red component * @param g The green component * @param b The blue component */ void setKa(Float r, Float g, Float b); /** * Returns the name of the ambient map of the material, * or null if it has no such map. * * @return The name of the ambient map of the material */ String getMapKa(); /** * Set the ambient map name of this material * * @param mapKa The ambient map name of this material */ void setMapKa(String mapKa); /** * Returns the ambient map options of the material, * or null if it has no options. * * @return The ambient map {@link TextureOptions} */ TextureOptions getMapKaOptions(); /** * Set the ambient map {@link TextureOptions} * * @param options The ambient map {@link TextureOptions} */ void setMapKaOptions(TextureOptions options); //-------------------------------------------------------------------------- // Diffuse /** * Returns the diffuse component of the material, * or null if it was not specified * * @return The diffuse component of the material */ FloatTuple getKd(); /** * Set the diffuse part of this material * * @param r The red component * @param g The green component * @param b The blue component */ void setKd(Float r, Float g, Float b); /** * Returns the name of the diffuse map of the material, * or null if it has no such map. * * @return The name of the diffuse map of the material */ String getMapKd(); /** * Set the diffuse map name of this material * * @param mapKd The diffuse map name of this material */ void setMapKd(String mapKd); /** * Returns the diffuse map options of the material, * or null if it has no options. * * @return The diffuse map {@link TextureOptions} */ TextureOptions getMapKdOptions(); /** * Set the diffuse map {@link TextureOptions} * * @param options The diffuse map {@link TextureOptions} */ void setMapKdOptions(TextureOptions options); //-------------------------------------------------------------------------- // Specular reflectivity /** * Returns the specular component of the material, * or null if it was not specified * * @return The specular component of the material */ FloatTuple getKs(); /** * Set the specular part of this material * * @param r The red component * @param g The green component * @param b The blue component */ void setKs(Float r, Float g, Float b); /** * Returns the name of the specular reflectivity map of the material, * or null if it has no such map. * * @return The name of the specular reflectivity map of the material */ String getMapKs(); /** * Set the specular reflectivity map name of this material * * @param mapKs The specular reflectivity map name of this material */ void setMapKs(String mapKs); /** * Returns the specular reflectivity map options of the material, * or null if it has no options. * * @return The specular reflectivity map {@link TextureOptions} */ TextureOptions getMapKsOptions(); /** * Set the specular reflectivity map {@link TextureOptions} * * @param options The specular reflectivity map {@link TextureOptions} */ void setMapKsOptions(TextureOptions options); //-------------------------------------------------------------------------- // Specular exponent (shininess) /** * Returns the shininess of the material. * * @return The shininess of the material. */ Float getNs(); /** * Set the shininess of this material, * or null if it was not specified * * @param ns The shininess of this material */ void setNs(Float ns); /** * Returns the name of the shininess map of the material, * or null if it has no map. * * @return The name of the shininess map of the material */ String getMapNs(); /** * Set the shininess map name of this material * * @param mapNs The shininess map name of this material */ void setMapNs(String mapNs); /** * Returns the shininess map options of the material, * or null if it has no options. * * @return The shininess map {@link TextureOptions} */ TextureOptions getMapNsOptions(); /** * Set the shininess map {@link TextureOptions} * * @param options The shininess map {@link TextureOptions} */ void setMapNsOptions(TextureOptions options); //-------------------------------------------------------------------------- // Opacity /** * Returns the opacity of the material, * or null if it was not specified * * @return The opacity of the material. */ Float getD(); /** * Set the opacity of the material * * @param d The opacity of the material */ void setD(Float d); /** * Returns whether dissolve (opacity) is dependent on the surface * orientation relative to the viewer * * @return The halo flag */ Boolean isHalo(); /** * Set the halo flag * * @param halo The halo flag */ void setHalo(Boolean halo); /** * Returns the name of the opacity map of the material, * or null if it has no map. * * @return The name of the opacity map of the material */ String getMapD(); /** * Set the opacity map name of this material * * @param mapD The opacity map name of this material */ void setMapD(String mapD); /** * Returns the opacity map options of the material, * or null if it has no options. * * @return The opacity map {@link TextureOptions} */ TextureOptions getMapDOptions(); /** * Set the opacity map {@link TextureOptions} * * @param options The opacity map {@link TextureOptions} */ void setMapDOptions(TextureOptions options); //-------------------------------------------------------------------------- // Bump /** * Returns the name of the bump map of the material, * or null if it has no map. * * @return The name of the bump map of the material */ String getBump(); /** * Set the bump map name of this material * * @param bump The bump map name of this material */ void setBump(String bump); /** * Returns the bump map options of the material, * or null if it has no options. * * @return The bump map {@link TextureOptions} */ TextureOptions getBumpOptions(); /** * Set the bump map {@link TextureOptions} * * @param options The bump map {@link TextureOptions} */ void setBumpOptions(TextureOptions options); //-------------------------------------------------------------------------- // Disp (displacement) /** * Returns the name of the displacement map of the material, * or null if it has no map. * * @return The name of the displacement map of the material */ String getDisp(); /** * Set the displacement map name of this material * * @param disp The displacement map name of this material */ void setDisp(String disp); /** * Returns the displacement map options of the material, * or null if it has no options. * * @return The displacement map {@link TextureOptions} */ TextureOptions getDispOptions(); /** * Set the displacement map {@link TextureOptions} * * @param options The displacement map {@link TextureOptions} */ void setDispOptions(TextureOptions options); //-------------------------------------------------------------------------- // Decal /** * Returns the name of the decal map of the material, * or null if it has no map. * * @return The name of the decal map of the material */ String getDecal(); /** * Set the decal map name of this material * * @param decal The decal map name of this material */ void setDecal(String decal); /** * Returns the decal map options of the material, * or null if it has no options. * * @return The decal map {@link TextureOptions} */ TextureOptions getDecalOptions(); /** * Set the decal map {@link TextureOptions} * * @param options The decal map {@link TextureOptions} */ void setDecalOptions(TextureOptions options); //-------------------------------------------------------------------------- // Refl (reflection) /** * Returns the list of {@link TextureOptions} objects for the reflection * maps of the material. This will never be null, but may * be an empty list if no reflection maps have been defined. * * @return The reflection map {@link TextureOptions} */ List getReflOptions(); //========================================================================== // PBR parameters: //-------------------------------------------------------------------------- // Roughness /** * Returns the roughness component of the material, * or null if it was not specified * * @return The roughness component of the material */ Float getPr(); /** * Set the roughness part of this material * * @param pr The roughness */ void setPr(Float pr); /** * Returns the name of the roughness map of the material, * or null if it has no such map. * * @return The name of the roughness map of the material */ String getMapPr(); /** * Set the roughness map name of this material * * @param mapPr The roughness map name of this material */ void setMapPr(String mapPr); /** * Returns the roughness map options of the material, * or null if it has no options. * * @return The roughness map {@link TextureOptions} */ TextureOptions getMapPrOptions(); /** * Set the roughness map {@link TextureOptions} * * @param options The roughness map {@link TextureOptions} */ void setMapPrOptions(TextureOptions options); //-------------------------------------------------------------------------- // Metallic /** * Returns the metallic component of the material, * or null if it was not specified * * @return The metallic component of the material */ Float getPm(); /** * Set the metallic part of this material * * @param pm The metallic part */ void setPm(Float pm); /** * Returns the name of the metallic map of the material, * or null if it has no such map. * * @return The name of the metallic map of the material */ String getMapPm(); /** * Set the metallic map name of this material * * @param mapPm The metallic map name of this material */ void setMapPm(String mapPm); /** * Returns the metallic map options of the material, * or null if it has no options. * * @return The metallic map {@link TextureOptions} */ TextureOptions getMapPmOptions(); /** * Set the metallic map {@link TextureOptions} * * @param options The metallic map {@link TextureOptions} */ void setMapPmOptions(TextureOptions options); //-------------------------------------------------------------------------- // Sheen /** * Returns the sheen component of the material, * or null if it was not specified * * @return The sheen component of the material */ Float getPs(); /** * Set the sheen part of this material * * @param ps The sheen part */ void setPs(Float ps); /** * Returns the name of the sheen map of the material, * or null if it has no such map. * * @return The name of the sheen map of the material */ String getMapPs(); /** * Set the sheen map name of this material * * @param mapPs The sheen map name of this material */ void setMapPs(String mapPs); /** * Returns the sheen map options of the material, * or null if it has no options. * * @return The sheen map {@link TextureOptions} */ TextureOptions getMapPsOptions(); /** * Set the sheen map {@link TextureOptions} * * @param options The sheen map {@link TextureOptions} */ void setMapPsOptions(TextureOptions options); //-------------------------------------------------------------------------- // Pc (clearcoat thickness) /** * Returns the clearcoat thickness of the material, * or null if it was not specified * * @return The clearcoat thickness */ Float getPc(); /** * Set the clearcoat thickness of the material * * @param pc The clearcoat thickness */ void setPc(Float pc); //-------------------------------------------------------------------------- // Pcr (clearcoat roughness) /** * Returns the clearcoat roughness of the material, * or null if it was not specified * * @return The clearcoat roughness */ Float getPcr(); /** * Set the clearcoat roughness of the material * * @param pcr The clearcoat roughness */ void setPcr(Float pcr); //-------------------------------------------------------------------------- // Emissive /** * Returns the emissive component of the material, * or null if it was not specified * * @return The emissive component of the material */ FloatTuple getKe(); /** * Set the emissive part of this material * * @param r The red component * @param g The green component * @param b The blue component */ void setKe(Float r, Float g, Float b); /** * Returns the name of the emissive map of the material, * or null if it has no such map. * * @return The name of the emissive map of the material */ String getMapKe(); /** * Set the emissive map name of this material * * @param mapKe The emissive map name of this material */ void setMapKe(String mapKe); /** * Returns the emissive map options of the material, * or null if it has no options. * * @return The emissive map {@link TextureOptions} */ TextureOptions getMapKeOptions(); /** * Set the emissive map {@link TextureOptions} * * @param options The emissive map {@link TextureOptions} */ void setMapKeOptions(TextureOptions options); //-------------------------------------------------------------------------- // aniso (anisotropy) /** * Returns the anisotropy of the material, * or null if it was not specified * * @return The anisotropy */ Float getAniso(); /** * Set the anisotropy of the material * * @param aniso The anisotropy */ void setAniso(Float aniso); //-------------------------------------------------------------------------- // anisor (anisotropy rotation) /** * Returns the anisotropy rotation of the material, * or null if it was not specified * * @return The anisotropy rotation */ Float getAnisor(); /** * Set the anisotropy rotation of the material * * @param anisor The anisotropy rotation */ void setAnisor(Float anisor); //-------------------------------------------------------------------------- // Normal /** * Returns the name of the normal map of the material, * or null if it has no such map. * * @return The name of the normal map of the material */ String getNorm(); /** * Set the normal map name of this material * * @param norm The normal map name of this material */ void setNorm(String norm); /** * Returns the normal map options of the material, * or null if it has no options. * * @return The normal map {@link TextureOptions} */ TextureOptions getNormOptions(); /** * Set the normal map {@link TextureOptions} * * @param options The normal map {@link TextureOptions} */ void setNormOptions(TextureOptions options); } ================================================ FILE: src/main/java/de/javagl/obj/MtlReader.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Queue; /** * A class that may read MTL data, and return the materials as a * list of {@link Mtl} objects. */ public class MtlReader { /** * Read the MTL data from the given stream, and return * it as {@link Mtl} objects. * The caller is responsible for closing the given stream. * * @param inputStream The stream to read from. * @return The list of Mtl object. * @throws IOException If an IO error occurs */ public static List read(InputStream inputStream) throws IOException { BufferedReader reader = new BufferedReader( new InputStreamReader(inputStream)); return readImpl(reader); } /** * Read the MTL data from the given reader, and return * it as {@link Mtl} objects. * The caller is responsible for closing the given reader. * * @param reader The reader to read from. * @return The list of Mtl object. * @throws IOException If an IO error occurs */ public static List read(Reader reader) throws IOException { if (reader instanceof BufferedReader) { return readImpl((BufferedReader)reader); } return readImpl(new BufferedReader(reader)); } /** * Read the MTL data from the given reader, and return * it as {@link Mtl} objects. * The caller is responsible for closing the given reader. * * @param reader The reader to read from. * @return The list of Mtl object. * @throws IOException If an IO error occurs */ private static List readImpl(BufferedReader reader) throws IOException { List mtlList = new ArrayList<>(); Mtl currentMtl = null; while(true) { String line = reader.readLine(); if(line == null) { break; } line = line.trim(); //System.out.println("read line: "+line); // Combine lines that have been broken boolean finished = false; while(line.endsWith("\\")) { line = line.substring(0, line.length() - 2); String nextLine = reader.readLine(); if (nextLine == null) { finished = true; break; } line += " " + nextLine; } if (finished) { break; } line = line.trim(); if (line.startsWith("newmtl")) { String name = line.substring("newmtl".length()).trim(); currentMtl = new DefaultMtl(name); mtlList.add(currentMtl); } else if (!line.startsWith("#") && !line.isEmpty()) { if (currentMtl == null) { throw new IOException( "Missing newmtl statement before " + line); } processLine(currentMtl, line); } } return mtlList; } /** * Process one (trimmed) line that is part of a newmtl * material definition, and write the result into the given {@link Mtl}. * * @param mtl The {@link Mtl} * @param line The line * @throws IOException If an IO error occurs */ private static void processLine(Mtl mtl, String line) throws IOException { Queue tokens = new LinkedList<>(Arrays.asList(line.split("[ \t\n\r\f]+"))); String command = tokens.poll(); // Illumination mode if (command.equalsIgnoreCase("illum")) { int value = Utils.parseInt(tokens.poll()); mtl.setIllum(value); } // Color values: R, and optional G and B if (command.equalsIgnoreCase("Ka")) { Float[] values = Utils.parseFloats(tokens, 3); mtl.setKa(values[0], values[1], values[2]); } else if (command.equalsIgnoreCase("Kd")) { Float[] values = Utils.parseFloats(tokens, 3); mtl.setKd(values[0], values[1], values[2]); } else if (command.equalsIgnoreCase("Ks")) { Float[] values = Utils.parseFloats(tokens, 3); mtl.setKs(values[0], values[1], values[2]); } else if (command.equalsIgnoreCase("Tf")) { Float[] values = Utils.parseFloats(tokens, 3); mtl.setTf(values[0], values[1], values[2]); } // Color values for PBR else if (command.equalsIgnoreCase("Ke")) { Float[] values = Utils.parseFloats(tokens, 3); mtl.setKe(values[0], values[1], values[2]); } // Single float values else if (command.equalsIgnoreCase("Tr")) { float value = Utils.parseFloat(tokens.poll()); mtl.setD(1.0f - value); } else if (command.equalsIgnoreCase("sharpness")) { float value = Utils.parseFloat(tokens.poll()); mtl.setSharpness(value); } else if (command.equalsIgnoreCase("d")) { String token = tokens.peek(); if ("-halo".equals(token)) { mtl.setHalo(true); tokens.poll(); } float value = Utils.parseFloat(tokens.poll()); mtl.setD(value); } else if (command.equalsIgnoreCase("Ni")) { float value = Utils.parseFloat(tokens.poll()); mtl.setNi(value); } else if (command.equalsIgnoreCase("Ns")) { float value = Utils.parseFloat(tokens.poll()); mtl.setNs(value); } // Single float values for PBR else if (command.equalsIgnoreCase("Pr")) { float value = Utils.parseFloat(tokens.poll()); mtl.setPr(value); } else if (command.equalsIgnoreCase("Pm")) { float value = Utils.parseFloat(tokens.poll()); mtl.setPm(value); } else if (command.equalsIgnoreCase("Ps")) { float value = Utils.parseFloat(tokens.poll()); mtl.setPs(value); } else if (command.equalsIgnoreCase("Pc")) { float value = Utils.parseFloat(tokens.poll()); mtl.setPc(value); } else if (command.equalsIgnoreCase("Pcr")) { float value = Utils.parseFloat(tokens.poll()); mtl.setPcr(value); } else if (command.equalsIgnoreCase("aniso")) { float value = Utils.parseFloat(tokens.poll()); mtl.setAniso(value); } else if (command.equalsIgnoreCase("anisor")) { float value = Utils.parseFloat(tokens.poll()); mtl.setAnisor(value); } // Texture map definitions else { readTextureMap(mtl, command, tokens); } } /** * Process the line of an MTL file that is supposed to contain a * texture map definition, and write the resulting texture * information into the given {@link Mtl}.
*
* The texture definition will be determined from the given command, * which may, for example, be "map_Ka" or * "refl" * * @param mtl The {@link Mtl} * @param command The command at the beginning of the line * @param tokens The tokens that have been created from the line * @throws IOException If an IO error occurs */ private static void readTextureMap( Mtl mtl, String command, Queue tokens) throws IOException { if (command.equalsIgnoreCase("map_Ka")) { mtl.setMapKaOptions(readTextureOptions(tokens)); } else if (command.equalsIgnoreCase("map_Kd")) { mtl.setMapKdOptions(readTextureOptions(tokens)); } else if (command.equalsIgnoreCase("map_Ks")) { mtl.setMapKsOptions(readTextureOptions(tokens)); } else if (command.equalsIgnoreCase("map_d")) { mtl.setMapDOptions(readTextureOptions(tokens)); } else if (command.equalsIgnoreCase("map_Ns")) { mtl.setMapNsOptions(readTextureOptions(tokens)); } else if (command.equalsIgnoreCase("bump") || command.equalsIgnoreCase("map_bump")) { mtl.setBumpOptions(readTextureOptions(tokens)); } else if (command.equalsIgnoreCase("disp")) { mtl.setDispOptions(readTextureOptions(tokens)); } else if (command.equalsIgnoreCase("decal")) { mtl.setDecalOptions(readTextureOptions(tokens)); } else if (command.equalsIgnoreCase("refl")) { TextureOptions refl = readTextureOptions(tokens); mtl.getReflOptions().add(refl); } // Texture map definitions for PBR else if (command.equalsIgnoreCase("map_Pr")) { mtl.setMapPrOptions(readTextureOptions(tokens)); } else if (command.equalsIgnoreCase("map_Pm")) { mtl.setMapPmOptions(readTextureOptions(tokens)); } else if (command.equalsIgnoreCase("map_Ps")) { mtl.setMapPsOptions(readTextureOptions(tokens)); } else if (command.equalsIgnoreCase("map_Ke")) { mtl.setMapKeOptions(readTextureOptions(tokens)); } else if (command.equalsIgnoreCase("norm")) { mtl.setNormOptions(readTextureOptions(tokens)); } } /** * Process the tokens in the given queue and construct a * {@link TextureOptions} object from them * * @param tokens The input token * @return The {@link TextureOptions} * @throws IOException If an IO error occurs */ static TextureOptions readTextureOptions(Queue tokens) throws IOException { DefaultTextureOptions textureOptions = new DefaultTextureOptions(); while (!tokens.isEmpty()) { String optionName = tokens.poll(); if (optionName.equalsIgnoreCase("-blendu")) { boolean value = Utils.parseBoolean(tokens.poll()); textureOptions.setBlendu(value); } else if (optionName.equalsIgnoreCase("-blendv")) { boolean value = Utils.parseBoolean(tokens.poll()); textureOptions.setBlendv(value); } else if (optionName.equalsIgnoreCase("-boost")) { float value = Utils.parseFloat(tokens.poll()); textureOptions.setBoost(value); } else if (optionName.equalsIgnoreCase("-cc")) { boolean value = Utils.parseBoolean(tokens.poll()); textureOptions.setCc(value); } else if (optionName.equalsIgnoreCase("-mm")) { float base = Utils.parseFloat(tokens.poll()); float gain = Utils.parseFloat(tokens.poll()); textureOptions.setMm(base, gain); } else if (optionName.equalsIgnoreCase("-o")) { Float[] values = Utils.parseFloats(tokens, 3); textureOptions.setO(values[0], values[1], values[2]); } else if (optionName.equalsIgnoreCase("-s")) { Float[] values = Utils.parseFloats(tokens, 3); textureOptions.setS(values[0], values[1], values[2]); } else if (optionName.equalsIgnoreCase("-t")) { Float[] values = Utils.parseFloats(tokens, 3); textureOptions.setT(values[0], values[1], values[2]); } else if (optionName.equalsIgnoreCase("-texres")) { float value = Utils.parseFloat(tokens.poll()); textureOptions.setTexres(value); } else if (optionName.equalsIgnoreCase("-clamp")) { boolean value = Utils.parseBoolean(tokens.poll()); textureOptions.setClamp(value); } else if (optionName.equalsIgnoreCase("-bm")) { float value = Utils.parseFloat(tokens.poll()); textureOptions.setBm(value); } else if (optionName.equalsIgnoreCase("-imfchan")) { String value = tokens.poll(); textureOptions.setImfchan(value); } else if (optionName.equalsIgnoreCase("-type")) { String value = tokens.poll(); textureOptions.setType(value); } else { textureOptions.setFileName(optionName); } } return textureOptions; } /** * Private constructor to prevent instantiation */ private MtlReader() { // Private constructor to prevent instantiation } } ================================================ FILE: src/main/java/de/javagl/obj/MtlWriter.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.List; /** * A class that may write {@link Mtl} objects into an MTL file */ public class MtlWriter { /** * Write the given {@link Mtl} objects to the given stream. The caller * is responsible for closing the stream. * * @param mtls The {@link Mtl} objects * @param outputStream The stream to write to * @throws IOException If an IO error occurs */ public static void write( Iterable mtls, OutputStream outputStream) throws IOException { OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); write(mtls, outputStreamWriter); } /** * Write the given {@link Mtl} objects to the given writer. The caller * is responsible for closing the writer. * * @param mtls The {@link Mtl} objects * @param writer The writer to write to * @throws IOException If an IO error occurs */ public static void write( Iterable mtls, Writer writer) throws IOException { for (Mtl mtl : mtls) { write(mtl, writer); } } /** * Write the given {@link Mtl} to the given writer * * @param mtl The {@link Mtl} * @param writer The writer * @throws IOException If an IO error occurs */ private static void write(Mtl mtl, Writer writer) throws IOException { writer.write(createString(mtl)); writer.flush(); } /** * Create the string representation of the given {@link Mtl}, as it * is written into an MTL file * * @param mtl The {@link Mtl} * @return The string representation */ private static String createString(Mtl mtl) { StringBuilder sb = new StringBuilder("newmtl "); sb.append(mtl.getName()).append("\n"); append(sb, "illum", mtl.getIllum(), "\n"); append(sb, "Ns", mtl.getNs(), "\n"); append(sb, "Ni", mtl.getNi(), "\n"); Float opacity = mtl.getD(); if (opacity != null) { sb.append("d").append(" "); if (Boolean.TRUE.equals(mtl.isHalo())) { sb.append("-halo").append(" "); } sb.append(opacity); sb.append("\n"); } appendTuple(sb, "Ka", mtl.getKa(), "\n"); appendTuple(sb, "Kd", mtl.getKd(), "\n"); appendTuple(sb, "Ks", mtl.getKs(), "\n"); appendTuple(sb, "Tf", mtl.getTf(), "\n"); append(sb, "sharpness", mtl.getSharpness(), "\n"); appendTextureOptions(sb, "map_Ka", mtl.getMapKaOptions()); appendTextureOptions(sb, "map_Kd", mtl.getMapKdOptions()); appendTextureOptions(sb, "map_Ks", mtl.getMapKsOptions()); appendTextureOptions(sb, "map_Ns", mtl.getMapNsOptions()); appendTextureOptions(sb, "map_d", mtl.getMapDOptions()); appendTextureOptions(sb, "bump", mtl.getBumpOptions()); appendTextureOptions(sb, "disp", mtl.getDispOptions()); appendTextureOptions(sb, "decal", mtl.getDecalOptions()); List refls = mtl.getReflOptions(); for (TextureOptions refl : refls) { appendTextureOptions(sb, "refl", refl); } // PBR parameters append(sb, "Pr", mtl.getPr(), "\n"); appendTextureOptions(sb, "map_Pr", mtl.getMapPrOptions()); append(sb, "Pm", mtl.getPm(), "\n"); appendTextureOptions(sb, "map_Pm", mtl.getMapPmOptions()); append(sb, "Ps", mtl.getPs(), "\n"); appendTextureOptions(sb, "map_Ps", mtl.getMapPsOptions()); append(sb, "Pc", mtl.getPc(), "\n"); append(sb, "Pcr", mtl.getPcr(), "\n"); appendTuple(sb, "Ke", mtl.getKe(), "\n"); appendTextureOptions(sb, "map_Ke", mtl.getMapKeOptions()); append(sb, "aniso", mtl.getAniso(), "\n"); append(sb, "anisor", mtl.getAnisor(), "\n"); appendTextureOptions(sb, "norm", mtl.getNormOptions()); return sb.toString(); } /** * Append the given {@link TextureOptions} to the given string builder, * if they are not null * * @param sb The string builder * @param key The key * @param options The {@link TextureOptions} */ private static void appendTextureOptions( StringBuilder sb, String key, TextureOptions options) { if (options != null) { sb.append(key).append(" "); sb.append(createString(options)).append("\n"); } } /** * Create the string representation for the given {@link TextureOptions}, * as a single line that may be written to the MTL file * * @param options The {@link TextureOptions} * @return The string representation */ static String createString(TextureOptions options) { StringBuilder sb = new StringBuilder(); append(sb, "-blendu", options.isBlendu(), " "); append(sb, "-blendv", options.isBlendv(), " "); append(sb, "-boost", options.getBoost(), " "); appendTuple(sb, "-mm", options.getMm(), " "); appendTuple(sb, "-o", options.getO(), " "); appendTuple(sb, "-s", options.getS(), " "); appendTuple(sb, "-t", options.getT(), " "); append(sb, "-texres", options.getTexres(), " "); append(sb, "-clamp", options.isClamp(), " "); append(sb, "-bm", options.getBm(), " "); append(sb, "-imfchan", options.getImfchan(), " "); append(sb, "-type", options.getType(), " "); sb.append(options.getFileName()); return sb.toString(); } /** * Append the given key-value mapping to the given string builder, if * the given value is not null * * @param sb The string builder * @param key The key * @param value The value * @param separator The separator to append after the value */ private static void append( StringBuilder sb, String key, Object value, String separator) { if (value != null) { sb.append(key).append(" "); sb.append(value); sb.append(separator); } } /** * Append the given key-value mapping to the given string builder, if * the given value is not null * * @param sb The string builder * @param key The key * @param value The value * @param separator The separator to append after the value */ private static void append( StringBuilder sb, String key, Boolean value, String separator) { if (value != null) { sb.append(key).append(" "); if (value) { sb.append("on"); } else { sb.append("off"); } sb.append(separator); } } /** * Append the given key-value mapping to the given string builder, if * the given value is not null * * @param sb The string builder * @param key The key * @param value The value * @param separator The separator to append after the value */ private static void appendTuple( StringBuilder sb, String key, FloatTuple value, String separator) { if (value != null) { sb.append(key).append(" "); sb.append(FloatTuples.createString(value)); sb.append(separator); } } /** * Private constructor to prevent instantiation */ private MtlWriter() { // Private constructor to prevent instantiation } } ================================================ FILE: src/main/java/de/javagl/obj/Mtls.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; /** * Methods to create {@link Mtl} instances */ public class Mtls { /** * Creates a new default {@link Mtl} * * @param name The name of the material * @return The {@link Mtl} */ public static Mtl create(String name) { return new DefaultMtl(name); } /** * Private constructor to prevent instantiation */ private Mtls() { // Private constructor to prevent instantiation } } ================================================ FILE: src/main/java/de/javagl/obj/Obj.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; /** * An in-memory representation of an OBJ file. This interface is only * a combination of a {@link ReadableObj} and a {@link WritableObj}.
*
* An {@link Obj} may be created with the {@link Objs#create()} method. * The {@link ObjReader#read(java.io.InputStream)} method may be used to * create an {@link Obj} from an OBJ file. */ public interface Obj extends ReadableObj, WritableObj { // No additional methods } ================================================ FILE: src/main/java/de/javagl/obj/ObjData.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; /** * Methods to obtain the data from {@link ReadableObj}s as plain arrays * or buffers */ public class ObjData { //========================================================================= // Number of vertices /** * Returns an array containing the number of vertices of the faces * of the given {@link ReadableObj} * * @param obj The {@link ReadableObj} * @return The array containing the number of vertices of the faces */ private static int[] getNumFaceVertices(ReadableObj obj) { int[] numVerticesOfFaces = new int[obj.getNumFaces()]; for(int i = 0; i < obj.getNumFaces(); i++) { ObjFace face = obj.getFace(i); numVerticesOfFaces[i] = face.getNumVertices(); } return numVerticesOfFaces; } /** * Returns the sum of all numbers of vertices of all faces in the given * {@link ReadableObj}. If the given {@link ReadableObj} only contains * triangles, this will be the same as obj.getNumFaces() * 3. * * @param obj The {@link ReadableObj} * @return The number of face vertex indices */ public static int getTotalNumFaceVertices(ReadableObj obj) { return sum(getNumFaceVertices(obj)); } /** * Returns the sum of the elements of the given array * * @param array The array * @return The sum of the elements of the given array */ private static int sum(int[] array) { int sum = 0; for (int i : array) { sum += i; } return sum; } //========================================================================= // Vertex indices /** * Returns the vertex indices from the faces of the given * {@link ReadableObj} as an array.
*
* This method will compute the * {@link #getTotalNumFaceVertices(ReadableObj) number of face vertex * indices} and return an array with this size. If the number of vertices * per face is known and equal for all faces, * {@link #getFaceVertexIndices(ReadableObj,int)} may be used instead. * * @param obj The obj * @return The face vertex indices */ public static int[] getFaceVertexIndicesArray(ReadableObj obj) { int[] array = new int[getTotalNumFaceVertices(obj)]; getFaceVertexIndices(obj, IntBuffer.wrap(array)); return array; } /** * Returns the vertex indices from the faces of the given * {@link ReadableObj} as direct IntBuffer. The position * of the returned buffer will be 0, and its limit and * capacity will match the stored data.
*
* This method will compute the * {@link #getTotalNumFaceVertices(ReadableObj) number of face vertex * indices} and return a buffer with this size. If the number of vertices * per face is known and equal for all faces, * {@link #getFaceVertexIndices(ReadableObj,int)} may be used instead. * * @param obj The obj * @return The face vertex indices */ public static IntBuffer getFaceVertexIndices(ReadableObj obj) { IntBuffer buffer = createDirectIntBuffer(getTotalNumFaceVertices(obj)); getFaceVertexIndices(obj, buffer); buffer.position(0); return buffer; } /** * Returns the vertex indices from the faces of the given * {@link ReadableObj} as an array.
*
* This method assumes that all faces have the given number of vertices * * @param obj The {@link ReadableObj} * @param numVerticesPerFace The number of vertices per face * @return The face vertex indices */ public static int[] getFaceVertexIndicesArray( ReadableObj obj, int numVerticesPerFace) { int[] array = new int[obj.getNumFaces() * numVerticesPerFace]; getFaceVertexIndices(obj, IntBuffer.wrap(array)); return array; } /** * Returns the vertex indices from the faces of the given * {@link ReadableObj} as direct IntBuffer. The position * of the returned buffer will be 0, and its limit and * capacity will match the stored data.
*
* This method assumes that all faces have the given number of vertices * * @param obj The {@link ReadableObj} * @param numVerticesPerFace The number of vertices per face * @return The face vertex indices */ public static IntBuffer getFaceVertexIndices( ReadableObj obj, int numVerticesPerFace) { IntBuffer buffer = createDirectIntBuffer( obj.getNumFaces() * numVerticesPerFace); getFaceVertexIndices(obj, buffer); buffer.position(0); return buffer; } /** * Stores the vertex indices of the faces of the given * {@link ReadableObj} in the given buffer. The position * of the given buffer will be advanced accordingly.
*
* This method assumes that the given buffer is sufficiently large * to store all indices. The required size may be computed with * {@link #getTotalNumFaceVertices(ReadableObj)}, or, if the number * of vertices per face is known and equal for all faces, with * n = obj.getNumFaces() * numVerticesPerFace * * @param obj The {@link ReadableObj} * @param target The buffer that will store the result * @throws BufferOverflowException If the buffer can not store the result */ public static void getFaceVertexIndices( ReadableObj obj, IntBuffer target) { for(int i = 0; i < obj.getNumFaces(); i++) { ObjFace face = obj.getFace(i); for (int j=0; j *
* This method will compute the * {@link #getTotalNumFaceVertices(ReadableObj) number of face vertices} * and return an array with this size. If the number of vertices per * face is known and equal for all faces, * {@link #getFaceTexCoordIndices(ReadableObj,int)} may be used instead. * * @param obj The obj * @return The face texCoord indices */ public static int[] getFaceTexCoordIndicesArray(ReadableObj obj) { int[] array = new int[getTotalNumFaceVertices(obj)]; getFaceTexCoordIndices(obj, IntBuffer.wrap(array)); return array; } /** * Returns the texCoord indices from the faces of the given * {@link ReadableObj} as a direct IntBuffer. The position * of the returned buffer will be 0, and its limit and * capacity will match the stored data.
*
* This method will compute the * {@link #getTotalNumFaceVertices(ReadableObj) number of face vertices} * and return a buffer with this size. If the number of vertices per * face is known and equal for all faces, * {@link #getFaceTexCoordIndices(ReadableObj,int)} may be used instead. * * @param obj The obj * @return The face texCoord indices */ public static IntBuffer getFaceTexCoordIndices(ReadableObj obj) { IntBuffer buffer = createDirectIntBuffer(getTotalNumFaceVertices(obj)); getFaceTexCoordIndices(obj, buffer); buffer.position(0); return buffer; } /** * Returns the texCoord indices from the faces of the given * {@link ReadableObj} as an array.
*
* This method assumes that all faces have the given number of vertices.
*
* This method assumes that the faces contain texture coordinate indices. * * @param obj The {@link ReadableObj} * @param numVerticesPerFace The number of vertices per face * @return The face texCoord indices */ public static int[] getFaceTexCoordIndicesArray( ReadableObj obj, int numVerticesPerFace) { int[] array = new int[obj.getNumFaces() * numVerticesPerFace]; getFaceTexCoordIndices(obj, IntBuffer.wrap(array)); return array; } /** * Returns the texCoord indices from the faces of the given * {@link ReadableObj} as a direct IntBuffer. The position * of the returned buffer will be 0, and its limit and * capacity will match the stored data.
*
* This method assumes that all faces have the given number of vertices.
*
* This method assumes that the faces contain texture coordinate indices. * * @param obj The {@link ReadableObj} * @param numVerticesPerFace The number of vertices per face * @return The face texCoord indices */ public static IntBuffer getFaceTexCoordIndices( ReadableObj obj, int numVerticesPerFace) { IntBuffer buffer = createDirectIntBuffer( obj.getNumFaces() * numVerticesPerFace); getFaceTexCoordIndices(obj, buffer); buffer.position(0); return buffer; } /** * Stores the texCoord indices of the faces of the given * {@link ReadableObj} in the given buffer. The position * of the given buffer will be advanced accordingly.
*
* This method assumes that the given buffer is sufficiently large * to store all indices. The required size may be computed with * {@link #getTotalNumFaceVertices(ReadableObj)}, or, if the number of * vertices per face is known and equal for all faces, with * n = obj.getNumFaces() * numVerticesPerFace.
*
* This method assumes that the faces contain texture coordinate indices. * * * @param obj The {@link ReadableObj} * @param target The buffer that will store the result * @throws BufferOverflowException If the buffer can not store the result */ public static void getFaceTexCoordIndices( ReadableObj obj, IntBuffer target) { for(int i = 0; i < obj.getNumFaces(); i++) { ObjFace face = obj.getFace(i); for (int j=0; j *
* This method will compute the * {@link #getTotalNumFaceVertices(ReadableObj) number of face vertices} * and return an array with this size. If the number of vertices per * face is known and equal for all faces, * {@link #getFaceNormalIndices(ReadableObj,int)} may be used instead. * * @param obj The {@link ReadableObj} * @return The face normal indices */ public static int[] getFaceNormalIndicesArray(ReadableObj obj) { int[] array = new int[getTotalNumFaceVertices(obj)]; getFaceNormalIndices(obj, IntBuffer.wrap(array)); return array; } /** * Returns the normal indices from the faces of the given * {@link ReadableObj} as a direct IntBuffer. The position * of the returned buffer will be 0, and its limit and * capacity will match the stored data.
*
* This method will compute the * {@link #getTotalNumFaceVertices(ReadableObj) number of face vertices} * and return a buffer with this size. If the number of vertices per * face is known and equal for all faces, * {@link #getFaceNormalIndices(ReadableObj,int)} may be used instead. * * @param obj The {@link ReadableObj} * @return The face normal indices */ public static IntBuffer getFaceNormalIndices(ReadableObj obj) { IntBuffer buffer = createDirectIntBuffer(getTotalNumFaceVertices(obj)); getFaceNormalIndices(obj, buffer); buffer.position(0); return buffer; } /** * Returns the normal indices from the faces of the given * {@link ReadableObj} as an array.
*
* This method assumes that all faces have the given number of vertices.
*
* This method assumes that the faces contain normal indices. * * @param obj The {@link ReadableObj} * @param numVerticesPerFace The number of vertices per face * @return The face normal indices */ public static int[] getFaceNormalIndicesArray( ReadableObj obj, int numVerticesPerFace) { int[] array = new int[obj.getNumFaces() * numVerticesPerFace]; getFaceNormalIndices(obj, IntBuffer.wrap(array)); return array; } /** * Returns the normal indices from the faces of the given * {@link ReadableObj} as a direct IntBuffer. The position * of the returned buffer will be 0, and its limit and * capacity will match the stored data.
*
* This method assumes that all faces have the given number of vertices.
*
* This method assumes that the faces contain normal indices. * * @param obj The {@link ReadableObj} * @param numVerticesPerFace The number of vertices per face * @return The face normal indices */ public static IntBuffer getFaceNormalIndices( ReadableObj obj, int numVerticesPerFace) { IntBuffer buffer = createDirectIntBuffer( obj.getNumFaces() * numVerticesPerFace); getFaceNormalIndices(obj, buffer); buffer.position(0); return buffer; } /** * Stores the normal indices of the faces of the given * {@link ReadableObj} in the given buffer. The position * of the given buffer will be advanced accordingly.
*
* This method assumes that the given buffer is sufficiently large * to store all indices. The required size may be computed with * {@link #getTotalNumFaceVertices(ReadableObj)}, or, if the number of * vertices per face is known and equal for all faces, with * n = obj.getNumFaces() * numVerticesPerFace.
*
* This method assumes that the faces contain texture coordinate indices. * * * @param obj The {@link ReadableObj} * @param target The buffer that will store the result * @throws BufferOverflowException If the buffer can not store the result */ public static void getFaceNormalIndices( ReadableObj obj, IntBuffer target) { for(int i = 0; i < obj.getNumFaces(); i++) { ObjFace face = obj.getFace(i); for (int j=0; jobj.getNumVertices() * 3. The position * of the given buffer will be advanced accordingly. * * @param obj The obj * @param target The target that will store the result * @throws BufferOverflowException If the target can not store the result */ public static void getVertices( ReadableObj obj, FloatBuffer target) { for(int i = 0; i < obj.getNumVertices(); i++) { FloatTuple tuple = obj.getVertex(i); target.put(tuple.getX()); target.put(tuple.getY()); target.put(tuple.getZ()); } } //========================================================================= // TexCoords /** * Returns all texture coordinates of the given * {@link ReadableObj} as an array. * * @param obj The {@link ReadableObj} * @param dimensions The dimensions that are assumed for the coordinates * @return The resulting array */ public static float[] getTexCoordsArray(ReadableObj obj, int dimensions) { return getTexCoordsArray(obj, dimensions, false); } /** * Returns all texture coordinates of the given * {@link ReadableObj} as an array. * * @param obj The {@link ReadableObj} * @param dimensions The dimensions that are assumed for the coordinates * @param flipY Whether the texture coordinates should be flipped * vertically. This means that the y-coordinates (at dimension index 1) * will be replaced with 1.0f - y. Most image loaders provide * image data with the first pixel being the upper left pixel of * the image. But OpenGL glTexImage2D calls expect the first * pixel to be the lower left. Flipping the texture coordinates * by passing flipY=true to this method allows to compensate * for this mismatch. * @return The resulting array */ public static float[] getTexCoordsArray( ReadableObj obj, int dimensions, boolean flipY) { float[] array = new float[obj.getNumTexCoords() * dimensions]; getTexCoords(obj, FloatBuffer.wrap(array), dimensions, flipY); return array; } /** * Returns all texture coordinates of the given * {@link ReadableObj} as direct FloatBuffer. The position * of the returned buffer will be 0, and its limit and * capacity will match the stored data. * * @param obj The {@link ReadableObj} * @param dimensions The dimensions that are assumed for the coordinates * @return The resulting buffer */ public static FloatBuffer getTexCoords(ReadableObj obj, int dimensions) { return getTexCoords(obj, dimensions, false); } /** * Returns all texture coordinates of the given * {@link ReadableObj} as direct FloatBuffer. The position * of the returned buffer will be 0, and its limit and * capacity will match the stored data. * * @param obj The {@link ReadableObj} * @param dimensions The dimensions that are assumed for the coordinates * @param flipY Whether the texture coordinates should be flipped * vertically. This means that the y-coordinates (at dimension index 1) * will be replaced with 1.0f - y. Most image loaders provide * image data with the first pixel being the upper left pixel of * the image. But OpenGL glTexImage2D calls expect the first * pixel to be the lower left. Flipping the texture coordinates * by passing flipY=true to this method allows to compensate * for this mismatch. * @return The resulting buffer */ public static FloatBuffer getTexCoords( ReadableObj obj, int dimensions, boolean flipY) { FloatBuffer buffer = createDirectFloatBuffer(obj.getNumTexCoords() * dimensions); getTexCoords(obj, buffer, dimensions, flipY); buffer.position(0); return buffer; } /** * Stores the texture coordinates of the given {@link ReadableObj} * in the given buffer. The position of the target will be increased by * obj.getNumTexCoords() * dimensions. The position * of the given buffer will be advanced accordingly. * * @param obj The {@link ReadableObj} * @param target The target that will store the result * @param dimensions The dimensions that are assumed for the coordinates * @throws BufferOverflowException If the target can not store the result */ public static void getTexCoords( ReadableObj obj, FloatBuffer target, int dimensions) { getTexCoords(obj, target, dimensions, false); } /** * Stores the texture coordinates of the given {@link ReadableObj} * in the given buffer. The position of the target will be increased by * obj.getNumTexCoords() * dimensions. The position * of the given buffer will be advanced accordingly. * * @param obj The {@link ReadableObj} * @param target The target that will store the result * @param dimensions The dimensions that are assumed for the coordinates * @param flipY Whether the texture coordinates should be flipped * vertically. This means that the y-coordinates (at dimension index 1) * will be replaced with 1.0f - y. Most image loaders provide * image data with the first pixel being the upper left pixel of * the image. But OpenGL glTexImage2D calls expect the first * pixel to be the lower left. Flipping the texture coordinates * by passing flipY=true to this method allows to compensate * for this mismatch. * @throws BufferOverflowException If the target can not store the result */ public static void getTexCoords( ReadableObj obj, FloatBuffer target, int dimensions, boolean flipY) { if (flipY) { for(int i = 0; i < obj.getNumTexCoords(); i++) { FloatTuple tuple = obj.getTexCoord(i); for (int j=0; jobj.getNumNormals() * 3. The position * of the given buffer will be advanced accordingly. * * @param obj The {@link ReadableObj} * @param target The buffer that will store the result * @throws BufferOverflowException If the target can not store the result */ public static void getNormals( ReadableObj obj, FloatBuffer target) { for(int i = 0; i < obj.getNumNormals(); i++) { FloatTuple tuple = obj.getNormal(i); target.put(tuple.getX()); target.put(tuple.getY()); target.put(tuple.getZ()); } } /** * Convert the given IntBuffer to a (direct) ShortBuffer, by casting all * elements to short.
*
* Note that these casts will be unchecked. If there is any value in the * input buffer that is larger than the maximum value that a * signed short can represent, then the * short value will become negative. The resulting buffer * will then still be valid for passing it to OpenGL when the index * mode is GL_UNSIGNED_SHORT, because the bitwise * representation is the same. When one of the integer values is larger * than the value that can be represented with an unsigned * short, then the resulting indices will be invalid * and cause rendering artifacts. * * @param intBuffer The IntBuffer * @return The ShortBuffer */ public static ShortBuffer convertToShortBuffer(IntBuffer intBuffer) { ShortBuffer shortBuffer = createDirectShortBuffer(intBuffer.capacity()); for (int i = 0; i < intBuffer.capacity(); i++) { shortBuffer.put(i, (short) intBuffer.get()); } return shortBuffer; } /** * Create a direct IntBuffer with the given size * * @param size The size * @return The IntBuffer */ private static IntBuffer createDirectIntBuffer(int size) { return ByteBuffer.allocateDirect(size * 4) .order(ByteOrder.nativeOrder()) .asIntBuffer(); } /** * Create a direct ShortBuffer with the given size * * @param size The size * @return The ShortBuffer */ private static ShortBuffer createDirectShortBuffer(int size) { return ByteBuffer.allocateDirect(size * 2) .order(ByteOrder.nativeOrder()) .asShortBuffer(); } /** * Create a direct FloatBuffer with the given size * * @param size The size * @return The FloatBuffer */ private static FloatBuffer createDirectFloatBuffer(int size) { return ByteBuffer.allocateDirect(size * 4) .order(ByteOrder.nativeOrder()) .asFloatBuffer(); } /** * Private constructor to prevent instantiation */ private ObjData() { // Private constructor to prevent instantiation } } ================================================ FILE: src/main/java/de/javagl/obj/ObjFace.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; /** * A single face that is stored in an OBJ file */ public interface ObjFace { /** * Returns the number of vertices this face consists of. * * @return The number of vertices this face consists of. */ int getNumVertices(); /** * Returns whether this face contains texture coordinate indices * * @return Whether this face contains texture coordinate indices */ boolean containsTexCoordIndices(); /** * Returns whether this face contains normal indices * * @return Whether this face contains normal indices */ boolean containsNormalIndices(); /** * Returns the index of the vertex with the given number. * The index that is returned will be ZERO-based, in contrast * to the ONE-based storage in the OBJ file. * * @param number The number of the vertex * @return The index of the vertex. * @throws IndexOutOfBoundsException If the given number is negative * or not smaller than {@link #getNumVertices()} */ int getVertexIndex(int number); /** * Returns the index of the texture coordinate with the given number. * The index that is returned will be ZERO-based, in contrast * to the ONE-based storage in the OBJ file. * * @param number The number of the texture coordinate * @return The index of the texture coordinate. * @throws IndexOutOfBoundsException If the given number is negative * or not smaller than {@link #getNumVertices()} */ int getTexCoordIndex(int number); /** * Returns the index of the normal with the given number. * The index that is returned will be ZERO-based, in contrast * to the ONE-based storage in the OBJ file. * * @param number The number of the normal * @return The index of the normal. * @throws IndexOutOfBoundsException If the given number is negative * or not smaller than {@link #getNumVertices()} */ int getNormalIndex(int number); } ================================================ FILE: src/main/java/de/javagl/obj/ObjFaceParser.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.io.IOException; import java.util.Arrays; /** * A class for reading the index data for an {@link ObjFace} from an * 'f'-line that was read from an OBJ file */ final class ObjFaceParser { /** * The initial size for the index buffers */ private static final int INITIAL_BUFFER_SIZE = 6; /** * Buffer for vertex indices */ private int[] vertexIndexBuffer = new int[INITIAL_BUFFER_SIZE]; /** * Buffer for texture coordinates */ private int[] texCoordIndexBuffer = new int[INITIAL_BUFFER_SIZE]; /** * Buffer normal indices */ private int[] normalIndexBuffer = new int[INITIAL_BUFFER_SIZE]; /** * Flag whether texture coordinates have been found during the last * call to {@link #parse(String)} */ private boolean foundTexCoordIndices = false; /** * Flag whether normal indices have been found during the last * call to {@link #parse(String)} */ private boolean foundNormalIndices = false; /** * Counter for the vertices */ private int vertexCounter = 0; /** * Index in the input array */ private int idx = 0; /** * The input array to parse */ private char[] lineData; /** * Parse the given 'f'-line that was read from an OBJ file * * @param line The line * @throws IOException If the given line can not be parsed */ void parse(String line) throws IOException { parseLine(line); } /** * Returns a new array containing the vertex indices that have * been parsed during the last call to * {@link #parse(String)}. * * @return The vertex indices */ int[] getVertexIndices() { return Arrays.copyOf(vertexIndexBuffer, vertexCounter); } /** * Returns a new array containing the texCoord indices that have * been parsed during the last call to * {@link #parse(String)}, or null * if no texture coordinate indices have been read * * @return The texCord indices */ int[] getTexCoordIndices() { if (foundTexCoordIndices) { return Arrays.copyOf(texCoordIndexBuffer, vertexCounter); } return null; } /** * Returns a new array containing the normal indices that have * been parsed during the last call to * {@link #parse(String)}, or null * if no normal indices have been read * * @return The normal indices */ int[] getNormalIndices() { if(foundNormalIndices) { return Arrays.copyOf(normalIndexBuffer, vertexCounter); } return null; } /** * Parse the Face from the given line
* f v0/vt0/vn0 ... vN/vtN/vnN
* * @param line String * @throws IOException If the line could not be parsed */ void parseLine(String line) throws IOException { foundTexCoordIndices = false; foundNormalIndices = false; vertexCounter = 0; idx = 0; lineData = line.toCharArray(); skipSpaces(); if(endOfInput()) { // Empty line return; } // Read the leading 'f' or 'F' if(lineData[idx] != 'f' && lineData[idx] != 'F') { throw new IOException( "Expected 'f' or 'F', but found '" + lineData[idx] + " in \""+line+"\""); } idx++; // Read all vertex v/vt/vn triples int count = 0; while(true) { skipSpaces(); if(endOfInput()) { break; } // Read the vertex index int vertexIndex = parseNonzeroInt(); if (vertexIndex == 0) { throw new IOException( "Could not read vertex index in \""+line+"\""); } if (count >= vertexIndexBuffer.length) { vertexIndexBuffer = Arrays.copyOf(vertexIndexBuffer, count+1); texCoordIndexBuffer = Arrays.copyOf(texCoordIndexBuffer, count+1); normalIndexBuffer = Arrays.copyOf(normalIndexBuffer, count+1); } if (vertexIndex != 0) { vertexIndexBuffer[count] = vertexIndex; } vertexCounter = count + 1; skipSpaces(); if(endOfInput()) { break; } // Read the texCoord index if(lineData[idx] == '/') { idx++; skipSpaces(); if(endOfInput()) { throw new IOException( "Unexpected end of input after '/' " + "in \""+line+"\""); } int texCoordIndex = parseNonzeroInt(); // It is not an error if texCoordIndex == 0: The indices // may be given as "1//2", and thus not contain an // texCoordIndex if(texCoordIndex != 0) { texCoordIndexBuffer[count] = texCoordIndex; foundTexCoordIndices = true; } skipSpaces(); if(endOfInput()) { break; } // Read the normal index if(lineData[idx] == '/') { idx++; skipSpaces(); if(endOfInput()) { throw new IOException( "Unexpected end of input after '/' " + "in \""+line+"\""); } int normalIndex = parseNonzeroInt(); if(normalIndex == 0) { throw new IOException( "Could not read normal index from \""+line+"\""); } foundNormalIndices = true; if (normalIndex != 0) { normalIndexBuffer[count] = normalIndex; } } } count++; } } /** * Returns whether the end of the input was reached * * @return 'true' iff the end of the input was reached */ private boolean endOfInput() { return idx >= lineData.length; } /** * Skips all space characters until the first non-space character is * found or the end of the input is reached */ private void skipSpaces() { while (!endOfInput() && lineData[idx] == ' ') { idx++; } } /** * Returns the next int in the input, or 0 if no int could be * read * * @return The next int in the input */ private int parseNonzeroInt() { int parsedInt = 0; boolean negate = false; if (lineData[idx] == '-') { negate = true; idx++; skipSpaces(); if(endOfInput()) { return 0; } } if(lineData[idx] >= '0' && lineData[idx] <= '9') { parsedInt = (lineData[idx] - '0'); idx++; while(!endOfInput() && lineData[idx] >= '0' && lineData[idx] <= '9') { parsedInt *= 10; parsedInt += (lineData[idx] - '0'); idx++; } } return negate ? -parsedInt : parsedInt; } } ================================================ FILE: src/main/java/de/javagl/obj/ObjFaces.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; /** * Methods for creating {@link ObjFace} instances */ public class ObjFaces { /** * Create a copy of the given face.
* * @param face The input face * @return The copy */ static DefaultObjFace create(ObjFace face) { int[] v = new int[face.getNumVertices()]; int[] vt = null; int[] vn = null; for(int i = 0; i < face.getNumVertices(); i++) { v[i] = face.getVertexIndex(i); } if(face.containsTexCoordIndices()) { vt = new int[face.getNumVertices()]; for(int i = 0; i < face.getNumVertices(); i++) { vt[i] = face.getTexCoordIndex(i); } } if(face.containsNormalIndices()) { vn = new int[face.getNumVertices()]; for(int i = 0; i < face.getNumVertices(); i++) { vn[i] = face.getNormalIndex(i); } } DefaultObjFace result = new DefaultObjFace(v, vt, vn); return result; } /** * Create a copy of the given face, adding the given offsets to the * respective indices. If the given face does not contain texture * coordinate or normal indices, then the respective offsets will * be ignored.
* * @param face The input face * @param verticesOffset The offset for the vertex indices * @param texCoordsOffset The offset for the texture coordinate indices * @param normalsOffset The offset for the normal indices * @return The copy */ static DefaultObjFace createWithOffsets(ObjFace face, int verticesOffset, int texCoordsOffset, int normalsOffset) { int[] v = new int[face.getNumVertices()]; int[] vt = null; int[] vn = null; for(int i = 0; i < face.getNumVertices(); i++) { v[i] = face.getVertexIndex(i) + verticesOffset; } if(face.containsTexCoordIndices()) { vt = new int[face.getNumVertices()]; for(int i = 0; i < face.getNumVertices(); i++) { vt[i] = face.getTexCoordIndex(i) + texCoordsOffset; } } if(face.containsNormalIndices()) { vn = new int[face.getNumVertices()]; for(int i = 0; i < face.getNumVertices(); i++) { vn[i] = face.getNormalIndex(i) + normalsOffset; } } DefaultObjFace result = new DefaultObjFace(v, vt, vn); return result; } /** * Create a copy of the given face, using only the specified vertices * of the given face. * * @param face The input face * @param n The vertices to use * @return The copy */ static DefaultObjFace create(ObjFace face, int ... n) { int[] v = new int[n.length]; int[] vt = null; int[] vn = null; for(int i = 0; i < n.length; i++) { v[i] = face.getVertexIndex(n[i]); } if(face.containsTexCoordIndices()) { vt = new int[n.length]; for(int i = 0; i < n.length; i++) { vt[i] = face.getTexCoordIndex(n[i]); } } if(face.containsNormalIndices()) { vn = new int[n.length]; for(int i = 0; i < n.length; i++) { vn[i] = face.getNormalIndex(n[i]); } } DefaultObjFace result = new DefaultObjFace(v, vt, vn); return result; } /** * Create a face with the given indices. The texCoord indices and the * normal indices may be null. In any case, it is assumed * that all non-null arrays have equal length. References * to the given arrays will be stored internally, so they should not * be modified after they have been passed to this method. * * @param v The vertex indices * @param vt The texCoord indices * @param vn The normal indices * @return The face */ public static ObjFace create(int[] v, int[] vt, int[] vn) { return createDefault(v, vt, vn); } /** * Create a face with the given indices. The texCoord indices and the * normal indices may be null. In any case, it is assumed * that all non-null arrays have equal length. References * to the given arrays will be stored internally, so they should not * be modified after they have been passed to this method. * * @param v The vertex indices * @param vt The texCoord indices * @param vn The normal indices * @return The face */ static DefaultObjFace createDefault(int[] v, int[] vt, int[] vn) { DefaultObjFace result = new DefaultObjFace(v, vt, vn); return result; } /** * Returns the string for the given face that makes up one 'f' line * in an OBJ file * * @param face The face * @return The string for the face */ public static String createString(ObjFace face) { StringBuilder sb = new StringBuilder("f "); for(int i = 0; i < face.getNumVertices(); i++) { if (i > 0) { sb.append(" "); } sb.append(face.getVertexIndex(i) + 1); if(face.containsTexCoordIndices() || face.containsNormalIndices()) { sb.append("/"); } if(face.containsTexCoordIndices()) { sb.append(face.getTexCoordIndex(i) + 1); } if(face.containsNormalIndices()) { sb.append("/").append(face.getNormalIndex(i) + 1); } } return sb.toString(); } /** * Private constructor to prevent instantiation */ private ObjFaces() { // Private constructor to prevent instantiation } } ================================================ FILE: src/main/java/de/javagl/obj/ObjGroup.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; /** * Interface describing a single group of an OBJ file. This may either * be a geometry group that is identified by the 'g' token, * or a group that is implied by a common material, identified by the * 'usemtl' token. */ public interface ObjGroup { /** * Returns the name of this group. * * @return The name of this group. */ String getName(); /** * Returns the number of faces in this group. * * @return The number of faces in this group. */ int getNumFaces(); /** * Returns the face with the given index. * * @param index The index of the face * @return The face with the given index. * @throws IndexOutOfBoundsException If the index is negative or not * smaller than {@link #getNumFaces()} */ ObjFace getFace(int index); } ================================================ FILE: src/main/java/de/javagl/obj/ObjReader.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.StringTokenizer; /** * A class that may read OBJ data from a stream and * store the read data in an {@link WritableObj}. */ public class ObjReader { /** * Read the OBJ data from the given stream and return it as an {@link Obj}. * The caller is responsible for closing the given stream. * * @param inputStream The stream to read from * @return The {@link Obj} * @throws IOException If an IO error occurs */ public static Obj read(InputStream inputStream) throws IOException { return read(inputStream, Objs.create()); } /** * Read the OBJ data from the given stream and store the read * elements in the given {@link WritableObj}. * The caller is responsible for closing the given stream. * * @param The output type * @param inputStream The stream to read from * @param output The {@link WritableObj} to store the read data * @return The output * @throws IOException If an IO error occurs */ public static T read( InputStream inputStream, T output) throws IOException { BufferedReader reader = new BufferedReader( new InputStreamReader(inputStream, StandardCharsets.US_ASCII)); return readImpl(reader, output); } /** * Read the OBJ data from the given reader and return it as an {@link Obj}. * The caller is responsible for closing the given reader. * * @param reader The reader to read from * @return The {@link Obj} * @throws IOException If an IO error occurs */ public static Obj read(Reader reader) throws IOException { return read(reader, Objs.create()); } /** * Read the OBJ data from the given reader and store the read * elements in the given {@link WritableObj}. * The caller is responsible for closing the given reader. * * @param The output type * @param reader The reader to read from * @param output The {@link WritableObj} to store the read data * @return The output * @throws IOException If an IO error occurs */ public static T read( Reader reader, T output) throws IOException { if (reader instanceof BufferedReader) { return readImpl((BufferedReader)reader, output); } return readImpl(new BufferedReader(reader), output); } /** * Read the OBJ data from the given reader and store the read * elements in the given {@link WritableObj}. * The caller is responsible for closing the given reader. * * @param The output type * @param reader The reader to read from * @param output The {@link WritableObj} to store the read data * @return The output * @throws IOException If an IO error occurs */ private static T readImpl( BufferedReader reader, T output) throws IOException { ObjFaceParser objFaceParser = new ObjFaceParser(); int vertexCounter = 0; int texCoordCounter = 0; int normalCounter = 0; while(true) { String line = reader.readLine(); if(line == null) { break; } line = line.trim(); //System.out.println("read line: "+line); // Combine lines that have been broken boolean finished = false; while(line.endsWith("\\")) { line = line.substring(0, line.length() - 2); String nextLine = reader.readLine(); if (nextLine == null) { finished = true; break; } line += " " + nextLine; } if (finished) { break; } StringTokenizer st = new StringTokenizer(line); if(!st.hasMoreTokens()) { continue; } String identifier = st.nextToken().toLowerCase(); // v: Vertex coordinates switch (identifier) { case "v": output.addVertex(Utils.readFloatTuple(st)); vertexCounter++; break; // vt: Texture coordinates for a vertex case "vt": output.addTexCoord(Utils.readFloatTuple(st)); texCoordCounter++; break; // vn: Vertex normal case "vn": output.addNormal(Utils.readFloatTuple(st)); normalCounter++; break; // mtllib: Name of the MTL file case "mtllib": { String s = line.substring(6).trim(); //output.setMtlFileNames(readStrings(s)); // According to the OBJ specification, the "mtllib" keyword // may be followed by multiple file names, separated with // whitespaces: // "When you assign a material library using the Model // program, only one map library per .obj file is allowed. // You can assign multiple libraries using a text editor." // However, to avoid problems with file names that contain // whitespaces, only ONE file name is assumed here: output.setMtlFileNames(Collections.singleton(s)); break; } // usemtl: Material groups case "usemtl": String materialGroupName = line.substring(6).trim(); output.setActiveMaterialGroupName(materialGroupName); break; // g: Geometry groups case "g": { String s = line.substring(1).trim(); String[] groupNames = readStrings(s); output.setActiveGroupNames(Arrays.asList(groupNames)); break; } // f: A face definition case "f": objFaceParser.parse(line); int[] v = objFaceParser.getVertexIndices(); int[] vt = objFaceParser.getTexCoordIndices(); int[] vn = objFaceParser.getNormalIndices(); makeIndicesAbsolute(v, vertexCounter); makeIndicesAbsolute(vt, texCoordCounter); makeIndicesAbsolute(vn, normalCounter); output.addFace(ObjFaces.create(v, vt, vn)); break; } } return output; } /** * Convert the indices in the given array to be absolute (non-negative) * and zero-based. This means that negative values are made positive * by adding the given count, and positive values are decreased by one. * * @param array The array. If this is null, nothing will * be done * @param count The count */ private static void makeIndicesAbsolute(int[] array, int count) { if (array == null) { return; } for (int i=0; i tokens = new ArrayList<>(); while (st.hasMoreTokens()) { tokens.add(st.nextToken()); } return tokens.toArray(new String[tokens.size()]); } /** * Private constructor to prevent instantiation */ private ObjReader() { // Private constructor to prevent instantiation } } ================================================ FILE: src/main/java/de/javagl/obj/ObjSplitter.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2017 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * Package-private class that can split an OBJ into multiple parts, based * on its number of vertices. Many details about the behavior of this class * are intentionally not specified. */ class ObjSplitter { /** * The logger used in this class */ private static final Logger logger = Logger.getLogger(ObjSplitter.class.getName()); /** * The log level */ private static final Level level = Level.FINE; /** * A predicate interface, equivalent to java.util.functional.Predicate, * but defined here in order to target Java 1.7 * * @param The parameter type */ private interface Predicate { /** * Test the given object * * @param t The object * @return Whether this predicate applies to the object */ boolean test(T t); } /** * The predicate that will be used to check whether an OBJ should * be split. */ private final Predicate splitPredicate; /** * Creates a new instance that splits OBJs into parts that have at most * the given maximum number of vertices (if this is possible without * splitting faces) * * @param maxNumVertices The maximum number of vertices */ ObjSplitter(final int maxNumVertices) { splitPredicate = new Predicate() { @Override public boolean test(ReadableObj obj) { return obj.getNumVertices() > maxNumVertices; } }; } /** * Split the given OBJ into multiple parts, if it is necessary according * to the {@link #splitPredicate}. * * @param obj The input OBJ * @return The list of resulting OBJs */ List split(ReadableObj obj) { // If no splitting is necessary, just return a single OBJ that // is a copy of the input. if (!splitPredicate.test(obj)) { Obj singleObj = Objs.create(); ObjUtils.add(obj, singleObj); return Collections.singletonList(singleObj); } // Perform the initial split, and proceed splitting the // parts until none of them matches the splitPredicate List currentObjs = splitSingle(obj); boolean didSplit = currentObjs.size() > 1; while (didSplit) { didSplit = false; List nextObjs = new ArrayList<>(); for (Obj currentObj : currentObjs) { if (splitPredicate.test(currentObj)) { List parts = splitSingle(currentObj); nextObjs.addAll(parts); if (parts.size() > 1) { didSplit = true; } } else { nextObjs.add(currentObj); } } currentObjs = nextObjs; } return currentObjs; } /** * Split a single OBJ into two parts. Depending on the exact splitting * strategy, this may not be possible in certain corner cases. In * these cases, the returned list will contain only a single element. * * @param obj The input OBJ * @return The list containing the parts */ private static List splitSingle(ReadableObj obj) { logger.log(level, "Splitting OBJ with " + obj.getNumVertices() + " vertices"); Predicate facePredicate = computeFacePredicate(obj); List faces0 = new ArrayList<>(); List faces1 = new ArrayList<>(); for (int i = 0; i < obj.getNumFaces(); i++) { ObjFace face = obj.getFace(i); if (facePredicate.test(face)) { faces0.add(face); } else { faces1.add(face); } } // When there are faces that are basically equal, then geometric // splitting is not possible. In this case, just split the list // of faces into two halves. if (faces0.isEmpty()) { return split(obj, faces1); } else if (faces1.isEmpty()) { return split(obj, faces0); } logger.log(level, "Split OBJ with " + obj.getNumFaces() + " faces " + "into " + faces0.size() + " and " + faces1.size() + " faces"); Obj obj0 = ObjUtils.groupToObj(obj, asGroup(faces0), null); Obj obj1 = ObjUtils.groupToObj(obj, asGroup(faces1), null); return Arrays.asList(obj0, obj1); } /** * Split the given list of faces into two parts of approximately equal * size, and return the {@link Obj} instances that correspond to the * subsets of these faces in the given {@link Obj}. If the given * list contains only a single element, then a single-element list * will be returned. * * @param obj The input OBJ * @param allFaces The list of all faces * @return The list containing the parts */ private static List split(ReadableObj obj, List allFaces) { if (allFaces.size() <= 1) { Obj obj0 = ObjUtils.groupToObj(obj, asGroup(allFaces), null); return Arrays.asList(obj0); } int centerIndex = (allFaces.size() + 1) / 2; List faces0 = allFaces.subList(0, centerIndex); List faces1 = allFaces.subList(centerIndex, allFaces.size()); Obj obj0 = ObjUtils.groupToObj(obj, asGroup(faces0), null); Obj obj1 = ObjUtils.groupToObj(obj, asGroup(faces1), null); return Arrays.asList(obj0, obj1); } /** * Returns a new OBJ group that is a view on the given list * * @param faces The list of faces * @return The OBJ group */ private static ObjGroup asGroup(final List faces) { return new ObjGroup() { @Override public String getName() { return "default"; } @Override public int getNumFaces() { return faces.size(); } @Override public ObjFace getFace(int index) { return faces.get(index); } }; } /** * Compute a predicate that splits the faces of the given OBJ into two * parts. Further details are not specified. * * @param obj The OBJ * @return The predicate */ private static Predicate computeFacePredicate( final ReadableObj obj) { // Compute the projections of the centers of all faces along the axes float[] centersX = computeFaceCenters(obj, 0); float[] centersY = computeFaceCenters(obj, 1); float[] centersZ = computeFaceCenters(obj, 2); // Compute the mean of the projections along the axes final float meanX = arithmeticMean(centersX); final float meanY = arithmeticMean(centersY); final float meanZ = arithmeticMean(centersZ); // Compute the variances of the projections float varianceX = variance(centersX, meanX); float varianceY = variance(centersY, meanY); float varianceZ = variance(centersZ, meanZ); // Create a predicate that splits the faces based on their location // relative to the mean, along the axis with the largest variance if (varianceX >= varianceY && varianceX >= varianceZ) { return new Predicate() { @Override public boolean test(ObjFace objFace) { float faceCenterX = computeFaceCenter(obj, objFace, 0); return faceCenterX >= meanX; } }; } if (varianceY >= varianceX && varianceY >= varianceZ) { return new Predicate() { @Override public boolean test(ObjFace objFace) { float faceCenterY = computeFaceCenter(obj, objFace, 1); return faceCenterY >= meanY; } }; } return new Predicate() { @Override public boolean test(ObjFace objFace) { float faceCenterZ = computeFaceCenter(obj, objFace, 2); return faceCenterZ >= meanZ; } }; } /** * Compute the specified components of the center positions of the * faces of the given OBJ * * @param obj The OBJ * @param component The component, 0,1 or 2 for x, y or z. * @return The face centers */ private static float[] computeFaceCenters(ReadableObj obj, int component) { int n = obj.getNumFaces(); float[] result = new float[n]; for (int i = 0; i < n; i++) { ObjFace face = obj.getFace(i); result[i] = computeFaceCenter(obj, face, component); } return result; } /** * Compute the specified component of the center position of the given * OBJ face * * @param obj The OBJ * @param face The face * @param component The component, 0,1 or 2 for x, y or z. * @return The center */ private static float computeFaceCenter( ReadableObj obj, ObjFace face, int component) { float sum = 0; int n = face.getNumVertices(); for (int i = 0; i < n; i++) { int vertexIndex = face.getVertexIndex(i); FloatTuple vertex = obj.getVertex(vertexIndex); sum += vertex.get(component); } return sum / n; } /** * Returns the arithmetic mean of the given array * * @param array The input array * @return The mean */ private static float arithmeticMean(float[] array) { float sum = 0; for (float value : array) { sum += value; } return sum / array.length; } /** * Returns the bias-corrected sample variance of the given array. * * @param array The input array * @param mean The mean * @return The variance */ private static float variance(float[] array, float mean) { float variance = 0; for (float v : array) { double difference = v - mean; variance += difference * difference; } return variance / (array.length - 1); } } ================================================ FILE: src/main/java/de/javagl/obj/ObjSplitting.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2017 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Methods for splitting {@link ReadableObj} objects into multiple parts, * based on different criteria. */ public class ObjSplitting { /** * Split the given {@link ReadableObj} based on its groups. This will * create one {@link Obj} from each (non-empty) group in the * given input. * * @param obj The input {@link ReadableObj} * @return One {@link Obj} for each non-empty group of the given input */ public static Map splitByGroups(ReadableObj obj) { Map objs = new LinkedHashMap<>(); int numGroups = obj.getNumGroups(); for (int i = 0; i < numGroups; i++) { ObjGroup group = obj.getGroup(i); if (group.getNumFaces() > 0) { String groupName = group.getName(); Obj groupObj = ObjUtils.groupToObj(obj, group, null); objs.put(groupName, groupObj); } } return objs; } /** * Split the given {@link ReadableObj} based on its material groups. * This will create one {@link Obj} from each (non-empty) material * group in the given input.
*
* Note that if the given OBJ does not contain any material groups * (that is, when it does not refer to a material with a * usemtl directive), then the resulting map will * be empty.
*
* Faces that are not associated with any material group * will not be contained in the output. * * @param obj The input {@link ReadableObj} * @return The mapping from material group names (corresponding to the * usemtl directives in the input file) to the {@link Obj} * that represents this material group. */ public static Map splitByMaterialGroups(ReadableObj obj) { Map objs = new LinkedHashMap<>(); int numMaterialGroups = obj.getNumMaterialGroups(); for (int i = 0; i < numMaterialGroups; i++) { ObjGroup materialGroup = obj.getMaterialGroup(i); if (materialGroup.getNumFaces() > 0) { String materialGroupName = materialGroup.getName(); Obj materialGroupObj = ObjUtils.groupToObj(obj, materialGroup, null); objs.put(materialGroupName, materialGroupObj); } } return objs; } /** * Split the given {@link ReadableObj} into {@link Obj} instances based * on the given maximum number of vertices.
*
* Note that this method will not split any faces in the given * OBJ. So although the resulting parts will usually not have more * than the given maximum number of vertices, this cannot be guaranteed: * When there is a face in the input OBJ that has n vertices, * the resulting parts will have at most maxNumVertices + n - 1 * vertices. For example, if the given input contains only triangles, * then the parts will have at most maxNumVertices + 2 * vertices.
*
* Many details about the splitting process that is used internally are * intentionally not specified. This method is mainly intended for * splitting a large {@link Obj} into parts that it may be rendered in * environments that only allow a certain number of vertices (indices) * per rendered object. For example, in order to create parts whose * indices can be represented with an unsigned short value, * this method may be called with maxNumVertices=65000. * * @param obj The input {@link ReadableObj} * @param maxNumVertices The maximum number of vertices * @return One {@link Obj} for each part. * @throws IllegalArgumentException If the given number is smaller than 3. */ public static List splitByMaxNumVertices( ReadableObj obj, int maxNumVertices) { if (maxNumVertices < 3) { throw new IllegalArgumentException( "The given number of vertices must at least be 3"); } ObjSplitter splitter = new ObjSplitter(maxNumVertices); return splitter.split(obj); } /** * Private constructor to prevent instantiation */ private ObjSplitting() { // Private constructor to prevent instantiation } } ================================================ FILE: src/main/java/de/javagl/obj/ObjUtils.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; /** * Utility methods for handling {@link Obj}s.
*
* Unless otherwise noted, none of the parameters to these methods * may be null */ public class ObjUtils { /** * Convert the given {@link ReadableObj} into an {@link Obj} that has * a structure appropriate for rendering it with OpenGL: *
    *
  • {@link #triangulate(ReadableObj) Triangulate} it
  • *
  • * {@link #makeTexCoordsUnique(ReadableObj) * Make the texture coordinates unique} *
  • *
  • * {@link #makeNormalsUnique(ReadableObj) * Make the normals unique} *
  • *
  • * Make the result {@link #makeVertexIndexed(ReadableObj) * vertex-indexed} *
  • *
* * @param input The input {@link ReadableObj} * @return The resulting {@link Obj} */ public static Obj convertToRenderable(ReadableObj input) { return convertToRenderable(input, Objs.create()); } /** * Convert the given {@link ReadableObj} into an {@link Obj} that has * a structure appropriate for rendering it with OpenGL: *
    *
  • {@link #triangulate(ReadableObj) Triangulate} it
  • *
  • * {@link #makeTexCoordsUnique(ReadableObj) * Make the texture coordinates unique} *
  • *
  • * {@link #makeNormalsUnique(ReadableObj) * Make the normals unique} *
  • *
  • * Make the result {@link #makeVertexIndexed(ReadableObj) * vertex-indexed} *
  • *
* * @param The type of the output * @param input The input {@link ReadableObj} * @param output The output {@link WritableObj} * @return The given output */ public static T convertToRenderable( ReadableObj input, T output) { Obj obj = triangulate(input); obj = makeTexCoordsUnique(obj); obj = makeNormalsUnique(obj); return makeVertexIndexed(obj, output); } /** * Triangulates the given input {@link ReadableObj} and returns the * result.
*
* This method will simply subdivide faces with more than 3 vertices so * that all faces in the output will be triangles. * * @param input The input {@link ReadableObj} * @return The resulting {@link Obj} */ public static Obj triangulate(ReadableObj input) { return triangulate(input, Objs.create()); } /** * Triangulates the given input {@link ReadableObj} and stores the result * in the given {@link WritableObj}.
*
* This method will simply subdivide faces with more than 3 vertices so * that all faces in the output will be triangles. * * @param The type of the output * @param input The input {@link ReadableObj} * @param output The output {@link WritableObj} * @return The given output */ public static T triangulate( ReadableObj input, T output) { output.setMtlFileNames(input.getMtlFileNames()); addAll(input, output); for (int i=0; i *
* The vertexIndexMapping may be null. If it * is not null, vertexIndexMapping.get(i) * afterwards stores the index that vertex i had in the * input. * * @param input The input {@link ReadableObj} * @param inputGroup The group of the input * @param vertexIndexMapping The optional index mapping * @return The resulting {@link Obj} */ public static Obj groupToObj( ReadableObj input, ObjGroup inputGroup, List vertexIndexMapping) { return groupToObj(input, inputGroup, vertexIndexMapping, Objs.create()); } /** * Stores the given group of the given {@link ReadableObj} * in the given output {@link WritableObj}.
*
* The vertexIndexMapping may be null. If it * is not null, vertexIndexMapping.get(i) * afterwards stores the index that vertex i had in the * input. * * @param The type of the output * @param input The input {@link ReadableObj} * @param inputGroup The group of the input * @param vertexIndexMapping The optional index mapping * @param output The output {@link WritableObj} * @return The given output */ public static T groupToObj( ReadableObj input, ObjGroup inputGroup, List vertexIndexMapping, T output) { output.setMtlFileNames(input.getMtlFileNames()); // vertexIndexMap[i] contains the index that vertex i of the // original Obj will have in the output int[] vertexIndexMap = new int[input.getNumVertices()]; int[] texCoordIndexMap = new int[input.getNumTexCoords()]; int[] normalIndexMap = new int[input.getNumNormals()]; Arrays.fill(vertexIndexMap, -1); Arrays.fill(texCoordIndexMap, -1); Arrays.fill(normalIndexMap, -1); int vertexCounter = 0; int texCoordCounter = 0; int normalCounter = 0; for(int i = 0; i < inputGroup.getNumFaces(); i++) { // Clone the face info from the input ObjFace face = inputGroup.getFace(i); DefaultObjFace resultFace = ObjFaces.create(face); activateGroups(input, face, output); // The indices of the cloned face have to be adjusted, // so that they point to the correct vertices in the output for(int j = 0; j < face.getNumVertices(); j++) { int vertexIndex = face.getVertexIndex(j); if(vertexIndexMap[vertexIndex] == -1) { vertexIndexMap[vertexIndex] = vertexCounter; output.addVertex(input.getVertex(vertexIndex)); vertexCounter++; } resultFace.setVertexIndex(j, vertexIndexMap[vertexIndex]); } if(face.containsTexCoordIndices()) { for(int j = 0; j < face.getNumVertices(); j++) { int texCoordIndex = face.getTexCoordIndex(j); if(texCoordIndexMap[texCoordIndex] == -1) { texCoordIndexMap[texCoordIndex] = texCoordCounter; output.addTexCoord(input.getTexCoord(texCoordIndex)); texCoordCounter++; } resultFace.setTexCoordIndex( j, texCoordIndexMap[texCoordIndex]); } } if(face.containsNormalIndices()) { for(int j = 0; j < face.getNumVertices(); j++) { int normalIndex = face.getNormalIndex(j); if(normalIndexMap[normalIndex] == -1) { normalIndexMap[normalIndex] = normalCounter; output.addNormal(input.getNormal(normalIndex)); normalCounter++; } resultFace.setNormalIndex(j, normalIndexMap[normalIndex]); } } // Add the cloned face with the adjusted indices to the output output.addFace(resultFace); } // Compute the vertexIndexMapping, so that vertexIndexMapping.get(i) // afterwards stores the index that vertex i had in the input if(vertexIndexMapping != null) { for(int i = 0; i < vertexCounter; i++) { vertexIndexMapping.add(-1); } for(int i = 0; i < input.getNumVertices(); i++) { if(vertexIndexMap[i] != -1) { vertexIndexMapping.set(vertexIndexMap[i], i); } } } return output; } /** * Interface for accessing a property index (vertex, texture coordinate * or normal) from a {@link ReadableObj} */ private interface PropertyIndexAccessor { /** * Returns the index of the specified property from the given * {@link ReadableObj} * * @param input The {@link ReadableObj} * @param face The face * @param vertexNumber The number of the vertex in the face * @return The index of the property * @throws IndexOutOfBoundsException If the index is out of bounds */ int getPropertyIndex( ReadableObj input, ObjFace face, int vertexNumber); /** * Returns whether the given face has indices for the property * that may be queries with this accessor * * @param face The face * @return Whether the face has the property */ boolean hasProperty(ObjFace face); } /** * Ensures that two vertices with different texture coordinates are * actually two different vertices with different indices.
*
* Two faces may reference the same vertex in the OBJ file. But different * texture coordinates may be assigned to the same vertex in * both faces. The vertex that requires two different properties will be * duplicated in the output, and the indices in one face will be updated * appropriately.
*
* This process solely operates on the indices of the properties. * It will not check whether the value of two properties (with * different indices) are actually equal. * * @param input The input {@link ReadableObj} * @return The resulting {@link Obj} */ public static Obj makeTexCoordsUnique(ReadableObj input) { return makeTexCoordsUnique(input, null, Objs.create()); } /** * Ensures that two vertices with different texture coordinates are * actually two different vertices with different indices.
*
* Two faces may reference the same vertex in the OBJ file. But different * texture coordinates may be assigned to the same vertex in * both faces. The vertex that requires two different properties will be * duplicated in the output, and the indices in one face will be updated * appropriately.
*
* This process solely operates on the indices of the properties. * It will not check whether the value of two properties (with * different indices) are actually equal. *
* The indexMapping may be null. Otherwise it is assumed that * it already contains the index mapping that was obtained by a call * to {@link #groupToObj(ReadableObj, ObjGroup, List, WritableObj)}, and * this mapping will be updated appropriately. * * @param The type of the output * @param input The input {@link ReadableObj} * @param indexMapping The optional index mapping * @param output The output {@link WritableObj} * @return The given output */ public static T makeTexCoordsUnique( ReadableObj input, List indexMapping, T output) { PropertyIndexAccessor accessor = new PropertyIndexAccessor() { @Override public int getPropertyIndex( ReadableObj input, ObjFace face, int vertexNumber) { return face.getTexCoordIndex(vertexNumber); } @Override public boolean hasProperty(ObjFace face) { return face.containsTexCoordIndices(); } }; makePropertiesUnique(input, accessor, indexMapping, output); return output; } /** * Ensures that two vertices with different normals are * actually two different vertices with different indices.
*
* Two faces may reference the same vertex in the OBJ file. But different * normals may be assigned to the same vertex in * both faces. The vertex that requires two different properties will be * duplicated in the output, and the indices in one face will be updated * appropriately.
*
* This process solely operates on the indices of the properties. * It will not check whether the value of two properties (with * different indices) are actually equal. * * @param input The input {@link ReadableObj} * @return The resulting {@link Obj} */ public static Obj makeNormalsUnique(ReadableObj input) { return makeNormalsUnique(input, null, Objs.create()); } /** * Ensures that two vertices with different normals are * actually two different vertices with different indices.
*
* Two faces may reference the same vertex in the OBJ file. But different * normals may be assigned to the same vertex in * both faces. The vertex that requires two different properties will be * duplicated in the output, and the indices in one face will be updated * appropriately.
*
* This process solely operates on the indices of the properties. * It will not check whether the value of two properties (with * different indices) are actually equal. *
* The indexMapping may be null. Otherwise it is assumed that * it already contains the index mapping that was obtained by a call * to {@link #groupToObj(ReadableObj, ObjGroup, List, WritableObj)}, and * this mapping will be updated appropriately. * * @param The type of the output * @param input The input {@link ReadableObj} * @param indexMapping The optional index mapping * @param output The output {@link WritableObj} * @return The given output */ public static T makeNormalsUnique( ReadableObj input, List indexMapping, T output) { PropertyIndexAccessor accessor = new PropertyIndexAccessor() { @Override public int getPropertyIndex( ReadableObj input, ObjFace face, int vertexNumber) { return face.getNormalIndex(vertexNumber); } @Override public boolean hasProperty(ObjFace face) { return face.containsNormalIndices(); } }; makePropertiesUnique(input, accessor, indexMapping, output); return output; } /** * Ensures that two vertices with different properties are * actually two different vertices with different indices.
*
* Two faces may reference the same vertex in the OBJ file. But different * normals or texture coordinates may be assigned to the same vertex in * both faces. The vertex that requires two different properties will be * duplicated in the output, and the indices in one face will be updated * appropriately.
*
* This process solely operates on the indices of the properties. * It will not check whether the value of two properties (with * different indices) are actually equal. *
* The indexMapping may be null. Otherwise it is assumed that * it already contains the index mapping that was obtained by a call * to {@link #groupToObj(ReadableObj, ObjGroup, List, WritableObj)}, and * this mapping will be updated appropriately. * * @param input The input {@link ReadableObj} * @param propertyIndexAccessor The accessor for the property index * @param indexMapping The optional index mapping * @param output The output {@link WritableObj} */ private static void makePropertiesUnique( ReadableObj input, PropertyIndexAccessor propertyIndexAccessor, List indexMapping, WritableObj output) { output.setMtlFileNames(input.getMtlFileNames()); addAll(input, output); int[] usedPropertyIndices = new int[input.getNumVertices()]; Arrays.fill(usedPropertyIndices, -1); List extendedVertices = new ArrayList<>(); for(int i = 0; i < input.getNumFaces(); i++) { ObjFace inputFace = input.getFace(i); activateGroups(input, inputFace, output); ObjFace outputFace = inputFace; if (propertyIndexAccessor.hasProperty(inputFace)) { DefaultObjFace extendedOutputFace = null; for(int j = 0; j < outputFace.getNumVertices(); j++) { int vertexIndex = outputFace.getVertexIndex(j); int propertyIndex = propertyIndexAccessor.getPropertyIndex( input, outputFace, j); // Check if the property of the vertex with the current // index already has been used, and it is not equal to // the property that it has in the current face if(usedPropertyIndices[vertexIndex] != -1 && usedPropertyIndices[vertexIndex] != propertyIndex) { FloatTuple vertex = input.getVertex(vertexIndex); // Add the vertex which has multiple properties once // more to the output, and update all indices that // now have to point to the "new" vertex int extendedVertexIndex = input.getNumVertices() + extendedVertices.size(); extendedVertices.add(vertex); output.addVertex(vertex); if (extendedOutputFace == null) { extendedOutputFace = ObjFaces.create(inputFace); } extendedOutputFace.setVertexIndex( j, extendedVertexIndex); if(indexMapping != null) { int indexInObj = indexMapping.get(vertexIndex); indexMapping.add(indexInObj); } } else { usedPropertyIndices[vertexIndex] = propertyIndex; } } if (extendedOutputFace != null) { outputFace = extendedOutputFace; } } output.addFace(outputFace); } } /** * Add all vertices, texture coordinates and normals of the given * {@link ReadableObj} to the given {@link WritableObj} * * @param input The {@link ReadableObj} * @param output The {@link WritableObj} */ private static void addAll(ReadableObj input, WritableObj output) { for (int i=0; i *
* Note that this may cause new groups or material groups to be created * in the output OBJ. If the output OBJ contains group names or material * group names that also exist in the input OBJ, then the elements of the * input OBJ will be added to these existing groups. * * @param input The {@link ReadableObj} * @param output The target {@link Obj} */ public static void add(ReadableObj input, Obj output) { int verticesOffset = output.getNumVertices(); for (int i=0; i *
* Note that the result may contain ambiguous texture coordinates and * normals, unless they have been made unique in the input before with * {@link #makeTexCoordsUnique(ReadableObj)} and * {@link #makeNormalsUnique(ReadableObj)}, respectively. * * @param input The input {@link ReadableObj} * @return The resulting {@link Obj} */ public static Obj makeVertexIndexed(ReadableObj input) { return makeVertexIndexed(input, Objs.create()); } /** * Converts the given {@link ReadableObj} data into data that uses the * same indices for vertices, texture coordinates and normals, and * stores the result in the given {@link WritableObj}.
*
* Note that the result may contain ambiguous texture coordinates and * normals, unless they have been made unique in the input before with * {@link #makeTexCoordsUnique(ReadableObj)} and * {@link #makeNormalsUnique(ReadableObj)}, respectively. * * @param The type of the output * @param input The input {@link ReadableObj} * @param output The output {@link WritableObj} * @return The given output */ public static T makeVertexIndexed( ReadableObj input, T output) { output.setMtlFileNames(input.getMtlFileNames()); for (int i=0; i activatedGroupNames = input.getActivatedGroupNames(face); if (activatedGroupNames != null) { output.setActiveGroupNames( activatedGroupNames); } String activatedMaterialGroupName = input.getActivatedMaterialGroupName(face); if (activatedMaterialGroupName != null) { output.setActiveMaterialGroupName( activatedMaterialGroupName); } } /** * Create a multi-line, formatted string containing information about * the given {@link ReadableObj}. This method is solely intended for * debugging. It should not be considered as part of the public API. * The exact output is not specified. * * @param obj The {@link ReadableObj} * @return The info string */ public static String createInfoString(ReadableObj obj) { StringBuilder sb = new StringBuilder(); sb.append("Obj:"+"\n"); sb.append(" mtlFileNames : "+obj.getMtlFileNames()+"\n"); sb.append(" numVertices : "+obj.getNumVertices()+"\n"); sb.append(" numTexCoords : "+obj.getNumTexCoords()+"\n"); sb.append(" numNormals : "+obj.getNumNormals()+"\n"); sb.append(" numFaces : "+obj.getNumFaces()+"\n"); sb.append(" numGroups : "+obj.getNumGroups()+"\n"); for (int i=0; i mtlFileNames = input.getMtlFileNames(); if (!mtlFileNames.isEmpty()) { writer.write("mtllib "); for (int i=0; i 0) { writer.write(" "); } writer.write(mtlFileNames.get(i)); } writer.write("\n"); } // Write the vertex- texture coordinate and normal data for(int i = 0; i < input.getNumVertices(); i++) { FloatTuple vertex = input.getVertex(i); writer.write( "v "+FloatTuples.createString(vertex) + "\n"); } for(int i = 0; i < input.getNumTexCoords(); i++) { FloatTuple texCoord = input.getTexCoord(i); writer.write( "vt "+FloatTuples.createString(texCoord) + "\n"); } for(int i = 0; i < input.getNumNormals(); i++) { FloatTuple normal = input.getNormal(i); writer.write( "vn "+FloatTuples.createString(normal) + "\n"); } boolean skipWritingDefaultGroup = true; for(int i = 0; i < input.getNumFaces(); i++) { ObjFace face = input.getFace(i); Set activatedGroupNames = input.getActivatedGroupNames(face); if (activatedGroupNames != null) { boolean isDefaultGroup = activatedGroupNames.equals( Collections.singleton("default")); if (!skipWritingDefaultGroup || !isDefaultGroup) { writer.write("g "); for (String activatedGroupName : activatedGroupNames) { writer.write(activatedGroupName); writer.write(" "); } writer.write("\n"); } skipWritingDefaultGroup = false; } String activatedMaterialGroupName = input.getActivatedMaterialGroupName(face); if (activatedMaterialGroupName != null) { writer.write( "usemtl " + activatedMaterialGroupName + "\n"); } String faceString = ObjFaces.createString(face); writer.write(faceString + "\n"); } writer.flush(); } /** * Private constructor to prevent instantiation */ private ObjWriter() { // Private constructor to prevent instantiation } } ================================================ FILE: src/main/java/de/javagl/obj/Objs.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.nio.FloatBuffer; import java.nio.IntBuffer; /** * Methods to create {@link Obj} instances */ public class Objs { /** * Creates a new default {@link Obj} * * @return The {@link Obj} */ public static Obj create() { return new DefaultObj(); } /** * Create an {@link Obj} from the given (single-) indexed triangle data.
*
* This method will not perform any sanity checks. It silently assumes * that the given indices are valid indices for the given buffers. *
* The texture coordinates and normals may be null.
*
* The buffers that are not null will be accessed using * the absolute access methods, up to their capacity. This means * that the position of the given buffers will not be affected. * * @param indices The indices. Three consecutive elements of this buffer * are assumed to form one triangle. * @param vertices The vertices * @param texCoords The texture coordinates, assumed to be 2D. * @param normals The normals * @return The {@link Obj} * @throws NullPointerException If the indices or vertices are * null. */ public static Obj createFromIndexedTriangleData( IntBuffer indices, FloatBuffer vertices, FloatBuffer texCoords, FloatBuffer normals) { int numTriangles = indices.capacity() / 3; int numVertices = vertices.capacity() / 3; Obj obj = Objs.create(); for (int i=0; i *
    *
  • Vertices
  • *
  • Texture coordinates
  • *
  • Normals
  • *
  • Faces
  • *
  • Groups
  • *
  • Material groups
  • *
*/ public interface ReadableObj { /** * Returns the number of vertices in the Obj. * * @return The number of vertices in the Obj. */ int getNumVertices(); /** * Returns the vertex with the given index. Note that the index * is 0-based, in contrast to the 1-based indices of the * actual OBJ file. * * @param index The index of the vertex * @return The vertex with the given index * @throws IndexOutOfBoundsException If the index is negative or not * smaller than {@link #getNumVertices()} */ FloatTuple getVertex(int index); /** * Returns the number of texture coordinates in the Obj. * * @return The number of texture coordinates in the Obj. */ int getNumTexCoords(); /** * Returns the texture coordinate with the given index. Note that the * is 0-based, in contrast to the 1-based indices of the * actual OBJ file. * * @param index The index of the texture coordinate * @return The texture coordinate with the given index * @throws IndexOutOfBoundsException If the index is negative or not * smaller than {@link #getNumTexCoords()} */ FloatTuple getTexCoord(int index); /** * Returns the number of normals in the Obj. * * @return The number of normals in the Obj. */ int getNumNormals(); /** * Returns the normal with the given index. Note that the index * is 0-based, in contrast to the 1-based indices of the * actual OBJ file. * * @param index The index of the normal * @return The normal with the given index * @throws IndexOutOfBoundsException If the index is negative or not * smaller than {@link #getNumNormals()} */ FloatTuple getNormal(int index); /** * Returns the number of faces in the Obj. * * @return The number of faces in the Obj. */ int getNumFaces(); /** * Returns the face with the given index. * * @param index The index of the face. * @return The face with the given index * @throws IndexOutOfBoundsException If the index is negative or not * smaller than {@link #getNumFaces()} */ ObjFace getFace(int index); /** * Returns an unmodifiable set containing the names of the groups that * are activated with the given face. If the groups that are * activated with the given face are the same as for the previous face, * then null will be returned. * * @param face The face * @return The names of the groups that are activated with the * given face */ Set getActivatedGroupNames(ObjFace face); /** * Returns the name of the material group that is activated with the * given face. If the material group that is activated with the given * face is the same as for the previous face, then null * will be returned. * * @param face The face * @return The name of the material group that is activated with the * given face */ String getActivatedMaterialGroupName(ObjFace face); /** * Returns the number of groups in this Obj. * * @return The number of groups in this Obj. */ int getNumGroups(); /** * Returns the group with the given index. * * @param index The index of the group. * @return The group with the given index. * @throws IndexOutOfBoundsException If the index is negative or not * smaller than {@link #getNumGroups()} */ ObjGroup getGroup(int index); /** * Returns the group with the given name, or null if * there is no such group in this Obj. * * @param name The name of the group. * @return The group with the given name. */ ObjGroup getGroup(String name); /** * Returns the number of material groups in this Obj. * * @return The number of material groups in this Obj. */ int getNumMaterialGroups(); /** * Returns the material group with the given index. * * @param index The index of the material group. * @return The material group with the given index. * @throws IndexOutOfBoundsException If the index is negative or not * smaller than {@link #getNumMaterialGroups()} */ ObjGroup getMaterialGroup(int index); /** * Returns the material group with the given name, or null if * there is no such group in this Obj. * * @param name The name of the material group. * @return The material group with the given name. */ ObjGroup getMaterialGroup(String name); /** * Returns an unmodifiable list containing the names of the MTL file * that are associated with this OBJ, as they have been read from * the mtllib line. * This may be an empty list, if no MTL file names have been read. * * @return The names of the MTL files. */ List getMtlFileNames(); } ================================================ FILE: src/main/java/de/javagl/obj/TextureOptions.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; /** * Interface for the texture options that are part of a map definition * of an {@link Mtl}. For details about the semantics of the properties * in this interface, refer to the MTL specification. */ public interface TextureOptions { /** * Returns the file name of the texture * * @return The file name */ String getFileName(); /** * Set the file name of the texture * * @param fileName The file name */ void setFileName(String fileName); /** * Returns the horizontal texture blending state (-blendu), * or null if it was not specified * * @return Whether horizontal blending is enabled */ Boolean isBlendu(); /** * Set the horizontal texture blending state * * @param blendu The blending state */ void setBlendu(Boolean blendu); /** * Returns the vertical texture blending state (-blendv), * or null if it was not specified * * @return Whether vertical blending is enabled */ Boolean isBlendv(); /** * Set the vertical texture blending state * * @param blendv The blending state */ void setBlendv(Boolean blendv); /** * Returns the color correction state (-cc), * or null if it was not specified * * @return The color correction state */ Boolean isCc(); /** * Set the color correction state * * @param cc The color correction state */ void setCc(Boolean cc); /** * Returns the boost mip-map sharpness value (-boost), * or null if it was not specified * * @return The boost mip-map sharpness. */ Float getBoost(); /** * Set the mip-map boost value * * @param boost The boost value */ void setBoost(Float boost); /** * Returns the texture map modifier (-mm), * or null if it was not specified * * @return The modified texture map contrast. */ FloatTuple getMm(); /** * Set the texture map modifier values * * @param base The base * @param gain The gain */ void setMm(Float base, Float gain); /** * Returns the origin offset (-o), * or null if it was not specified * * @return The origin offset. */ FloatTuple getO(); /** * Set the origin offset * * @param u The u component * @param v The v component * @param w The w component */ void setO(Float u, Float v, Float w); /** * Returns the scale (-s), * or null if it was not specified * * @return The scale. */ FloatTuple getS(); /** * Set the scale * * @param u The u component * @param v The v component * @param w The w component */ void setS(Float u, Float v, Float w); /** * Returns the turbulence (-t), * or null if it was not specified * * @return The turbulence. */ FloatTuple getT(); /** * Set the turbulence * * @param u The u component * @param v The v component * @param w The w component */ void setT(Float u, Float v, Float w); /** * Returns the texture resolution (-texres), * or null if it was not specified * * @return The texture resolution. */ Float getTexres(); /** * Set the texture resolution * * @param texres The texture resolution */ void setTexres(Float texres); /** * Returns the clamping state (-clamp), * or null if it was not specified * * @return Whether or not clamping is enabled. */ Boolean isClamp(); /** * Set the clamping state * * @param clamp The clamping state */ void setClamp(Boolean clamp); /** * Returns the bump multiplier (-bm), * or null if it was not specified * * @return The bump multiplier. */ Float getBm(); /** * Set the bump multiplier * * @param bm The bump multiplier */ void setBm(Float bm); /** * Returns the IMF channel to use (-imfchan), * or null if it was not specified * * @return The IMF channel to use. */ String getImfchan(); /** * Set the IMF channel * * @param imfchan The IMF channel */ void setImfchan(String imfchan); /** * Returns the type of texture map (-type), * or null if it was not specified * * @return The type of texture map */ String getType(); /** * Set the type * * @param type The type */ void setType(String type); } ================================================ FILE: src/main/java/de/javagl/obj/Utils.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.io.IOException; import java.util.Queue; import java.util.StringTokenizer; /** * Utility methods for reading and parsing */ class Utils { /** * Reads a float tuple from the given StringTokenizer * * @param st The StringTokenizer * @return The FloatTuple * @throws IOException If the tuple can not be read */ static FloatTuple readFloatTuple(StringTokenizer st) throws IOException { float x = parseFloat(st.nextToken()); if (st.hasMoreTokens()) { float y = parseFloat(st.nextToken()); if (st.hasMoreTokens()) { float z = parseFloat(st.nextToken()); if (st.hasMoreTokens()) { float w = parseFloat(st.nextToken()); return FloatTuples.create(x,y,z,w); } return FloatTuples.create(x,y,z); } return FloatTuples.create(x,y); } return FloatTuples.create(x); } /** * Parse a float from the given string, wrapping number format * exceptions into an IOException * * @param s The string * @return The float * @throws IOException If the string does not contain a valid float value */ static float parseFloat(String s) throws IOException { try { return Float.parseFloat(s); } catch (NumberFormatException e) { throw new IOException(e); } } /** * Returns whether the given string can be parsed into a float value * * @param s The string * @return Whether the string is a float value. If the given string is * null, then false is returned. */ private static boolean isFloat(String s) { if (s == null) { return false; } try { Float.parseFloat(s); return true; } catch (NumberFormatException e) { return false; } } /** * Parse up to max float values from the given tokens. * If there are fewer than max tokens, then the * resulting values will be null. The parsing will * stop when a value is encountered that is not a float value. * * @param tokens The input tokens * @param max The maximum number of tokens to process * @return The array containing the parsed values */ static Float[] parseFloats(Queue tokens, int max) { Float[] result = new Float[max]; for (int i = 0; i < max; i++) { String token = tokens.poll(); if (Utils.isFloat(token)) { float value = Float.parseFloat(token); result[i] = value; } } return result; } /** * Parse a boolean value from the given string, converting * "true" and "on" to true, * and anything else to false. * * @param s The string * @return The boolean value */ static boolean parseBoolean(String s) { if ("true".equalsIgnoreCase(s)) { return true; } if ("on".equalsIgnoreCase(s)) { return true; } return false; } /** * Parse an int from the given string, wrapping number format * exceptions into an IOException * * @param s The string * @return The int * @throws IOException If the string does not contain a valid int value */ static int parseInt(String s) throws IOException { try { return Integer.parseInt(s); } catch (NumberFormatException e) { throw new IOException(e); } } /** * Creates a {@link FloatTuple} from the given RGB values, treating * optional values as described in the MTL specification: If * the r component is null, then * null is returned. If the g or * b component is null, then the r * component will be used instead. * * @param r The r-component * @param g The g-component * @param b The b-component * @return The {@link FloatTuple} */ static FloatTuple createRgbTuple(Float r, Float g, Float b) { if (r == null) { return null; } float fr = r; float fg = r; float fb = r; if (g != null) { fg = g; } if (b != null) { fb = b; } return FloatTuples.create(fr, fg, fb); } /** * Creates a {@link FloatTuple} from the given UVW values, treating * optional values as described in the MTL specification: If * the u component is null, then * null is returned. If the v or * w component is null, then the given default value * will be used. * * @param u The u-component * @param v The v-component * @param w The w-component * @param defaultValue The default value for v and w * @return The {@link FloatTuple} */ static FloatTuple createUvwTuple( Float u, Float v, Float w, float defaultValue) { if (u == null) { return null; } float fu = u; float fv = (v == null ? defaultValue : v); float fw = (w == null ? defaultValue : w); return FloatTuples.create(fu, fv, fw); } /** * Private constructor to prevent instantiation */ private Utils() { // Private constructor to prevent instantiation } } ================================================ FILE: src/main/java/de/javagl/obj/WritableObj.java ================================================ /* * www.javagl.de - Obj * * Copyright (c) 2008-2015 Marco Hutter - http://www.javagl.de * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package de.javagl.obj; import java.util.Collection; /** * Interface for all classes that may receive the data that is read * from an OBJ file by an {@link ObjReader} */ public interface WritableObj { /** * Add the given vertex * * @param vertex The vertex to add. * @throws NullPointerException If the vertex is null */ void addVertex(FloatTuple vertex); /** * Add the given vertex * * @param x The x-coordinate * @param y The y-coordinate * @param z The z-coordinate */ void addVertex(float x, float y, float z); /** * Add the given texture coordinate * * @param texCoord The texture coordinate to add. * @throws NullPointerException If the texture coordinate is * null */ void addTexCoord(FloatTuple texCoord); /** * Add the given texture coordinate * * @param x The x-coordinate */ void addTexCoord(float x); /** * Add the given texture coordinate * * @param x The x-coordinate * @param y The y-coordinate */ void addTexCoord(float x, float y); /** * Add the given texture coordinate * * @param x The x-coordinate * @param y The y-coordinate * @param z The z-coordinate */ void addTexCoord(float x, float y, float z); /** * Add the given normal * * @param normal The normal to add. * @throws NullPointerException If the normal is null */ void addNormal(FloatTuple normal); /** * Add the given normal * * @param x The x-coordinate * @param y The y-coordinate * @param z The z-coordinate */ void addNormal(float x, float y, float z); /** * Set the groups with the given names to be active right now. Faces that * are added subsequently will be added to all active groups, creating * these groups if necessary. If the given collection is null, * then this call will have no effect. If the given collection is empty, * then the default group (named "default") will be activated. * * @param groupNames The group names * @throws NullPointerException If the given collection contains * null elements */ void setActiveGroupNames(Collection groupNames); /** * Set the material group with the given names to be active right now * Faces that are added subsequently will be added to the active * material group, creating this material group if necessary. If * the given name is null, then this call will have no * effect. * * @param materialGroupName The material group name */ void setActiveMaterialGroupName(String materialGroupName); /** * Add the given face. * The indices in the given face are absolute (non-negative) and * 0-based. * The implementation is free to store a reference to the given * face. * * @param face The face to add. * @throws NullPointerException If the face is null */ void addFace(ObjFace face); /** * Add the specified face with the given vertex indices, but without * texture- or normal indices. * The given indices are absolute (non-negative) and 0-based. * The implementation is free to store a reference to the given * array. So the array should not be modified after this method has * been called. * * @param v The vertex indices * @throws IllegalArgumentException If one of the given indices is * negative or not smaller than the number of vertices that have been * added until now. */ void addFace(int ... v); /** * Add the specified face with the given vertex and texture coordinate * indices, but without normal indices. * The given indices are absolute (non-negative) and 0-based. * The implementation is free to store a reference to the given * array. So the array should not be modified after this method has * been called. * * @param v The vertex- and texture coordinate indices * @throws IllegalArgumentException If one of the given indices is * negative or not smaller than the number of vertices or texture * coordinates that have been added until now. */ void addFaceWithTexCoords(int ... v); /** * Add the specified face with the given vertex and normal indices, * but without texture coordinate indices. * The given indices are absolute (non-negative) and 0-based. * The implementation is free to store a reference to the given * array. So the array should not be modified after this method has * been called. * * @param v The vertex- and normal indices * @throws IllegalArgumentException If one of the given indices is * negative or not smaller than the number of vertices or normals * that have been added until now. */ void addFaceWithNormals(int ... v); /** * Add the specified face with the given vertex, texture coordinate * and normal indices. * The given indices are absolute (non-negative) and 0-based. * The implementation is free to store a reference to the given * array. So the array should not be modified after this method has * been called. * * @param v The vertex- texture coordinate and normal indices * @throws IllegalArgumentException If one of the given indices is * negative or not smaller than the number of vertices, texture * coordinates or normals that have been added until now. */ void addFaceWithAll(int ... v); /** * Add the specified face. * The given indices are absolute (non-negative) and 0-based. * The implementation is free to store a reference to the given * arrays. So the arrays should not be modified after this method has * been called. * * @param v The vertex indices * @param vt The texture coordinate indices. May be null. * @param vn The normal indices. May be null * @throws NullPointerException If the vertex indices array is * null * @throws IllegalArgumentException If one of the given indices is * negative or not smaller than the number of corresponding vertices, * texture coordinates or normals that have been added until now, * respectively. * @throws IllegalArgumentException If the given (non-null) arrays * have different lengths */ void addFace(int[] v, int[] vt, int[] vn); /** * Set the given MTL file names. A copy of the given * collection will be stored. * * @param mtlFileNames The names of the MTL file */ void setMtlFileNames(Collection mtlFileNames); } ================================================ FILE: src/main/java/de/javagl/obj/package-info.java ================================================ /** * Classes for reading and writing Wavefront OBJ and MTL files. *

* {@link de.javagl.obj.Obj} objects may be created with * {@link de.javagl.obj.Objs#create()} or by reading an OBJ file with * {@link de.javagl.obj.ObjReader#read(java.io.InputStream)}. * {@link de.javagl.obj.Obj} objects may be written as OBJ files with * {@link de.javagl.obj.ObjWriter#write(ReadableObj, java.io.OutputStream)}. *
*/ package de.javagl.obj; ================================================ FILE: src/test/java/de/javagl/obj/TestMtlReader.java ================================================ package de.javagl.obj; import static org.junit.Assert.assertEquals; import java.io.IOException; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import org.junit.Test; @SuppressWarnings("javadoc") public class TestMtlReader { /** * An epsilon for floating-point comparisons */ private static final float FLOAT_ERROR = 1e-6f; @Test public void readMtl() throws IOException { List mtls = MtlReader.read(getClass().getResourceAsStream( "/twoMaterialsA.mtl")); assertEquals(2, mtls.size()); assertEquals(new DefaultFloatTuple(1,1,1), mtls.get(1).getKa()); assertEquals(new DefaultFloatTuple(1,1,1), mtls.get(1).getKd()); assertEquals(new DefaultFloatTuple(1,1,1), mtls.get(1).getKs()); assertEquals(0, mtls.get(1).getNs(), FLOAT_ERROR); assertEquals(0.5f, mtls.get(1).getD(), FLOAT_ERROR); assertEquals("texture.png", mtls.get(1).getMapKd()); } @Test public void readMtlWithWhitespace() throws IOException { List mtls = MtlReader.read(getClass().getResourceAsStream( "/mtlWithWhitespace.mtl")); assertEquals(1, mtls.size()); Mtl mtl = mtls.get(0); assertEquals("material0", mtl.getName()); assertEquals(new DefaultFloatTuple(1,0,0), mtl.getKa()); assertEquals(new DefaultFloatTuple(1,1,0), mtl.getKd()); assertEquals(new DefaultFloatTuple(1,1,1), mtl.getKs()); assertEquals(500, mtl.getNs(), FLOAT_ERROR); assertEquals(1.0f, mtl.getD(), FLOAT_ERROR); assertEquals("texture.png", mtl.getMapKd()); } @Test public void readMtlWithBrokenLines() throws IOException { List mtls = MtlReader.read(getClass().getResourceAsStream( "/mtlWithBrokenLines.mtl")); assertEquals(1, mtls.size()); Mtl mtl = mtls.get(0); assertEquals("material0", mtl.getName()); assertEquals(new DefaultFloatTuple(1,0,0), mtl.getKa()); assertEquals(new DefaultFloatTuple(1,1,0), mtl.getKd()); assertEquals(new DefaultFloatTuple(1,1,1), mtl.getKs()); assertEquals(500, mtl.getNs(), FLOAT_ERROR); assertEquals(123.0f, mtl.getD(), FLOAT_ERROR); assertEquals("texture.png", mtl.getMapKd()); } @Test public void readTextureOptionsWithAllOptions() throws IOException { String[] tokens = new String[] { "-blendu", "off", "-blendv", "off", "-boost", "0.4", "-cc", "on", "-mm", "0.2", "0.33", "-o", "0.01", "0.02", "0.03", "-s", "0.04", "0.05", "0.06", "-t", "0.07", "0.08", "0.09", "-texres", ".44", "-clamp", "on", "-bm", "3.45", "-imfchan", "g", "-type", "sphere", "texture.png" }; TextureOptions options = MtlReader.readTextureOptions( new LinkedList<>(Arrays.asList(tokens))); assertEquals(Boolean.FALSE, options.isBlendu()); assertEquals(Boolean.FALSE, options.isBlendv()); assertEquals(0.4f, options.getBoost(), FLOAT_ERROR); assertEquals(Boolean.TRUE, options.isCc()); assertEquals(0.2f, options.getMm().get(0), FLOAT_ERROR); assertEquals(0.33f, options.getMm().get(1), FLOAT_ERROR); assertEquals(0.01f, options.getO().getX(), FLOAT_ERROR); assertEquals(0.02f, options.getO().getY(), FLOAT_ERROR); assertEquals(0.03f, options.getO().getZ(), FLOAT_ERROR); assertEquals(0.04f, options.getS().getX(), FLOAT_ERROR); assertEquals(0.05f, options.getS().getY(), FLOAT_ERROR); assertEquals(0.06f, options.getS().getZ(), FLOAT_ERROR); assertEquals(0.07f, options.getT().getX(), FLOAT_ERROR); assertEquals(0.08f, options.getT().getY(), FLOAT_ERROR); assertEquals(0.09f, options.getT().getZ(), FLOAT_ERROR); assertEquals(0.44f, options.getTexres(), FLOAT_ERROR); assertEquals(Boolean.TRUE, options.isClamp()); assertEquals(3.45f, options.getBm(), FLOAT_ERROR); assertEquals("g", options.getImfchan()); assertEquals("sphere", options.getType()); } @Test public void readTextureOptionsWithSingleOriginOffsetValue() throws IOException { String[] tokens = new String[] { "-o", "0.1", "texture.png" }; TextureOptions options = MtlReader.readTextureOptions( new LinkedList<>(Arrays.asList(tokens))); assertEquals(0.1f, options.getO().getX(), FLOAT_ERROR); assertEquals(0.0f, options.getO().getY(), FLOAT_ERROR); assertEquals(0.0f, options.getO().getZ(), FLOAT_ERROR); } @Test public void readTextureOptionsWithDoubleOriginOffsetValue() throws Exception { String[] tokens = new String[] { "-o", "0.1", "0.2", "texture.png" }; TextureOptions options = MtlReader.readTextureOptions( new LinkedList<>(Arrays.asList(tokens))); assertEquals(0.1f, options.getO().getX(), FLOAT_ERROR); assertEquals(0.2f, options.getO().getY(), FLOAT_ERROR); assertEquals(0.0f, options.getO().getZ(), FLOAT_ERROR); } @Test public void readMtlWithPbrProperties() throws IOException { List mtls = MtlReader.read(getClass().getResourceAsStream( "/mtlWithPbrProperties.mtl")); assertEquals(1, mtls.size()); Mtl mtl = mtls.get(0); assertEquals("Material.001", mtl.getName()); assertEquals(0.5, mtl.getPr(), FLOAT_ERROR); assertEquals(0.7, mtl.getPm(), FLOAT_ERROR); assertEquals(0.1, mtl.getPs(), FLOAT_ERROR); assertEquals(0.4, mtl.getPc(), FLOAT_ERROR); assertEquals(0.03, mtl.getPcr(), FLOAT_ERROR); assertEquals(0.001, mtl.getAniso(), FLOAT_ERROR); assertEquals(0.01, mtl.getAnisor(), FLOAT_ERROR); } } ================================================ FILE: src/test/java/de/javagl/obj/TestMtlWriter.java ================================================ package de.javagl.obj; import static org.junit.Assert.assertEquals; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Scanner; import org.junit.Test; @SuppressWarnings("javadoc") public class TestMtlWriter { @Test public void writeMtl() throws IOException { String inputString = readResourceAsString( "/twoMaterialsA.mtl"); List mtls = MtlReader.read( new ByteArrayInputStream(inputString.getBytes())); ByteArrayOutputStream baos = new ByteArrayOutputStream(); MtlWriter.write(mtls, baos); String outputString = new String(baos.toByteArray()); //System.out.println(outputString); assertEquals(inputString, outputString); } @Test public void writeComplexMtl() throws IOException { String inputString = readResourceAsString( "/complexMaterial.mtl"); List mtls = MtlReader.read( new ByteArrayInputStream(inputString.getBytes())); ByteArrayOutputStream baos = new ByteArrayOutputStream(); MtlWriter.write(mtls, baos); String outputString = new String(baos.toByteArray()); //System.out.println(outputString); assertEquals(inputString, outputString); } @Test public void writePbrMtl() throws IOException { String inputString = readResourceAsString( "/pbrMaterial.mtl"); List mtls = MtlReader.read( new ByteArrayInputStream(inputString.getBytes())); ByteArrayOutputStream baos = new ByteArrayOutputStream(); MtlWriter.write(mtls, baos); String outputString = new String(baos.toByteArray()); System.out.println(outputString); assertEquals(inputString, outputString); } private static String readResourceAsString(String name) { InputStream inputStream = TestObjWriter.class.getResourceAsStream(name); String string = readAsString(inputStream); string = string.replaceAll("\r\n", "\n"); return string; } private static String readAsString(InputStream inputStream) { try (Scanner scanner = new Scanner(inputStream)) { scanner.useDelimiter("\\A"); String string = scanner.next(); return string; } } } ================================================ FILE: src/test/java/de/javagl/obj/TestObjData.java ================================================ package de.javagl.obj; import static org.junit.Assert.*; import java.io.IOException; import org.junit.Test; @SuppressWarnings("javadoc") public class TestObjData { @Test public void testGetVertices() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/squareTextured.obj")); obj = ObjUtils.convertToRenderable(obj); assertEquals(2, obj.getNumFaces()); assertEquals(4, obj.getNumVertices()); assertEquals(4, obj.getNumTexCoords()); assertEquals(0, obj.getNumNormals()); assertEquals(1, obj.getNumGroups()); // default assertEquals(0, obj.getNumMaterialGroups()); float[] actualVertices = ObjData.getVerticesArray(obj); float[] expectedVertices = { 0.0f, 0.0f, 0.0f, 4.0f, 0.0f, 0.0f, 4.0f, 4.0f, 0.0f, 0.0f, 4.0f, 0.0f }; assertArrayEquals(expectedVertices, actualVertices, 0.0f); } @Test public void testGetTexCoords() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/squareTextured.obj")); obj = ObjUtils.convertToRenderable(obj); assertEquals(2, obj.getNumFaces()); assertEquals(4, obj.getNumVertices()); assertEquals(4, obj.getNumTexCoords()); assertEquals(0, obj.getNumNormals()); assertEquals(1, obj.getNumGroups()); // default assertEquals(0, obj.getNumMaterialGroups()); float[] actualTexCoords = ObjData.getTexCoordsArray(obj, 2); float[] expectedTexCoords = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f }; assertArrayEquals(expectedTexCoords, actualTexCoords, 0.0f); } @Test public void testGetTexCoordsFlipped() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/squareTextured.obj")); obj = ObjUtils.convertToRenderable(obj); assertEquals(2, obj.getNumFaces()); assertEquals(4, obj.getNumVertices()); assertEquals(4, obj.getNumTexCoords()); assertEquals(0, obj.getNumNormals()); assertEquals(1, obj.getNumGroups()); // default assertEquals(0, obj.getNumMaterialGroups()); float[] actualTexCoords = ObjData.getTexCoordsArray(obj, 2, true); float[] expectedTexCoords = { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f }; assertArrayEquals(expectedTexCoords, actualTexCoords, 0.0f); } } ================================================ FILE: src/test/java/de/javagl/obj/TestObjReader.java ================================================ package de.javagl.obj; import static org.junit.Assert.assertEquals; import java.io.IOException; import java.util.List; import org.junit.Test; @SuppressWarnings("javadoc") public class TestObjReader { @Test public void readSquare() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/square.obj")); assertEquals(1, obj.getNumFaces()); assertEquals(4, obj.getNumVertices()); assertEquals(0, obj.getNumTexCoords()); assertEquals(0, obj.getNumNormals()); assertEquals(1, obj.getNumGroups()); assertEquals(0, obj.getNumMaterialGroups()); } @Test public void readSquareAndTriangle() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/squareAndTriangle.obj")); assertEquals(2, obj.getNumFaces()); assertEquals(5, obj.getNumVertices()); assertEquals(0, obj.getNumTexCoords()); assertEquals(0, obj.getNumNormals()); assertEquals(1, obj.getNumGroups()); assertEquals(0, obj.getNumMaterialGroups()); } @Test public void readSquareAndTriangleInTwoGroups() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/squareAndTriangleInTwoGroups.obj")); assertEquals(2, obj.getNumFaces()); assertEquals(5, obj.getNumVertices()); assertEquals(0, obj.getNumTexCoords()); assertEquals(0, obj.getNumNormals()); assertEquals(3, obj.getNumGroups()); // 2 + default assertEquals(0, obj.getNumMaterialGroups()); } @Test public void readFourTrianglesInMixedGroups() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/fourTrianglesInMixedGroups.obj")); assertEquals(4, obj.getNumFaces()); assertEquals(6, obj.getNumVertices()); assertEquals(0, obj.getNumTexCoords()); assertEquals(0, obj.getNumNormals()); assertEquals(3, obj.getNumGroups()); // 2 + default assertEquals(4, obj.getNumMaterialGroups()); assertEquals("twoMaterialsA.mtl", obj.getMtlFileNames().get(0)); } @Test public void readTwoTrianglesOneInDefaultGroup() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/twoTrianglesOneInDefaultGroup.obj")); assertEquals(2, obj.getNumFaces()); assertEquals(4, obj.getNumVertices()); assertEquals(0, obj.getNumTexCoords()); assertEquals(0, obj.getNumNormals()); assertEquals(2, obj.getNumGroups()); // 1 + default assertEquals(0, obj.getNumMaterialGroups()); } @Test public void readTwoTrianglesSharedInThreeGroups() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/twoTrianglesSharedInThreeGroups.obj")); assertEquals(2, obj.getNumFaces()); assertEquals(4, obj.getNumVertices()); assertEquals(0, obj.getNumTexCoords()); assertEquals(0, obj.getNumNormals()); assertEquals(4, obj.getNumGroups()); // 3 + default assertEquals(0, obj.getNumMaterialGroups()); } @Test public void readSquareAndTriangleWithRelativeIndices() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/squareAndTriangleWithRelativeIndices.obj")); assertEquals(2, obj.getNumFaces()); assertEquals(5, obj.getNumVertices()); assertEquals(0, obj.getNumTexCoords()); assertEquals(0, obj.getNumNormals()); assertEquals(1, obj.getNumGroups()); // default assertEquals(0, obj.getNumMaterialGroups()); } @Test public void readMtls() throws IOException { List mtls = MtlReader.read(getClass().getResourceAsStream( "/twoMaterialsA.mtl")); assertEquals(2, mtls.size()); Mtl mtl0 = mtls.get(0); assertEquals("material0", mtl0.getName()); assertEquals(new DefaultFloatTuple(1,0,0), mtl0.getKa()); assertEquals(new DefaultFloatTuple(1,0,0), mtl0.getKd()); assertEquals(new DefaultFloatTuple(1,1,1), mtl0.getKs()); assertEquals(500, mtl0.getNs(), 1e-6); } } ================================================ FILE: src/test/java/de/javagl/obj/TestObjSplitting.java ================================================ package de.javagl.obj; import static org.junit.Assert.*; import java.io.IOException; import java.util.List; import java.util.Map; import org.junit.Test; @SuppressWarnings("javadoc") public class TestObjSplitting { @Test public void testSplitByGroup() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/squareAndTriangleInTwoGroups.obj")); Map objs = ObjSplitting.splitByGroups(obj); assertEquals(2, objs.size()); } @Test public void testSplitByGroupOnlyDefault() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/square.obj")); Map objs = ObjSplitting.splitByGroups(obj); assertEquals(1, objs.size()); } @Test public void testSplitByMaterialGroup() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/fourTrianglesInMixedGroups.obj")); Map objs = ObjSplitting.splitByMaterialGroups(obj); assertEquals(4, objs.size()); } @Test public void testSplitByMaterialGroupWithoutMaterial() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/square.obj")); Map objs = ObjSplitting.splitByMaterialGroups(obj); // There are no material groups in the "square.obj" assertEquals(0, objs.size()); } @Test public void testSplitByMaterialGroupForPartialGroups() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/fourTrianglesPartiallyInMaterialGroups.obj")); Map objs = ObjSplitting.splitByMaterialGroups(obj); // Only two of the triangles are in material groups assertEquals(2, objs.size()); } @Test public void testSplitByNumVertices() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/fourTrianglesInMixedGroups.obj")); int maxNumVertices = 4; List objs = ObjSplitting.splitByMaxNumVertices(obj, maxNumVertices); assertEquals(2, objs.size()); for (ReadableObj r : objs) { assertTrue(r.getNumVertices() <= maxNumVertices); } } @Test public void testSplitByNumVerticesForInvalidInput() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/square.obj")); int maxNumVertices = 3; List objs = ObjSplitting.splitByMaxNumVertices(obj, maxNumVertices); assertEquals(1, objs.size()); for (ReadableObj r : objs) { // This is larger than the maximum, but an OBJ containing // a square cannot be split otherwise: assertEquals(4, r.getNumVertices()); } } } ================================================ FILE: src/test/java/de/javagl/obj/TestObjUtilsAdd.java ================================================ package de.javagl.obj; import static org.junit.Assert.assertEquals; import java.io.IOException; import org.junit.Test; @SuppressWarnings("javadoc") public class TestObjUtilsAdd { @Test public void testAdd() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/twoTrianglesOneInDefaultGroup.obj")); assertEquals(2, obj.getNumFaces()); assertEquals(4, obj.getNumVertices()); assertEquals(0, obj.getNumTexCoords()); assertEquals(0, obj.getNumNormals()); assertEquals(2, obj.getNumGroups()); // 1 + default assertEquals(0, obj.getNumMaterialGroups()); Obj combinedObj = Objs.create(); ObjUtils.add(obj, combinedObj); ObjUtils.add(obj, combinedObj); assertEquals(4, combinedObj.getNumFaces()); assertEquals(8, combinedObj.getNumVertices()); assertEquals(0, combinedObj.getNumTexCoords()); assertEquals(0, combinedObj.getNumNormals()); assertEquals(2, combinedObj.getNumGroups()); // Still only two groups! assertEquals(0, combinedObj.getNumMaterialGroups()); } @Test public void testAddDifferent() throws IOException { Obj obj0 = ObjReader.read(getClass().getResourceAsStream( "/twoTrianglesOneInDefaultGroup.obj")); Obj obj1 = ObjReader.read(getClass().getResourceAsStream( "/squareAndTriangleInTwoGroups.obj")); assertEquals(2, obj0.getNumFaces()); assertEquals(4, obj0.getNumVertices()); assertEquals(0, obj0.getNumTexCoords()); assertEquals(0, obj0.getNumNormals()); assertEquals(2, obj0.getNumGroups()); // 1 + default assertEquals(0, obj0.getNumMaterialGroups()); assertEquals(2, obj1.getNumFaces()); assertEquals(5, obj1.getNumVertices()); assertEquals(0, obj1.getNumTexCoords()); assertEquals(0, obj1.getNumNormals()); assertEquals(3, obj1.getNumGroups()); // 2 + default assertEquals(0, obj1.getNumMaterialGroups()); Obj combinedObj = Objs.create(); ObjUtils.add(obj0, combinedObj); ObjUtils.add(obj1, combinedObj); assertEquals(4, combinedObj.getNumFaces()); assertEquals(9, combinedObj.getNumVertices()); assertEquals(0, combinedObj.getNumTexCoords()); assertEquals(0, combinedObj.getNumNormals()); // The default group, "group0" and "group1" assertEquals(3, combinedObj.getNumGroups()); assertEquals(0, combinedObj.getNumMaterialGroups()); } @Test public void testAddWithMaterialGroups() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/fourTrianglesInMixedGroups.obj")); assertEquals(4, obj.getNumFaces()); assertEquals(6, obj.getNumVertices()); assertEquals(0, obj.getNumTexCoords()); assertEquals(0, obj.getNumNormals()); assertEquals(3, obj.getNumGroups()); // 2 + (empty) default assertEquals(4, obj.getNumMaterialGroups()); // 4 groups Obj combinedObj = Objs.create(); ObjUtils.add(obj, combinedObj); ObjUtils.add(obj, combinedObj); assertEquals(8, combinedObj.getNumFaces()); assertEquals(12, combinedObj.getNumVertices()); assertEquals(0, combinedObj.getNumTexCoords()); assertEquals(0, combinedObj.getNumNormals()); assertEquals(3, combinedObj.getNumGroups()); assertEquals(4, combinedObj.getNumMaterialGroups()); // Still 4 } } ================================================ FILE: src/test/java/de/javagl/obj/TestObjUtilsGroupToObj.java ================================================ package de.javagl.obj; import static org.junit.Assert.assertEquals; import java.io.IOException; import org.junit.Test; @SuppressWarnings("javadoc") public class TestObjUtilsGroupToObj { @Test public void testGroupToObj() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/squareAndTriangleInTwoGroups.obj")); assertEquals(2, obj.getNumFaces()); assertEquals(5, obj.getNumVertices()); assertEquals(0, obj.getNumTexCoords()); assertEquals(0, obj.getNumNormals()); assertEquals(3, obj.getNumGroups()); // 2 + default assertEquals(0, obj.getNumMaterialGroups()); Obj groupObj0 = ObjUtils.groupToObj(obj, obj.getGroup("group0"), null); Obj groupObj1 = ObjUtils.groupToObj(obj, obj.getGroup("group1"), null); assertEquals(1, groupObj0.getNumFaces()); assertEquals(4, groupObj0.getNumVertices()); assertEquals(0, groupObj0.getNumTexCoords()); assertEquals(0, groupObj0.getNumNormals()); assertEquals(2, groupObj0.getNumGroups()); assertEquals(0, groupObj0.getNumMaterialGroups()); assertEquals(1, groupObj1.getNumFaces()); assertEquals(3, groupObj1.getNumVertices()); assertEquals(0, groupObj1.getNumTexCoords()); assertEquals(0, groupObj1.getNumNormals()); assertEquals(2, groupObj1.getNumGroups()); assertEquals(0, groupObj1.getNumMaterialGroups()); } } ================================================ FILE: src/test/java/de/javagl/obj/TestObjUtilsMakeTexCoordsUnique.java ================================================ package de.javagl.obj; import static org.junit.Assert.assertEquals; import java.io.IOException; import org.junit.Test; @SuppressWarnings("javadoc") public class TestObjUtilsMakeTexCoordsUnique { @Test public void testMakeTexCoordsUnique() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/twoTrianglesWithAmbiguousTexCoords.obj")); Obj unique = ObjUtils.makeTexCoordsUnique(obj); assertEquals(2, unique.getNumFaces()); assertEquals(5, unique.getNumVertices()); assertEquals(4, unique.getNumTexCoords()); assertEquals(0, unique.getNumNormals()); assertEquals(1, unique.getNumGroups()); assertEquals(0, unique.getNumMaterialGroups()); } } ================================================ FILE: src/test/java/de/javagl/obj/TestObjUtilsTriangulate.java ================================================ package de.javagl.obj; import static org.junit.Assert.assertEquals; import java.io.IOException; import org.junit.Test; @SuppressWarnings("javadoc") public class TestObjUtilsTriangulate { @Test public void testTriangulateSquareAndTriangle() throws IOException { Obj obj = ObjReader.read(getClass().getResourceAsStream( "/squareAndTriangle.obj")); assertEquals(2, obj.getNumFaces()); assertEquals(5, obj.getNumVertices()); assertEquals(0, obj.getNumTexCoords()); assertEquals(0, obj.getNumNormals()); assertEquals(1, obj.getNumGroups()); assertEquals(0, obj.getNumMaterialGroups()); Obj triangulatedObj = ObjUtils.triangulate(obj); assertEquals(3, triangulatedObj.getNumFaces()); assertEquals(5, triangulatedObj.getNumVertices()); assertEquals(0, triangulatedObj.getNumTexCoords()); assertEquals(0, triangulatedObj.getNumNormals()); assertEquals(1, triangulatedObj.getNumGroups()); assertEquals(0, triangulatedObj.getNumMaterialGroups()); } } ================================================ FILE: src/test/java/de/javagl/obj/TestObjWriter.java ================================================ package de.javagl.obj; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Scanner; import static org.junit.Assert.*; import org.junit.Test; @SuppressWarnings("javadoc") public class TestObjWriter { @Test public void readWriteSquare() throws IOException { String inputString = readResourceAsString( "/square.obj"); Obj obj = ObjReader.read( new ByteArrayInputStream(inputString.getBytes())); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjWriter.write(obj, baos); String outputString = new String(baos.toByteArray()); //System.out.println(outputString); assertEquals(inputString, outputString); } @Test public void readWriteSquareAndTriangle() throws IOException { String inputString = readResourceAsString( "/squareAndTriangle.obj"); Obj obj = ObjReader.read( new ByteArrayInputStream(inputString.getBytes())); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjWriter.write(obj, baos); String outputString = new String(baos.toByteArray()); //System.out.println(outputString); assertEquals(inputString, outputString); } @Test public void readWriteSquareAndTriangleInTwoGroups() throws IOException { String inputString = readResourceAsString( "/squareAndTriangleInTwoGroups.obj"); Obj obj = ObjReader.read( new ByteArrayInputStream(inputString.getBytes())); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjWriter.write(obj, baos); String outputString = new String(baos.toByteArray()); //System.out.println(outputString); assertEquals(inputString, outputString); } @Test public void readWriteFourTrianglesInMixedGroups() throws IOException { String inputString = readResourceAsString( "/fourTrianglesInMixedGroups.obj"); Obj obj = ObjReader.read( new ByteArrayInputStream(inputString.getBytes())); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjWriter.write(obj, baos); String outputString = new String(baos.toByteArray()); //System.out.println(outputString); assertEquals(inputString, outputString); } @Test public void readWriteTwoTrianglesOneInDefaultGroup() throws IOException { String inputString = readResourceAsString( "/twoTrianglesOneInDefaultGroup.obj"); Obj obj = ObjReader.read( new ByteArrayInputStream(inputString.getBytes())); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjWriter.write(obj, baos); String outputString = new String(baos.toByteArray()); //System.out.println(outputString); assertEquals(inputString, outputString); } @Test public void readTwoTrianglesSharedInThreeGroups() throws IOException { String inputString = readResourceAsString( "/twoTrianglesSharedInThreeGroups.obj"); Obj obj = ObjReader.read( new ByteArrayInputStream(inputString.getBytes())); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjWriter.write(obj, baos); String outputString = new String(baos.toByteArray()); //System.out.println(outputString); assertEquals(inputString, outputString); } private static String readResourceAsString(String name) { InputStream inputStream = TestObjWriter.class.getResourceAsStream(name); String string = readAsString(inputStream); string = string.replaceAll("\r\n", "\n"); return string; } private static String readAsString(InputStream inputStream) { try (Scanner scanner = new Scanner(inputStream)) { scanner.useDelimiter("\\A"); String string = scanner.next(); return string; } } } ================================================ FILE: src/test/java/de/javagl/obj/TestObjsCreate.java ================================================ package de.javagl.obj; import static org.junit.Assert.assertEquals; import java.io.IOException; import java.nio.FloatBuffer; import java.nio.IntBuffer; import org.junit.Test; @SuppressWarnings("javadoc") public class TestObjsCreate { @Test public void createFromIndexedTriangleData() throws IOException { float[] vertices = { 0,0,0, 1,0,0, 1,1,0, 0,1,0, }; float[] texCoords = { 0,0, 1,0, 1,1, 0,1, }; float[] normals = { 0,0,1, 0,0,1, 0,0,1, 0,0,1, }; int[] indices = { 0,1,3, 0,1,2 }; Obj obj = Objs.createFromIndexedTriangleData( IntBuffer.wrap(indices), FloatBuffer.wrap(vertices), FloatBuffer.wrap(texCoords), FloatBuffer.wrap(normals)); assertEquals(2, obj.getNumFaces()); assertEquals(4, obj.getNumVertices()); assertEquals(4, obj.getNumTexCoords()); assertEquals(4, obj.getNumNormals()); assertEquals(1, obj.getNumGroups()); assertEquals(0, obj.getNumMaterialGroups()); } } ================================================ FILE: src/test/resources/complexMaterial.mtl ================================================ newmtl material0 illum 3 Ns 500.0 Ni 2.3 d 1.0 Ka 1.0 1.0 1.0 Kd 1.0 0.0 1.0 Ks 1.0 1.0 1.0 Tf 0.0 1.0 0.0 sharpness 100.0 map_Ka -blendu off -blendv on -mm 2.3 4.5 -clamp on ambient.png map_Kd -texres 512.0 diffuse.png map_Ks -o 0.1 0.2 0.3 -s 1.1 1.2 1.3 -t 0.1 0.2 0.3 specularReflectivity.png map_Ns -o 0.1 0.2 0.3 -s 1.1 1.2 1.3 specularExponent.png map_d -mm 0.2 0.8 opacity.png bump -imfchan -bm bump.png disp displacement.png decal decal.png refl -type cube_top cubeTop.png refl -type cube_bottom cubeBottom.png refl -type cube_front cubeFront.png refl -type cube_back cubeBack.png refl -type cube_left cubeLeft.png refl -type cube_right cubeRight.png ================================================ FILE: src/test/resources/fourTrianglesInMixedGroups.obj ================================================ mtllib twoMaterialsA.mtl v 0.0 0.0 0.0 v 4.0 0.0 0.0 v 4.0 4.0 0.0 v 0.0 4.0 0.0 v 8.0 0.0 0.0 v 8.0 4.0 0.0 g group0 usemtl material0 f 1 2 3 usemtl material1 f 1 3 4 g group1 usemtl material2 f 2 5 6 usemtl material3 f 2 6 3 ================================================ FILE: src/test/resources/fourTrianglesPartiallyInMaterialGroups.obj ================================================ mtllib twoMaterialsA.mtl v 0.0 0.0 0.0 v 4.0 0.0 0.0 v 4.0 4.0 0.0 v 0.0 4.0 0.0 v 8.0 0.0 0.0 v 8.0 4.0 0.0 f 1 2 3 f 1 3 4 g group1 usemtl material0 f 2 5 6 usemtl material1 f 2 6 3 ================================================ FILE: src/test/resources/mtlWithBrokenLines.mtl ================================================ # Some lines are broken with a backslash newmtl material0 Ka 1.0 \ 0.0 \ 0.0 Kd 1.0 1.0 0.0 Ks 1.0 1.0 1.0 Ns 500.0 map_Kd texture.png d \ 123.0 ================================================ FILE: src/test/resources/mtlWithPbrProperties.mtl ================================================ # www.blender.org newmtl Material.001 Kd 0.800000 0.304983 0.243147 Ks 0.500000 0.500000 0.500000 Ke 0.000000 0.000000 0.000000 Ni 1.450000 d 1.000000 illum 2 Pr 0.500000 Pm 0.700000 Ps 0.100000 Pc 0.400000 Pcr 0.030000 aniso 0.001000 anisor 0.010000 ================================================ FILE: src/test/resources/mtlWithWhitespace.mtl ================================================ # Some lines have leading and trailing whitespace, # in form of space characters or tabs newmtl material0 Ka 1.0 0.0 0.0 Kd 1.0 1.0 0.0 Ks 1.0 1.0 1.0 Ns 500.0 map_Kd texture.png d 1.0 ================================================ FILE: src/test/resources/pbrMaterial.mtl ================================================ newmtl material1 Pr 0.12 map_Pr roughness.png Pm 0.23 map_Pm metallic.png Ps 0.34 map_Ps sheen.png Pc 0.11 Pcr 0.222 Ke 0.2 0.3 0.4 map_Ke emissive.png aniso 0.2 anisor 0.3 norm normals.png ================================================ FILE: src/test/resources/square.obj ================================================ v 0.0 0.0 0.0 v 4.0 0.0 0.0 v 4.0 4.0 0.0 v 0.0 4.0 0.0 f 1 2 3 4 ================================================ FILE: src/test/resources/squareAndTriangle.obj ================================================ v 0.0 0.0 0.0 v 4.0 0.0 0.0 v 4.0 4.0 0.0 v 0.0 4.0 0.0 v 2.0 6.0 0.0 f 1 2 3 4 f 3 4 5 ================================================ FILE: src/test/resources/squareAndTriangleInTwoGroups.obj ================================================ v 0.0 0.0 0.0 v 4.0 0.0 0.0 v 4.0 4.0 0.0 v 0.0 4.0 0.0 v 2.0 6.0 0.0 g group0 f 1 2 3 4 g group1 f 3 4 5 ================================================ FILE: src/test/resources/squareAndTriangleWithRelativeIndices.obj ================================================ v 0.0 0.0 0.0 v 4.0 0.0 0.0 v 4.0 4.0 0.0 v 0.0 4.0 0.0 v 2.0 6.0 0.0 f 1 -4 -3 4 f 3 -2 5 ================================================ FILE: src/test/resources/squareTextured.obj ================================================ v 0.0 0.0 0.0 v 4.0 0.0 0.0 v 4.0 4.0 0.0 v 0.0 4.0 0.0 vt 0.0 0.0 vt 1.0 0.0 vt 1.0 1.0 vt 0.0 1.0 f 1/1 2/2 3/3 4/4 ================================================ FILE: src/test/resources/twoMaterialsA.mtl ================================================ newmtl material0 Ns 500.0 d 1.0 Ka 1.0 0.0 0.0 Kd 1.0 0.0 0.0 Ks 1.0 1.0 1.0 newmtl material1 Ns 0.0 d 0.5 Ka 1.0 1.0 1.0 Kd 1.0 1.0 1.0 Ks 1.0 1.0 1.0 map_Kd texture.png ================================================ FILE: src/test/resources/twoMaterialsB.mtl ================================================ newmtl material2 Ka 1 0 1 Kd 1 0 1 Ks 1 1 1 Ns 200 newmtl material3 Ka 0 1 1 Kd 0 1 1 Ks 0 1 1 Ns 100 d 0.25 ================================================ FILE: src/test/resources/twoTrianglesOneInDefaultGroup.obj ================================================ v 0.0 0.0 0.0 v 4.0 0.0 0.0 v 4.0 4.0 0.0 v 0.0 4.0 0.0 f 1 2 4 g group0 f 2 3 4 ================================================ FILE: src/test/resources/twoTrianglesSharedInThreeGroups.obj ================================================ v 0.0 0.0 0.0 v 4.0 0.0 0.0 v 4.0 4.0 0.0 v 0.0 4.0 0.0 g group0 group1 f 1 2 4 g group1 group2 f 2 3 4 ================================================ FILE: src/test/resources/twoTrianglesWithAmbiguousTexCoords.obj ================================================ v 0 0 0 v 4 0 0 v 4 4 0 v 0 4 0 vt 0 0 vt 1 0 vt 1 1 vt 0 1 f 1/1 2/2 4/4 f 2/1 3/3 4/4