1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
51 StringWriter writer = new StringWriter();
52
53
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
63 StringWriter writer = new StringWriter();
64
65
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
75 StringWriter writer = new StringWriter();
76
77
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
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
96 StringWriter writer = new StringWriter();
97
98
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
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
112 StringWriter writer = new StringWriter();
113
114
115 try (OBJWriter meshWriter = new OBJWriter(writer)) {
116 meshWriter.writeComment("test");
117 meshWriter.writeComment(" a\r\n multi-line\ncomment");
118 }
119
120
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
131 StringWriter writer = new StringWriter();
132
133
134 try (OBJWriter meshWriter = new OBJWriter(writer)) {
135 meshWriter.writeObjectName("test-object");
136 }
137
138
139 Assert.assertEquals("o test-object\n", writer.getBuffer().toString());
140 }
141
142 @Test
143 public void testWriteGroupName() throws IOException {
144
145 StringWriter writer = new StringWriter();
146
147
148 try (OBJWriter meshWriter = new OBJWriter(writer)) {
149 meshWriter.writeGroupName("test-group");
150 }
151
152
153 Assert.assertEquals("g test-group\n", writer.getBuffer().toString());
154 }
155
156 @Test
157 public void testWriteVertex() throws IOException {
158
159 StringWriter writer = new StringWriter();
160
161
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
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
182 StringWriter writer = new StringWriter();
183
184
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
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
208 StringWriter writer = new StringWriter();
209
210
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
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
231 try (OBJWriter meshWriter = new OBJWriter(writer)) {
232 meshWriter.writeMesh(mesh);
233 }
234
235
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
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
256 try (OBJWriter meshWriter = new OBJWriter(writer)) {
257 meshWriter.writeBoundaries(mesh);
258 }
259
260
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
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
281 try (OBJWriter meshWriter = new OBJWriter(writer)) {
282 meshWriter.writeBoundaries(src);
283 }
284
285
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
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
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
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
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
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
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 }