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.IOException;
20  import java.io.StringWriter;
21  import java.io.UncheckedIOException;
22  import java.nio.file.Files;
23  import java.nio.file.Path;
24  import java.text.DecimalFormat;
25  import java.util.regex.Pattern;
26  
27  import org.apache.commons.geometry.core.GeometryTestUtils;
28  import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
29  import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
30  import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
31  import org.apache.commons.geometry.euclidean.threed.Planes;
32  import org.apache.commons.geometry.euclidean.threed.RegionBSPTree3D;
33  import org.apache.commons.geometry.euclidean.threed.Vector3D;
34  import org.apache.commons.geometry.euclidean.threed.mesh.SimpleTriangleMesh;
35  import org.apache.commons.geometry.euclidean.threed.mesh.TriangleMesh;
36  import org.apache.commons.geometry.euclidean.threed.shape.Parallelepiped;
37  import org.apache.commons.geometry.euclidean.threed.shape.Sphere;
38  import org.junit.Assert;
39  import org.junit.Test;
40  
41  public class OBJWriterTest {
42  
43      private static final double TEST_EPS = 1e-10;
44  
45      private static final DoublePrecisionContext TEST_PRECISION =
46              new EpsilonDoublePrecisionContext(TEST_EPS);
47  
48      @Test
49      public void testDefaults() throws IOException {
50          // arrange
51          StringWriter writer = new StringWriter();
52  
53          // act/assert
54          try (OBJWriter meshWriter = new OBJWriter(writer)) {
55              Assert.assertEquals("\n", meshWriter.getLineSeparator());
56              Assert.assertEquals(6, meshWriter.getDecimalFormat().getMaximumFractionDigits());
57          }
58      }
59  
60      @Test
61      public void testClose_calledMultipleTimes() throws IOException {
62          // arrange
63          StringWriter writer = new StringWriter();
64  
65          // act/assert
66          try (OBJWriter meshWriter = new OBJWriter(writer)) {
67              meshWriter.close();
68              meshWriter.close();
69          }
70      }
71  
72      @Test
73      public void testSetLineSeparator() throws IOException {
74          // arrange
75          StringWriter writer = new StringWriter();
76  
77          // act
78          try (OBJWriter meshWriter = new OBJWriter(writer)) {
79              meshWriter.setLineSeparator("\r\n");
80  
81              meshWriter.writeComment("line 1");
82              meshWriter.writeComment("line 2");
83              meshWriter.writeVertex(Vector3D.ZERO);
84          }
85  
86          // assert
87          Assert.assertEquals(
88              "# line 1\r\n" +
89              "# line 2\r\n" +
90              "v 0 0 0\r\n", writer.getBuffer().toString());
91      }
92  
93      @Test
94      public void testSetDecimalFormat() throws IOException {
95          // arrange
96          StringWriter writer = new StringWriter();
97  
98          // act
99          try (OBJWriter meshWriter = new OBJWriter(writer)) {
100             meshWriter.setDecimalFormat(new DecimalFormat("00.0"));
101 
102             meshWriter.writeVertex(Vector3D.of(1, 2, 3));
103         }
104 
105         // assert
106         Assert.assertEquals("v 01.0 02.0 03.0\n", writer.getBuffer().toString());
107     }
108 
109     @Test
110     public void testWriteComment() throws IOException {
111         // arrange
112         StringWriter writer = new StringWriter();
113 
114         // act
115         try (OBJWriter meshWriter = new OBJWriter(writer)) {
116             meshWriter.writeComment("test");
117             meshWriter.writeComment(" a\r\n multi-line\ncomment");
118         }
119 
120         // assert
121         Assert.assertEquals(
122             "# test\n" +
123             "#  a\n" +
124             "#  multi-line\n" +
125             "# comment\n", writer.getBuffer().toString());
126     }
127 
128     @Test
129     public void testWriteObjectName() throws IOException {
130         // arrange
131         StringWriter writer = new StringWriter();
132 
133         // act
134         try (OBJWriter meshWriter = new OBJWriter(writer)) {
135             meshWriter.writeObjectName("test-object");
136         }
137 
138         // assert
139         Assert.assertEquals("o test-object\n", writer.getBuffer().toString());
140     }
141 
142     @Test
143     public void testWriteGroupName() throws IOException {
144         // arrange
145         StringWriter writer = new StringWriter();
146 
147         // act
148         try (OBJWriter meshWriter = new OBJWriter(writer)) {
149             meshWriter.writeGroupName("test-group");
150         }
151 
152         // assert
153         Assert.assertEquals("g test-group\n", writer.getBuffer().toString());
154     }
155 
156     @Test
157     public void testWriteVertex() throws IOException {
158         // arrange
159         StringWriter writer = new StringWriter();
160 
161         // act
162         int index1;
163         int index2;
164         try (OBJWriter meshWriter = new OBJWriter(writer)) {
165             meshWriter.getDecimalFormat().setMaximumFractionDigits(1);
166 
167             index1 = meshWriter.writeVertex(Vector3D.of(1.09, 2.1, 3.005));
168             index2 = meshWriter.writeVertex(Vector3D.of(0.06, 10, 12));
169         }
170 
171         // assert
172         Assert.assertEquals(1, index1);
173         Assert.assertEquals(2, index2);
174         Assert.assertEquals(
175             "v 1.1 2.1 3\n" +
176             "v 0.1 10 12\n", writer.getBuffer().toString());
177     }
178 
179     @Test
180     public void testWriteFace() throws IOException {
181         // arrange
182         StringWriter writer = new StringWriter();
183 
184         // act
185         try (OBJWriter meshWriter = new OBJWriter(writer)) {
186             meshWriter.writeVertex(Vector3D.ZERO);
187             meshWriter.writeVertex(Vector3D.of(1, 0, 0));
188             meshWriter.writeVertex(Vector3D.of(1, 1, 0));
189             meshWriter.writeVertex(Vector3D.of(0, 1, 0));
190 
191             meshWriter.writeFace(1, 2, 3);
192             meshWriter.writeFace(1, 2, 3, -1);
193         }
194 
195         // assert
196         Assert.assertEquals(
197             "v 0 0 0\n" +
198             "v 1 0 0\n" +
199             "v 1 1 0\n" +
200             "v 0 1 0\n" +
201             "f 1 2 3\n" +
202             "f 1 2 3 -1\n", writer.getBuffer().toString());
203     }
204 
205     @Test
206     public void testWriteFace_invalidVertexNumber() throws IOException {
207         // arrange
208         StringWriter writer = new StringWriter();
209 
210         // act
211         GeometryTestUtils.assertThrows(() -> {
212             try (OBJWriter meshWriter = new OBJWriter(writer)) {
213                 meshWriter.writeFace(1, 2);
214             } catch (IOException exc) {
215                 throw new UncheckedIOException(exc);
216             }
217         }, IllegalArgumentException.class, "Face must have more than 3 vertices; found 2");
218     }
219 
220     @Test
221     public void testWriteMesh() throws IOException {
222         // arrange
223         SimpleTriangleMesh mesh = SimpleTriangleMesh.builder(TEST_PRECISION)
224                 .addFaceUsingVertices(Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(0, 1, 0))
225                 .addFaceUsingVertices(Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(0, 0, 1))
226                 .build();
227 
228         StringWriter writer = new StringWriter();
229 
230         // act
231         try (OBJWriter meshWriter = new OBJWriter(writer)) {
232             meshWriter.writeMesh(mesh);
233         }
234 
235         // assert
236         Assert.assertEquals(
237             "v 0 0 0\n" +
238             "v 1 0 0\n" +
239             "v 0 1 0\n" +
240             "v 0 0 1\n" +
241             "f 1 2 3\n" +
242             "f 1 2 4\n", writer.getBuffer().toString());
243     }
244 
245     @Test
246     public void testWriteBoundaries_meshArgument() throws IOException {
247         // arrange
248         SimpleTriangleMesh mesh = SimpleTriangleMesh.builder(TEST_PRECISION)
249                 .addFaceUsingVertices(Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(0, 1, 0))
250                 .addFaceUsingVertices(Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(0, 0, 1))
251                 .build();
252 
253         StringWriter writer = new StringWriter();
254 
255         // act
256         try (OBJWriter meshWriter = new OBJWriter(writer)) {
257             meshWriter.writeBoundaries(mesh);
258         }
259 
260         // assert
261         Assert.assertEquals(
262             "v 0 0 0\n" +
263             "v 1 0 0\n" +
264             "v 0 1 0\n" +
265             "v 0 0 1\n" +
266             "f 1 2 3\n" +
267             "f 1 2 4\n", writer.getBuffer().toString());
268     }
269 
270     @Test
271     public void testWriteBoundaries_nonMeshArgument() throws IOException {
272         // arrange
273         BoundarySource3D src = BoundarySource3D.from(
274                     Planes.triangleFromVertices(Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(0, 1, 0), TEST_PRECISION),
275                     Planes.triangleFromVertices(Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(0, 0, 1), TEST_PRECISION)
276                 );
277 
278         StringWriter writer = new StringWriter();
279 
280         // act
281         try (OBJWriter meshWriter = new OBJWriter(writer)) {
282             meshWriter.writeBoundaries(src);
283         }
284 
285         // assert
286         Assert.assertEquals(
287             "v 0 0 0\n" +
288             "v 1 0 0\n" +
289             "v 0 1 0\n" +
290             "f 1 2 3\n" +
291             "v 0 0 0\n" +
292             "v 1 0 0\n" +
293             "v 0 0 1\n" +
294             "f 4 5 6\n", writer.getBuffer().toString());
295     }
296 
297     @Test
298     public void testWriteBoundaries_infiniteBoundary() throws IOException {
299         // arrange
300         BoundarySource3D src = BoundarySource3D.from(
301                     Planes.triangleFromVertices(Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(0, 1, 0), TEST_PRECISION),
302                     Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION).span()
303                 );
304 
305         StringWriter writer = new StringWriter();
306 
307         // act/assert
308         GeometryTestUtils.assertThrows(() -> {
309             try (OBJWriter meshWriter = new OBJWriter(writer)) {
310                 meshWriter.writeBoundaries(src);
311             } catch (IOException exc) {
312                 throw new UncheckedIOException(exc);
313             }
314         }, IllegalArgumentException.class, Pattern.compile("^OBJ input geometry cannot be infinite: .*"));
315     }
316 
317     @Test
318     public void testWriteToFile_boundaries() throws IOException {
319         // arrange
320         RegionBSPTree3D box = Parallelepiped.unitCube(TEST_PRECISION).toTree();
321         RegionBSPTree3D sphere = Sphere.from(Vector3D.ZERO, 0.6, TEST_PRECISION)
322                 .toTree(3);
323 
324         RegionBSPTree3D result = RegionBSPTree3D.empty();
325         result.difference(box, sphere);
326 
327         TriangleMesh mesh = result.toTriangleMesh(TEST_PRECISION);
328 
329         // act
330         Path out = Files.createTempFile("objTest", ".obj");
331         try (OBJWriter writer = new OBJWriter(out.toFile())) {
332             writer.writeComment("A test obj file\nWritten by " + OBJReaderTest.class.getName());
333 
334             writer.writeBoundaries(mesh);
335         } finally {
336             Files.delete(out);
337         }
338     }
339 
340     @Test
341     public void testWriteToFile_mesh() throws IOException {
342         // arrange
343         RegionBSPTree3D box = Parallelepiped.unitCube(TEST_PRECISION).toTree();
344         RegionBSPTree3D sphere = Sphere.from(Vector3D.ZERO, 0.6, TEST_PRECISION)
345                 .toTree(3);
346 
347         RegionBSPTree3D result = RegionBSPTree3D.empty();
348         result.difference(box, sphere);
349 
350         // act
351         Path out = Files.createTempFile("objTest", ".obj");
352         try (OBJWriter writer = new OBJWriter(out.toFile())) {
353             writer.writeComment("A test obj file\nWritten by " + OBJReaderTest.class.getName());
354 
355             writer.writeBoundaries(result);
356         } finally {
357             Files.delete(out);
358         }
359     }
360 }