View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.geometry.examples.io.threed.obj;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.StringReader;
22  import java.io.UncheckedIOException;
23  import java.net.URL;
24  
25  import org.apache.commons.geometry.core.GeometryTestUtils;
26  import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
27  import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
28  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
29  import org.apache.commons.geometry.euclidean.threed.RegionBSPTree3D;
30  import org.apache.commons.geometry.euclidean.threed.Triangle3D;
31  import org.apache.commons.geometry.euclidean.threed.Vector3D;
32  import org.apache.commons.geometry.euclidean.threed.mesh.TriangleMesh;
33  import org.junit.Assert;
34  import org.junit.Test;
35  
36  public class OBJReaderTest {
37  
38      private static final double TEST_EPS = 1e-10;
39  
40      private static final DoublePrecisionContext TEST_PRECISION =
41              new EpsilonDoublePrecisionContext(TEST_EPS);
42  
43      private static final String CUBE_MINUS_SPHERE_MODEL = "/models/cube-minus-sphere.obj";
44  
45      private static final int CUBE_MINUS_SPHERE_VERTICES = 1688;
46  
47      private static final int CUBE_MINUS_SPHERE_FACES = 728;
48  
49      private OBJReader reader = new OBJReader();
50  
51      @Test
52      public void testReadMesh_emptyInput() throws Exception {
53          // act
54          TriangleMesh mesh = reader.readTriangleMesh(new StringReader(""), TEST_PRECISION);
55  
56          // assert
57          Assert.assertEquals(0, mesh.getVertexCount());
58          Assert.assertEquals(0, mesh.getFaceCount());
59      }
60  
61      @Test
62      public void testReadMesh_mixedVertexIndexTypesAndWhitespace() throws Exception {
63          // arrange
64          String input =
65              "#some comments  \n\r\n \n" +
66              " # some other comments\n" +
67              "v 0.0 0.0 0.0\n" +
68              "v 1e-1 0 0 \r\n" +
69              " v 0 1 0\n" +
70              "\tv\t0 0 1\r\n" +
71              "f 1 2 3\n" +
72              " f    -1   -2\t-3";
73  
74          // act
75          TriangleMesh mesh = reader.readTriangleMesh(new StringReader(input), TEST_PRECISION);
76  
77          // assert
78          Assert.assertEquals(4, mesh.getVertexCount());
79          Assert.assertEquals(2, mesh.getFaceCount());
80  
81          Triangle3D t0 = mesh.getFace(0).getPolygon();
82          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, t0.getPoint1(), TEST_EPS);
83          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.1, 0, 0), t0.getPoint2(), TEST_EPS);
84          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), t0.getPoint3(), TEST_EPS);
85  
86          Triangle3D t1 = mesh.getFace(1).getPolygon();
87          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 1), t1.getPoint1(), TEST_EPS);
88          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), t1.getPoint2(), TEST_EPS);
89          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.1, 0, 0), t1.getPoint3(), TEST_EPS);
90      }
91  
92      @Test
93      public void testReadMesh_multipleFaceIndices_usesTriangleFan() throws Exception {
94          // arrange
95          String input =
96              "v 0 0 0\n" +
97              "v 1 0 0\n" +
98              "v 1 1 0\n" +
99              "v 0.5 1.5 0\n" +
100             "v 0 1 0\n" +
101             "f 1 2 3 -2 -1\n";
102 
103         // act
104         TriangleMesh mesh = reader.readTriangleMesh(new StringReader(input), TEST_PRECISION);
105 
106         // assert
107         Assert.assertEquals(5, mesh.getVertexCount());
108         Assert.assertEquals(3, mesh.getFaceCount());
109 
110         Triangle3D t0 = mesh.getFace(0).getPolygon();
111         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, t0.getPoint1(), TEST_EPS);
112         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 0, 0), t0.getPoint2(), TEST_EPS);
113         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, 0), t0.getPoint3(), TEST_EPS);
114 
115         Triangle3D t1 = mesh.getFace(1).getPolygon();
116         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, t1.getPoint1(), TEST_EPS);
117         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, 0), t1.getPoint2(), TEST_EPS);
118         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 1.5, 0), t1.getPoint3(), TEST_EPS);
119 
120         Triangle3D t2 = mesh.getFace(2).getPolygon();
121         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, t2.getPoint1(), TEST_EPS);
122         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 1.5, 0), t2.getPoint2(), TEST_EPS);
123         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), t2.getPoint3(), TEST_EPS);
124     }
125 
126     @Test
127     public void testReadMesh_ignoresUnsupportedContent() throws Exception {
128         // arrange
129         String input =
130             "mtllib abc.mtl\n" +
131             "nope\n" +
132             "v 0 0 0\n" +
133             "v 1 0 0\n" +
134             "v 0 1 0\n" +
135             "f 1/10/20 2//40 3//\n";
136 
137         // act
138         TriangleMesh mesh = reader.readTriangleMesh(new StringReader(input), TEST_PRECISION);
139 
140         // assert
141         Assert.assertEquals(3, mesh.getVertexCount());
142         Assert.assertEquals(1, mesh.getFaceCount());
143 
144         Triangle3D t0 = mesh.getFace(0).getPolygon();
145         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, t0.getPoint1(), TEST_EPS);
146         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 0, 0), t0.getPoint2(), TEST_EPS);
147         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), t0.getPoint3(), TEST_EPS);
148     }
149 
150     @Test
151     public void testReadMesh_invalidVertexDefinition() throws Exception {
152         // arrange
153         String badNumber =
154             "v abc 0 0\n" +
155             "v 1 0 0\n" +
156             "v 0 1 0\n" +
157             "f 1 2 3\n";
158 
159         String notEnoughVertices =
160             "v 0 0\n" +
161             "v 1 0 0\n" +
162             "v 0 1 0\n" +
163             "f 1 2 3\n";
164 
165         // act/assert
166         GeometryTestUtils.assertThrows(() -> {
167             try {
168                 reader.readTriangleMesh(new StringReader(badNumber), TEST_PRECISION);
169             } catch (IOException exc) {
170                 throw new UncheckedIOException(exc);
171             }
172         }, NumberFormatException.class);
173 
174         GeometryTestUtils.assertThrows(() -> {
175             try {
176                 reader.readTriangleMesh(new StringReader(notEnoughVertices), TEST_PRECISION);
177             } catch (IOException exc) {
178                 throw new UncheckedIOException(exc);
179             }
180         }, IllegalArgumentException.class, "Invalid vertex definition: at least 3 fields required but found only 2");
181     }
182 
183     @Test
184     public void testReadMesh_invalidFaceDefinition() throws Exception {
185         // arrange
186         String badNumber =
187             "v 0 0 0\n" +
188             "v 1 0 0\n" +
189             "v 0 1 0\n" +
190             "f 1 abc 3\n";
191 
192         String notEnoughIndices =
193             "v 0 0 0\n" +
194             "v 1 0 0\n" +
195             "v 0 1 0\n" +
196             "f 1 2\n";
197 
198         // act/assert
199         GeometryTestUtils.assertThrows(() -> {
200             try {
201                 reader.readTriangleMesh(new StringReader(badNumber), TEST_PRECISION);
202             } catch (IOException exc) {
203                 throw new UncheckedIOException(exc);
204             }
205         }, NumberFormatException.class);
206 
207         GeometryTestUtils.assertThrows(() -> {
208             try {
209                 reader.readTriangleMesh(new StringReader(notEnoughIndices), TEST_PRECISION);
210             } catch (IOException exc) {
211                 throw new UncheckedIOException(exc);
212             }
213         }, IllegalArgumentException.class, "Invalid face definition: at least 3 fields required but found only 2");
214     }
215 
216     @Test
217     public void testReadMesh_cubeMinusSphereFile() throws Exception {
218         // arrange
219         URL url = getClass().getResource(CUBE_MINUS_SPHERE_MODEL);
220         File file = new File(url.toURI());
221 
222         // act
223         TriangleMesh mesh = reader.readTriangleMesh(file, TEST_PRECISION);
224 
225         // assert
226         Assert.assertEquals(CUBE_MINUS_SPHERE_VERTICES, mesh.getVertexCount());
227         Assert.assertEquals(CUBE_MINUS_SPHERE_FACES, mesh.getFaceCount());
228 
229         RegionBSPTree3D tree = RegionBSPTree3D.partitionedRegionBuilder()
230                 .insertAxisAlignedGrid(mesh.getBounds(), 1, TEST_PRECISION)
231                 .insertBoundaries(mesh)
232                 .build();
233 
234         double eps = 1e-5;
235         Assert.assertEquals(0.11509505362599505, tree.getSize(), eps);
236         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, tree.getCentroid(), TEST_EPS);
237     }
238 
239     @Test
240     public void testReadMesh_cubeMinusSphereUrl() throws IOException {
241         // arrange
242         URL url = getClass().getResource(CUBE_MINUS_SPHERE_MODEL);
243 
244         // act
245         TriangleMesh mesh = reader.readTriangleMesh(url, TEST_PRECISION);
246 
247         // assert
248         Assert.assertEquals(CUBE_MINUS_SPHERE_VERTICES, mesh.getVertexCount());
249         Assert.assertEquals(CUBE_MINUS_SPHERE_FACES, mesh.getFaceCount());
250     }
251 }