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 }