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.txt;
    18
    19  import java.io.StringWriter;
    20  import java.text.DecimalFormat;
    21  import java.text.DecimalFormatSymbols;
    22  import java.util.Arrays;
    23  import java.util.Collections;
    24  import java.util.List;
    25  import java.util.Locale;
    26
    27  import org.apache.commons.geometry.core.GeometryTestUtils;
    28  import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
    29  import org.apache.commons.geometry.euclidean.threed.ConvexPolygon3D;
    30  import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
    31  import org.apache.commons.geometry.euclidean.threed.Planes;
    32  import org.apache.commons.geometry.euclidean.threed.Vector3D;
    33  import org.apache.commons.geometry.io.euclidean.threed.SimpleFacetDefinition;
    34  import org.apache.commons.numbers.core.Precision;
    35  import org.junit.jupiter.api.Assertions;
    36  import org.junit.jupiter.api.BeforeEach;
    37  import org.junit.jupiter.api.Test;
    38
    39  class TextFacetDefinitionWriterTest {
    40
    41      private static final double TEST_EPS = 1e-10;
    42
    43      private static final Precision.DoubleEquivalence TEST_PRECISION =
    44              Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
    45
    46      private StringWriter writer;
    47
    48      private TextFacetDefinitionWriter fdWriter;
    49
    50      @BeforeEach
    51      public void setup() {
    52          writer = new StringWriter();
    53          fdWriter = new TextFacetDefinitionWriter(writer);
    54      }
    55
    56      @Test
    57      void testPropertyDefaults() {
    58          // act/assert
    59          Assertions.assertEquals("\n", fdWriter.getLineSeparator());
    60          Assertions.assertNotNull(fdWriter.getDoubleFormat());
    61          Assertions.assertEquals(" ", fdWriter.getVertexComponentSeparator());
    62          Assertions.assertEquals("; ", fdWriter.getVertexSeparator());
    63          Assertions.assertEquals(-1, fdWriter.getFacetVertexCount());
    64          Assertions.assertEquals("# ", fdWriter.getCommentToken());
    65      }
    66
    67      @Test
    68      void testSetFacetVertexCount_normalizesToMinusOne() {
    69          // act
    70          fdWriter.setFacetVertexCount(-10);
    71
    72          // assert
    73          Assertions.assertEquals(-1, fdWriter.getFacetVertexCount());
    74      }
    75
    76      @Test
    77      void testSetFacetVertexCount_invalidArgs() {
    78          // arrange
    79          final String baseMsg = "Facet vertex count must be less than 0 or greater than 2; was ";
    80
    81          // act
    82          GeometryTestUtils.assertThrowsWithMessage(() -> {
    83              fdWriter.setFacetVertexCount(0);
    84          }, IllegalArgumentException.class, baseMsg + "0");
    85
    86          GeometryTestUtils.assertThrowsWithMessage(() -> {
    87              fdWriter.setFacetVertexCount(1);
    88          }, IllegalArgumentException.class, baseMsg + "1");
    89
    90          GeometryTestUtils.assertThrowsWithMessage(() -> {
    91              fdWriter.setFacetVertexCount(2);
    92          }, IllegalArgumentException.class, baseMsg + "2");
    93      }
    94
    95      @Test
    96      void testSetCommentToken_invalidArgs() {
    97          // act/assert
    98          GeometryTestUtils.assertThrowsWithMessage(() -> {
    99              fdWriter.setCommentToken("");
    100         }, IllegalArgumentException.class, "Comment token cannot be empty");
    101
    102         GeometryTestUtils.assertThrowsWithMessage(() -> {
    103             fdWriter.setCommentToken(" ");
    104         }, IllegalArgumentException.class, "Comment token cannot begin with whitespace");
    105
    106         GeometryTestUtils.assertThrowsWithMessage(() -> {
    107             fdWriter.setCommentToken("\n \t");
    108         }, IllegalArgumentException.class, "Comment token cannot begin with whitespace");
    109     }
    110
    111     @Test
    112     void testWriteComment() {
    113         // arrange
    114         fdWriter.setCommentToken("-- ");
    115         fdWriter.setLineSeparator("\r\n");
    116
    117         // act
    118         fdWriter.writeComment("first line");
    119         fdWriter.writeComment(null);
    120         fdWriter.writeComment("second line \n third line \r\nfourth line");
    121
    122         // assert
    123         Assertions.assertEquals(
    124                 "-- first line\r\n" +
    125                 "-- second line \r\n" +
    126                 "--  third line \r\n" +
    127                 "-- fourth line\r\n", writer.toString());
    128     }
    129
    130     @Test
    131     void testWriteComment_noCommentToken() {
    132         // arrange
    133         fdWriter.setCommentToken(null);
    134
    135         // act/assert
    136         GeometryTestUtils.assertThrowsWithMessage(() -> {
    137             fdWriter.writeComment("comment");
    138         }, IllegalStateException.class, "Cannot write comment: no comment token configured");
    139     }
    140
    141     @Test
    142     void testWriteBlankLine() {
    143         // act
    144         fdWriter.writeBlankLine();
    145         fdWriter.setLineSeparator("\r");
    146         fdWriter.writeBlankLine();
    147
    148         // assert
    149         Assertions.assertEquals("\n\r", writer.toString());
    150     }
    151
    152     @Test
    153     void testWriteVertices() {
    154         // arrange
    155         final List<Vector3D> vertices1 = Arrays.asList(
    156                 Vector3D.ZERO, Vector3D.of(0.5, 0, 0), Vector3D.of(0, -0.5, 0));
    157         final List<Vector3D> vertices2 = Arrays.asList(
    158                 Vector3D.of(0.5, 0.7, 1.2), Vector3D.of(10.01, -4, 2), Vector3D.of(-10.0 / 3.0, 0, 0), Vector3D.ZERO);
    159
    160         // act
    161         fdWriter.write(vertices1);
    162         fdWriter.write(vertices2);
    163
    164         // assert
    165         Assertions.assertEquals(
    166                 "0.0 0.0 0.0; 0.5 0.0 0.0; 0.0 -0.5 0.0\n" +
    167                 "0.5 0.7 1.2; 10.01 -4.0 2.0; -3.3333333333333335 0.0 0.0; 0.0 0.0 0.0\n", writer.toString());
    168     }
    169
    170     @Test
    171     void testWriteVertices_invalidCount() {
    172         // arrange
    173         fdWriter.setFacetVertexCount(4);
    174         final List<Vector3D> notEnough = Arrays.asList(Vector3D.ZERO, Vector3D.Unit.PLUS_X);
    175         final List<Vector3D> tooMany = Arrays.asList(
    176                 Vector3D.ZERO, Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y,
    177                 Vector3D.Unit.MINUS_X, Vector3D.Unit.MINUS_Y);
    178
    179         // act/assert
    180         GeometryTestUtils.assertThrowsWithMessage(() -> {
    181             fdWriter.write(notEnough);
    182         }, IllegalArgumentException.class, "At least 3 vertices are required per facet; found 2");
    183
    184         GeometryTestUtils.assertThrowsWithMessage(() -> {
    185             fdWriter.write(tooMany);
    186         }, IllegalArgumentException.class, "Writer requires 4 vertices per facet; found 5");
    187     }
    188
    189     @Test
    190     void testWriteFacetDefinition() {
    191         // arrange
    192         final DecimalFormat fmt =
    193                 new DecimalFormat("0.0##", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
    194
    195         final SimpleFacetDefinition f1 = new SimpleFacetDefinition(Arrays.asList(
    196                 Vector3D.ZERO, Vector3D.of(0.5, 0, 0), Vector3D.of(0, -0.5, 0)));
    197         final SimpleFacetDefinition f2 = new SimpleFacetDefinition(Arrays.asList(
    198                 Vector3D.of(0.5, 0.7, 1.2), Vector3D.of(10.01, -4, 2), Vector3D.of(-10.0 / 3.0, 0, 0), Vector3D.ZERO));
    199
    200         fdWriter.setDoubleFormat(fmt::format);
    201
    202         // act
    203         fdWriter.write(f1);
    204         fdWriter.write(f2);
    205
    206         // assert
    207         Assertions.assertEquals(
    208                 "0.0 0.0 0.0; 0.5 0.0 0.0; 0.0 -0.5 0.0\n" +
    209                 "0.5 0.7 1.2; 10.01 -4.0 2.0; -3.333 0.0 0.0; 0.0 0.0 0.0\n", writer.toString());
    210     }
    211
    212     @Test
    213     void testWriteFacetDefinition_invalidCount() {
    214         // arrange
    215         fdWriter.setFacetVertexCount(4);
    216         final SimpleFacetDefinition tooMany = new SimpleFacetDefinition(Arrays.asList(
    217                 Vector3D.ZERO, Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y,
    218                 Vector3D.Unit.MINUS_X, Vector3D.Unit.MINUS_Y));
    219
    220         // act/assert
    221         GeometryTestUtils.assertThrowsWithMessage(() -> {
    222             fdWriter.write(tooMany);
    223         }, IllegalArgumentException.class, "Writer requires 4 vertices per facet; found 5");
    224     }
    225
    226     @Test
    227     void testWritePlaneConvexSubset() {
    228         // arrange
    229         final ConvexPolygon3D poly1 = Planes.convexPolygonFromVertices(Arrays.asList(
    230                     Vector3D.ZERO, Vector3D.of(0, 0, -0.5), Vector3D.of(0, -0.5, 0)
    231                 ), TEST_PRECISION);
    232         final ConvexPolygon3D poly2 = Planes.convexPolygonFromVertices(Arrays.asList(
    233                 Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(1, 1, 0), Vector3D.of(0, 1, 0)
    234             ), TEST_PRECISION);
    235
    236         // act
    237         fdWriter.write(poly1);
    238         fdWriter.write(poly2);
    239
    240         // assert
    241         Assertions.assertEquals(
    242                 "0.0 0.0 0.0; 0.0 0.0 -0.5; 0.0 -0.5 0.0\n" +
    243                 "0.0 0.0 0.0; 1.0 0.0 0.0; 1.0 1.0 0.0; 0.0 1.0 0.0\n", writer.toString());
    244     }
    245
    246     @Test
    247     void testWritePlaneConvexSubset_convertsToTriangles() {
    248         // arrange
    249         final ConvexPolygon3D poly = Planes.convexPolygonFromVertices(Arrays.asList(
    250                     Vector3D.ZERO, Vector3D.of(0, 1, 0), Vector3D.of(0, 1, 1), Vector3D.of(0, 0, 1)
    251                 ), TEST_PRECISION);
    252
    253         fdWriter.setFacetVertexCount(3);
    254
    255         // act
    256         fdWriter.write(poly);
    257
    258         // assert
    259         Assertions.assertEquals(
    260                 "0.0 0.0 0.0; 0.0 1.0 0.0; 0.0 1.0 1.0\n" +
    261                 "0.0 0.0 0.0; 0.0 1.0 1.0; 0.0 0.0 1.0\n", writer.toString());
    262     }
    263
    264     @Test
    265     void testWritePlaneConvexSubset_infinite() {
    266         // arrange
    267         final PlaneConvexSubset inf = Planes.fromNormal(Vector3D.Unit.PLUS_X, TEST_PRECISION).span();
    268
    269         // act/assert
    270         GeometryTestUtils.assertThrowsWithMessage(() -> {
    271             fdWriter.write(inf);
    272         }, IllegalArgumentException.class, "Cannot write infinite convex subset");
    273     }
    274
    275     @Test
    276     void testWriteBoundarySource() {
    277         // arrange
    278         final ConvexPolygon3D poly1 = Planes.convexPolygonFromVertices(Arrays.asList(
    279                 Vector3D.ZERO, Vector3D.of(0, 0, -0.5), Vector3D.of(0, -0.5, 0)
    280             ), TEST_PRECISION);
    281         final ConvexPolygon3D poly2 = Planes.convexPolygonFromVertices(Arrays.asList(
    282                 Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(1, 1, 0), Vector3D.of(0, 1, 0)
    283             ), TEST_PRECISION);
    284
    285         // act
    286         fdWriter.write(BoundarySource3D.of(poly1, poly2));
    287
    288         // assert
    289         Assertions.assertEquals(
    290                 "0.0 0.0 0.0; 0.0 0.0 -0.5; 0.0 -0.5 0.0\n" +
    291                 "0.0 0.0 0.0; 1.0 0.0 0.0; 1.0 1.0 0.0; 0.0 1.0 0.0\n", writer.toString());
    292     }
    293
    294     @Test
    295     void testWriteBoundarySource_empty() {
    296         // act
    297         fdWriter.write(BoundarySource3D.of(Collections.emptyList()));
    298
    299         // assert
    300         Assertions.assertEquals("", writer.toString());
    301     }
    302
    303     @Test
    304     void testWriteBoundarySource_alternativeFormatting() {
    305         // arrange
    306         final DecimalFormat fmt =
    307                 new DecimalFormat("0.0", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
    308         final ConvexPolygon3D poly1 = Planes.convexPolygonFromVertices(Arrays.asList(
    309                 Vector3D.ZERO, Vector3D.of(0, 0, -0.5901), Vector3D.of(0, -0.501, 0)
    310             ), TEST_PRECISION);
    311         final ConvexPolygon3D poly2 = Planes.convexPolygonFromVertices(Arrays.asList(
    312                 Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(1, 1, 0), Vector3D.of(0, 1, 0)
    313             ), TEST_PRECISION);
    314
    315         fdWriter.setDoubleFormat(fmt::format);
    316
    317         fdWriter.setFacetVertexCount(3);
    318         fdWriter.setLineSeparator("\r\n");
    319         fdWriter.setVertexComponentSeparator(",");
    320         fdWriter.setVertexSeparator(" | ");
    321
    322         // act
    323         fdWriter.writeComment("Test boundary source");
    324         fdWriter.writeBlankLine();
    325         fdWriter.write(BoundarySource3D.of(poly1, poly2));
    326
    327         // assert
    328         Assertions.assertEquals(
    329                 "# Test boundary source\r\n" +
    330                 "\r\n" +
    331                 "0.0,0.0,0.0 | 0.0,0.0,-0.6 | 0.0,-0.5,0.0\r\n" +
    332                 "0.0,0.0,0.0 | 1.0,0.0,0.0 | 1.0,1.0,0.0\r\n" +
    333                 "0.0,0.0,0.0 | 1.0,1.0,0.0 | 0.0,1.0,0.0\r\n", writer.toString());
    334     }
    335
    336     @Test
    337     void testCsvFormat() {
    338         // arrange
    339         final ConvexPolygon3D poly1 = Planes.convexPolygonFromVertices(Arrays.asList(
    340                 Vector3D.ZERO, Vector3D.of(0, 0, -0.5901), Vector3D.of(0, -0.501, 0)
    341             ), TEST_PRECISION);
    342         final ConvexPolygon3D poly2 = Planes.convexPolygonFromVertices(Arrays.asList(
    343                 Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(1, 1, 0), Vector3D.of(0, 1, 0)
    344             ), TEST_PRECISION);
    345
    346         final TextFacetDefinitionWriter csvWriter = TextFacetDefinitionWriter.csvFormat(writer);
    347
    348         // act
    349         csvWriter.write(BoundarySource3D.of(poly1, poly2));
    350
    351         // assert
    352         Assertions.assertEquals(
    353                 "0.0,0.0,0.0,0.0,0.0,-0.5901,0.0,-0.501,0.0\n" +
    354                 "0.0,0.0,0.0,1.0,0.0,0.0,1.0,1.0,0.0\n" +
    355                 "0.0,0.0,0.0,1.0,1.0,0.0,0.0,1.0,0.0\n", writer.toString());
    356     }
    357
    358     @Test
    359     void testCsvFormat_properties() {
    360         // act
    361         final TextFacetDefinitionWriter csvWriter = TextFacetDefinitionWriter.csvFormat(writer);
    362
    363         // act/assert
    364         Assertions.assertEquals(",", csvWriter.getVertexComponentSeparator());
    365         Assertions.assertEquals(",", csvWriter.getVertexSeparator());
    366         Assertions.assertNull(csvWriter.getCommentToken());
    367     }
    368 }