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 }