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.io.euclidean.threed;
18  
19  import java.nio.file.Paths;
20  import java.util.Arrays;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.stream.Collectors;
24  import java.util.stream.Stream;
25  
26  import org.apache.commons.geometry.core.GeometryTestUtils;
27  import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
28  import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
29  import org.apache.commons.geometry.euclidean.threed.Planes;
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.SimpleTriangleMesh;
33  import org.apache.commons.geometry.euclidean.threed.mesh.TriangleMesh;
34  import org.apache.commons.geometry.io.core.GeometryFormat;
35  import org.apache.commons.geometry.io.core.input.FileGeometryInput;
36  import org.apache.commons.geometry.io.core.input.GeometryInput;
37  import org.apache.commons.geometry.io.core.output.FileGeometryOutput;
38  import org.apache.commons.geometry.io.core.output.GeometryOutput;
39  import org.apache.commons.geometry.io.core.test.StubGeometryFormat;
40  import org.apache.commons.numbers.core.Precision;
41  import org.junit.jupiter.api.Assertions;
42  import org.junit.jupiter.api.Test;
43  
44  class BoundaryIOManager3DTest {
45  
46      private static final double TEST_EPS = 1e-10;
47  
48      private static final Precision.DoubleEquivalence TEST_PRECISION = Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
49  
50      private static final GeometryFormat TEST_FMT = new StubGeometryFormat("test");
51  
52      private static final FacetDefinitionReader FACET_DEF_READER = new FacetDefinitionReader() {
53  
54          @Override
55          public FacetDefinition readFacet() {
56              throw new UnsupportedOperationException();
57          }
58  
59          @Override
60          public void close() {
61              // do nothing
62          }
63      };
64  
65      private static final FacetDefinition FACET = new SimpleFacetDefinition(Arrays.asList(
66              Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(1, 1, 0), Vector3D.of(0, 1, 0)));
67  
68      private static final Triangle3D TRI = Planes.triangleFromVertices(
69              Vector3D.ZERO, Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
70  
71      private static final TriangleMesh TRI_MESH = SimpleTriangleMesh.builder(TEST_PRECISION).build();
72  
73      private final BoundaryIOManager3D manager = new BoundaryIOManager3D();
74  
75      @Test
76      void testRegisterDefaultHandlers() {
77          // act
78          manager.registerDefaultHandlers();
79  
80          // assert
81          // ensure that we have default read/write handlers for every defined format
82          final GeometryFormat3D[] fmts = GeometryFormat3D.values();
83  
84          Assertions.assertEquals(fmts.length, manager.getReadHandlers().size());
85          Assertions.assertEquals(fmts.length, manager.getWriteHandlers().size());
86  
87          for (final GeometryFormat3D fmt : fmts) {
88              Assertions.assertNotNull(manager.getReadHandlerForFormat(fmt));
89              Assertions.assertNotNull(manager.getWriteHandlerForFormat(fmt));
90          }
91      }
92  
93      @Test
94      void testFacetDefinitionReader_formatGiven() {
95          // arrange
96          final StubReadHandler3D readHandler = new StubReadHandler3D();
97          manager.registerReadHandler(readHandler);
98  
99          final GeometryInput in = new FileGeometryInput(Paths.get("myfile"));
100 
101         // act
102         final FacetDefinitionReader result = manager.facetDefinitionReader(in, TEST_FMT);
103 
104         // assert
105         Assertions.assertSame(FACET_DEF_READER, result);
106         Assertions.assertSame(in, readHandler.inArg);
107     }
108 
109     @Test
110     void testFacetDefinitionReader_nullFormat() {
111         // arrange
112         final StubReadHandler3D readHandler = new StubReadHandler3D();
113         manager.registerReadHandler(readHandler);
114 
115         final GeometryInput in = new FileGeometryInput(Paths.get("myfile.test"));
116 
117         // act
118         final FacetDefinitionReader result = manager.facetDefinitionReader(in, null);
119 
120         // assert
121         Assertions.assertSame(FACET_DEF_READER, result);
122         Assertions.assertSame(in, readHandler.inArg);
123     }
124 
125     @Test
126     void testFacetDefinitionReader_unknownHandler() {
127         // act/assert
128         checkUnknownReadHandler(manager::facetDefinitionReader);
129     }
130 
131     @Test
132     void testFacets_formatGiven() {
133         // arrange
134         final StubReadHandler3D readHandler = new StubReadHandler3D();
135         manager.registerReadHandler(readHandler);
136 
137         final GeometryInput in = new FileGeometryInput(Paths.get("myfile"));
138 
139         // act
140         final Stream<FacetDefinition> result = manager.facets(in, TEST_FMT);
141 
142         // assert
143         Assertions.assertEquals(Collections.singletonList(FACET), result.collect(Collectors.toList()));
144         Assertions.assertSame(in, readHandler.inArg);
145     }
146 
147     @Test
148     void testFacets_nullFormat() {
149         // arrange
150         final StubReadHandler3D readHandler = new StubReadHandler3D();
151         manager.registerReadHandler(readHandler);
152 
153         final GeometryInput in = new FileGeometryInput(Paths.get("myfile.test"));
154 
155         // act
156         final Stream<FacetDefinition> result = manager.facets(in, null);
157 
158         // assert
159         Assertions.assertEquals(Collections.singletonList(FACET), result.collect(Collectors.toList()));
160         Assertions.assertSame(in, readHandler.inArg);
161     }
162 
163     @Test
164     void testFacets_unknownHandler() {
165         // act/assert
166         checkUnknownReadHandler(manager::facets);
167     }
168 
169     @Test
170     void testTriangles_formatGiven() {
171         // arrange
172         final StubReadHandler3D readHandler = new StubReadHandler3D();
173         manager.registerReadHandler(readHandler);
174 
175         final GeometryInput in = new FileGeometryInput(Paths.get("myfile"));
176 
177         // act
178         final Stream<Triangle3D> result = manager.triangles(in, TEST_FMT, TEST_PRECISION);
179 
180         // assert
181         Assertions.assertEquals(Collections.singletonList(TRI), result.collect(Collectors.toList()));
182         Assertions.assertSame(in, readHandler.inArg);
183         Assertions.assertSame(TEST_PRECISION, readHandler.precisionArg);
184     }
185 
186     @Test
187     void testTriangles_nullFormat() {
188         // arrange
189         final StubReadHandler3D readHandler = new StubReadHandler3D();
190         manager.registerReadHandler(readHandler);
191 
192         final GeometryInput in = new FileGeometryInput(Paths.get("myfile.test"));
193 
194         // act
195         final Stream<Triangle3D> result = manager.triangles(in, null, TEST_PRECISION);
196 
197         // assert
198         Assertions.assertEquals(Collections.singletonList(TRI), result.collect(Collectors.toList()));
199         Assertions.assertSame(in, readHandler.inArg);
200         Assertions.assertSame(TEST_PRECISION, readHandler.precisionArg);
201     }
202 
203     @Test
204     void testTriangles_unknownHandler() {
205         // act/assert
206         checkUnknownReadHandler((in, fmt) -> manager.triangles(in, fmt, TEST_PRECISION));
207     }
208 
209     @Test
210     void testReadTriangleMesh_formatGiven() {
211         // arrange
212         final StubReadHandler3D readHandler = new StubReadHandler3D();
213         manager.registerReadHandler(readHandler);
214 
215         final GeometryInput in = new FileGeometryInput(Paths.get("myfile"));
216 
217         // act
218         final TriangleMesh result = manager.readTriangleMesh(in, TEST_FMT, TEST_PRECISION);
219 
220         // assert
221         Assertions.assertEquals(TRI_MESH, result);
222         Assertions.assertSame(in, readHandler.inArg);
223         Assertions.assertSame(TEST_PRECISION, readHandler.precisionArg);
224     }
225 
226     @Test
227     void testReadTriangleMesh_nullFormat() {
228         // arrange
229         final StubReadHandler3D readHandler = new StubReadHandler3D();
230         manager.registerReadHandler(readHandler);
231 
232         final GeometryInput in = new FileGeometryInput(Paths.get("myfile.test"));
233 
234         // act
235         final TriangleMesh result = manager.readTriangleMesh(in, null, TEST_PRECISION);
236 
237         // assert
238         Assertions.assertEquals(TRI_MESH, result);
239         Assertions.assertSame(in, readHandler.inArg);
240         Assertions.assertSame(TEST_PRECISION, readHandler.precisionArg);
241     }
242 
243     @Test
244     void testReadTriangleMesh_unknownHandler() {
245         // act/assert
246         checkUnknownReadHandler((in, fmt) -> manager.readTriangleMesh(in, fmt, TEST_PRECISION));
247     }
248 
249     @Test
250     void testWrite_stream_formatGiven() {
251         // arrange
252         final StubWriteHandler3D writeHandler = new StubWriteHandler3D();
253         manager.registerWriteHandler(writeHandler);
254 
255         final GeometryOutput out = new FileGeometryOutput(Paths.get("myfile"));
256 
257         // act
258         manager.write(Stream.of(TRI), out, TEST_FMT);
259 
260         // assert
261         Assertions.assertEquals(Collections.singletonList(TRI), writeHandler.boundariesArg);
262         Assertions.assertSame(out, writeHandler.outArg);
263     }
264 
265     @Test
266     void testWrite_stream_nullFormat() {
267         // arrange
268         final StubWriteHandler3D writeHandler = new StubWriteHandler3D();
269         manager.registerWriteHandler(writeHandler);
270 
271         final GeometryOutput out = new FileGeometryOutput(Paths.get("myfile.TEST"));
272 
273         // act
274         manager.write(Stream.of(TRI), out, null);
275 
276         // assert
277         Assertions.assertEquals(Collections.singletonList(TRI), writeHandler.boundariesArg);
278         Assertions.assertSame(out, writeHandler.outArg);
279     }
280 
281     @Test
282     void testWrite_stream_unknownHandler() {
283         // act/assert
284         checkUnknownWriteHandler((out, fmt) -> manager.write(Stream.of(TRI), out, fmt));
285     }
286 
287     @Test
288     void testWriteFacets_stream_formatGiven() {
289         // arrange
290         final StubWriteHandler3D writeHandler = new StubWriteHandler3D();
291         manager.registerWriteHandler(writeHandler);
292 
293         final GeometryOutput out = new FileGeometryOutput(Paths.get("myfile"));
294 
295         // act
296         manager.writeFacets(Stream.of(FACET), out, TEST_FMT);
297 
298         // assert
299         Assertions.assertEquals(Collections.singletonList(FACET), writeHandler.facetsArg);
300         Assertions.assertSame(out, writeHandler.outArg);
301     }
302 
303     @Test
304     void testWriteFacets_stream_nullFormat() {
305         // arrange
306         final StubWriteHandler3D writeHandler = new StubWriteHandler3D();
307         manager.registerWriteHandler(writeHandler);
308 
309         final GeometryOutput out = new FileGeometryOutput(Paths.get("myfile.TEST"));
310 
311         // act
312         manager.writeFacets(Stream.of(FACET), out, null);
313 
314         // assert
315         Assertions.assertEquals(Collections.singletonList(FACET), writeHandler.facetsArg);
316         Assertions.assertSame(out, writeHandler.outArg);
317     }
318 
319     @Test
320     void testWriteFacets_stream_unknownHandler() {
321         // act/assert
322         checkUnknownWriteHandler((out, fmt) -> manager.writeFacets(Stream.of(FACET), out, fmt));
323     }
324 
325     @Test
326     void testWriteFacets_collection_formatGiven() {
327         // arrange
328         final StubWriteHandler3D writeHandler = new StubWriteHandler3D();
329         manager.registerWriteHandler(writeHandler);
330 
331         final GeometryOutput out = new FileGeometryOutput(Paths.get("myfile"));
332 
333         // act
334         manager.writeFacets(Collections.singletonList(FACET), out, TEST_FMT);
335 
336         // assert
337         Assertions.assertEquals(Collections.singletonList(FACET), writeHandler.facetsArg);
338         Assertions.assertSame(out, writeHandler.outArg);
339     }
340 
341     @Test
342     void testWriteFacets_collection_nullFormat() {
343         // arrange
344         final StubWriteHandler3D writeHandler = new StubWriteHandler3D();
345         manager.registerWriteHandler(writeHandler);
346 
347         final GeometryOutput out = new FileGeometryOutput(Paths.get("myfile.TEST"));
348 
349         // act
350         manager.writeFacets(Collections.singletonList(FACET), out, null);
351 
352         // assert
353         Assertions.assertEquals(Collections.singletonList(FACET), writeHandler.facetsArg);
354         Assertions.assertSame(out, writeHandler.outArg);
355     }
356 
357     @Test
358     void testWriteFacets_collection_unknownHandler() {
359         // act/assert
360         checkUnknownWriteHandler((out, fmt) -> manager.writeFacets(Collections.singletonList(FACET), out, fmt));
361     }
362 
363     private static void checkUnknownReadHandler(final ThrowingBiConsumer<GeometryInput, GeometryFormat> fn) {
364         // arrange
365         final GeometryInput withFileExt = new FileGeometryInput(Paths.get("myfile.test"));
366         final GeometryInput noFileExt = new FileGeometryInput(Paths.get("myfile"));
367 
368         // act/assert
369         GeometryTestUtils.assertThrowsWithMessage(
370                 () -> fn.accept(withFileExt, TEST_FMT),
371                 IllegalArgumentException.class, "Failed to find handler for format \"test\"");
372 
373         GeometryTestUtils.assertThrowsWithMessage(
374                 () -> fn.accept(withFileExt, null),
375                 IllegalArgumentException.class, "Failed to find handler for file extension \"test\"");
376 
377         GeometryTestUtils.assertThrowsWithMessage(
378                 () -> fn.accept(noFileExt, null),
379                 IllegalArgumentException.class, "Failed to find handler: no format specified and no file extension available");
380     }
381 
382     private static void checkUnknownWriteHandler(final ThrowingBiConsumer<GeometryOutput, GeometryFormat> fn) {
383         // arrange
384         final GeometryOutput withFileExt = new FileGeometryOutput(Paths.get("myfile.test"));
385         final GeometryOutput noFileExt = new FileGeometryOutput(Paths.get("myfile"));
386 
387         // act/assert
388         GeometryTestUtils.assertThrowsWithMessage(
389                 () -> fn.accept(withFileExt, TEST_FMT),
390                 IllegalArgumentException.class, "Failed to find handler for format \"test\"");
391 
392         GeometryTestUtils.assertThrowsWithMessage(
393                 () -> fn.accept(withFileExt, null),
394                 IllegalArgumentException.class, "Failed to find handler for file extension \"test\"");
395 
396         GeometryTestUtils.assertThrowsWithMessage(
397                 () -> fn.accept(noFileExt, null),
398                 IllegalArgumentException.class, "Failed to find handler: no format specified and no file extension available");
399     }
400 
401     @FunctionalInterface
402     private interface ThrowingBiConsumer<T, V> {
403         void accept(T t, V v) throws Exception;
404     }
405 
406     private static final class StubReadHandler3D implements BoundaryReadHandler3D {
407 
408         private GeometryInput inArg;
409 
410         private Precision.DoubleEquivalence precisionArg;
411 
412         /** {@inheritDoc} */
413         @Override
414         public GeometryFormat getFormat() {
415             return TEST_FMT;
416         }
417 
418         /** {@inheritDoc} */
419         @Override
420         public BoundarySource3D read(final GeometryInput in, final Precision.DoubleEquivalence precision) {
421             throw new UnsupportedOperationException();
422         }
423 
424         /** {@inheritDoc} */
425         @Override
426         public Stream<PlaneConvexSubset> boundaries(final GeometryInput in,
427                 final Precision.DoubleEquivalence precision) {
428             this.inArg = in;
429             this.precisionArg = precision;
430 
431             return Stream.of(TRI);
432         }
433 
434         /** {@inheritDoc} */
435         @Override
436         public FacetDefinitionReader facetDefinitionReader(final GeometryInput in) {
437             this.inArg = in;
438 
439             return FACET_DEF_READER;
440         }
441 
442         /** {@inheritDoc} */
443         @Override
444         public Stream<FacetDefinition> facets(final GeometryInput in) {
445             this.inArg = in;
446 
447             return Stream.of(FACET);
448         }
449 
450         /** {@inheritDoc} */
451         @Override
452         public TriangleMesh readTriangleMesh(final GeometryInput in, final Precision.DoubleEquivalence precision) {
453             this.inArg = in;
454             this.precisionArg = precision;
455 
456             return TRI_MESH;
457         }
458     }
459 
460     private static final class StubWriteHandler3D implements BoundaryWriteHandler3D {
461 
462         private Collection<? extends PlaneConvexSubset> boundariesArg;
463 
464         private Collection<? extends FacetDefinition> facetsArg;
465 
466         private GeometryOutput outArg;
467 
468         /** {@inheritDoc} */
469         @Override
470         public GeometryFormat getFormat() {
471             return TEST_FMT;
472         }
473 
474         /** {@inheritDoc} */
475         @Override
476         public void write(final Stream<? extends PlaneConvexSubset> boundaries, final GeometryOutput out) {
477             this.boundariesArg = boundaries.collect(Collectors.toList());
478             this.outArg = out;
479         }
480 
481         /** {@inheritDoc} */
482         @Override
483         public void write(final BoundarySource3D src, final GeometryOutput out) {
484             throw new UnsupportedOperationException();
485         }
486 
487         /** {@inheritDoc} */
488         @Override
489         public void writeFacets(final Stream<? extends FacetDefinition> facets, final GeometryOutput out) {
490             this.facetsArg = facets.collect(Collectors.toList());
491             this.outArg = out;
492         }
493 
494         /** {@inheritDoc} */
495         @Override
496         public void writeFacets(final Collection<? extends FacetDefinition> facets, final GeometryOutput out) {
497             this.facetsArg = facets;
498             this.outArg = out;
499         }
500     }
501 }