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.stl;
    18
    19  import java.io.ByteArrayOutputStream;
    20  import java.util.ArrayList;
    21  import java.util.Arrays;
    22  import java.util.List;
    23
    24  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
    25  import org.apache.commons.geometry.euclidean.threed.Vector3D;
    26  import org.junit.jupiter.api.Assertions;
    27  import org.junit.jupiter.api.Test;
    28
    29  class BinaryStlWriterTest {
    30
    31      private static final double TEST_EPS = 1e-7;
    32
    33      private static final int VECTOR_SIZE = 3 * Float.BYTES;
    34
    35      private final ByteArrayOutputStream out = new ByteArrayOutputStream();
    36
    37      @Test
    38      void testWriteHeader_nullHeaderContent() {
    39          // act
    40          try (BinaryStlWriter writer = new BinaryStlWriter(out)) {
    41              writer.writeHeader(null, Short.MAX_VALUE);
    42          }
    43
    44          // assert
    45          final byte[] bytes = out.toByteArray();
    46          Assertions.assertEquals(StlConstants.BINARY_HEADER_BYTES + 4, bytes.length);
    47
    48          assertBytes(0, bytes, 0, StlConstants.BINARY_HEADER_BYTES);
    49          Assertions.assertEquals(Short.MAX_VALUE, readAsInt(bytes, StlConstants.BINARY_HEADER_BYTES, 4));
    50      }
    51
    52      @Test
    53      void testWriteHeader_givenHeaderContent() {
    54          // arrange
    55          final byte[] headerContent = new byte[StlConstants.BINARY_HEADER_BYTES];
    56          Arrays.fill(headerContent, (byte) 1);
    57
    58          // act
    59          try (BinaryStlWriter writer = new BinaryStlWriter(out)) {
    60              writer.writeHeader(headerContent, 1);
    61          }
    62
    63          // assert
    64          final byte[] bytes = out.toByteArray();
    65          Assertions.assertEquals(StlConstants.BINARY_HEADER_BYTES + 4, bytes.length);
    66
    67          assertBytes(1, bytes, 0, StlConstants.BINARY_HEADER_BYTES);
    68          Assertions.assertEquals(1, readAsInt(bytes, StlConstants.BINARY_HEADER_BYTES, 4));
    69      }
    70
    71      @Test
    72      void testWriteHeader_givenHeaderContentExceedsMaxLength() {
    73          // arrange
    74          final byte[] headerContent = new byte[2 * StlConstants.BINARY_HEADER_BYTES];
    75          Arrays.fill(headerContent, (byte) 1);
    76
    77          // act
    78          try (BinaryStlWriter writer = new BinaryStlWriter(out)) {
    79              writer.writeHeader(headerContent, 0);
    80          }
    81
    82          // assert
    83          final byte[] bytes = out.toByteArray();
    84          Assertions.assertEquals(StlConstants.BINARY_HEADER_BYTES + 4, bytes.length);
    85
    86          assertBytes(1, bytes, 0, StlConstants.BINARY_HEADER_BYTES);
    87          Assertions.assertEquals(0, readAsInt(bytes, StlConstants.BINARY_HEADER_BYTES, 4));
    88      }
    89
    90      @Test
    91      void testWriteFacet() {
    92          // arrange
    93          try (BinaryStlWriter writer = new BinaryStlWriter(out)) {
    94              writer.writeHeader(null, 2);
    95
    96              // act
    97              writer.writeTriangle(
    98                      Vector3D.of(1, 2, 3),
    99                      Vector3D.of(4, 5, 6),
    100                     Vector3D.of(7, 8, 9),
    101                     Vector3D.of(10, 11, 12));
    102
    103             writer.writeTriangle(
    104                     Vector3D.of(-1, -2, -3),
    105                     Vector3D.of(-4, -5, -6),
    106                     Vector3D.of(-7, -8, -9),
    107                     Vector3D.of(-10, -11, -12),
    108                     512);
    109         }
    110
    111         // assert
    112         final byte[] bytes = out.toByteArray();
    113
    114         Assertions.assertEquals(StlConstants.BINARY_HEADER_BYTES + 4 + (2 * StlConstants.BINARY_TRIANGLE_BYTES),
    115                 bytes.length);
    116
    117         assertBytes(0, bytes, 0, StlConstants.BINARY_HEADER_BYTES);
    118         Assertions.assertEquals(2, readAsInt(bytes, StlConstants.BINARY_HEADER_BYTES, Integer.BYTES));
    119
    120         int offset = StlConstants.BINARY_HEADER_BYTES + 4;
    121
    122         final List<Vector3D> tri1 = readVectors(bytes, offset, 4);
    123
    124         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(10, 11, 12).normalize(), tri1.get(0), TEST_EPS);
    125         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 3), tri1.get(1), TEST_EPS);
    126         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(4, 5, 6), tri1.get(2), TEST_EPS);
    127         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(7, 8, 9), tri1.get(3), TEST_EPS);
    128         offset += 4 * VECTOR_SIZE;
    129
    130         Assertions.assertEquals(0, readAsInt(bytes, offset, 2));
    131         offset += 2;
    132
    133         final List<Vector3D> tri2 = readVectors(bytes, offset, 4);
    134
    135         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-10, -11, -12).normalize(), tri2.get(0), TEST_EPS);
    136         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, -2, -3), tri2.get(1), TEST_EPS);
    137         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-4, -5, -6), tri2.get(2), TEST_EPS);
    138         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-7, -8, -9), tri2.get(3), TEST_EPS);
    139         offset += 4 * VECTOR_SIZE;
    140
    141         Assertions.assertEquals(512, readAsInt(bytes, offset, 2));
    142     }
    143
    144     @Test
    145     void testWriteFacet_ordersFacetCounterClockwise() {
    146         // arrange
    147         try (BinaryStlWriter writer = new BinaryStlWriter(out)) {
    148             writer.writeHeader(null, 2);
    149
    150             // act
    151             writer.writeTriangle(
    152                     Vector3D.ZERO,
    153                     Vector3D.of(1, 0, 0),
    154                     Vector3D.of(0, 1, 0),
    155                     Vector3D.of(0, 0, 1));
    156
    157             writer.writeTriangle(
    158                     Vector3D.ZERO,
    159                     Vector3D.of(0, 1, 0),
    160                     Vector3D.of(1, 0, 0),
    161                     Vector3D.of(0, 0, 1));
    162         }
    163
    164         // assert
    165         final byte[] bytes = out.toByteArray();
    166
    167         Assertions.assertEquals(StlConstants.BINARY_HEADER_BYTES + 4 + (2 * StlConstants.BINARY_TRIANGLE_BYTES),
    168                 bytes.length);
    169
    170         assertBytes(0, bytes, 0, StlConstants.BINARY_HEADER_BYTES);
    171         Assertions.assertEquals(2, readAsInt(bytes, StlConstants.BINARY_HEADER_BYTES, Integer.BYTES));
    172
    173         int offset = StlConstants.BINARY_HEADER_BYTES + 4;
    174
    175         final List<Vector3D> tri1 = readVectors(bytes, offset, 4);
    176
    177         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 1), tri1.get(0), TEST_EPS);
    178         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 0), tri1.get(1), TEST_EPS);
    179         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 0, 0), tri1.get(2), TEST_EPS);
    180         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), tri1.get(3), TEST_EPS);
    181         offset += 4 * VECTOR_SIZE;
    182
    183         Assertions.assertEquals(0, readAsInt(bytes, offset, 2));
    184         offset += 2;
    185
    186         final List<Vector3D> tri2 = readVectors(bytes, offset, 4);
    187
    188         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 1), tri2.get(0), TEST_EPS);
    189         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 0), tri2.get(1), TEST_EPS);
    190         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 0, 0), tri2.get(2), TEST_EPS);
    191         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), tri2.get(3), TEST_EPS);
    192         offset += 4 * VECTOR_SIZE;
    193
    194         Assertions.assertEquals(0, readAsInt(bytes, offset, 2));
    195     }
    196
    197     @Test
    198     void testWriteFacet_invalidNormalGiven() {
    199         // arrange
    200         try (BinaryStlWriter writer = new BinaryStlWriter(out)) {
    201             writer.writeHeader(null, 3);
    202
    203             // act
    204             writer.writeTriangle(
    205                     Vector3D.ZERO,
    206                     Vector3D.of(1, 0, 0),
    207                     Vector3D.of(0, 1, 0),
    208                     Vector3D.ZERO);
    209
    210             writer.writeTriangle(
    211                     Vector3D.ZERO,
    212                     Vector3D.of(0, 1, 0),
    213                     Vector3D.of(1, 0, 0),
    214                     null,
    215                     512);
    216
    217             writer.writeTriangle(
    218                     Vector3D.ZERO,
    219                     Vector3D.ZERO,
    220                     Vector3D.of(1, 1, 1),
    221                     null);
    222         }
    223
    224         // assert
    225         final byte[] bytes = out.toByteArray();
    226
    227         Assertions.assertEquals(StlConstants.BINARY_HEADER_BYTES + 4 + (3 * StlConstants.BINARY_TRIANGLE_BYTES),
    228                 bytes.length);
    229
    230         assertBytes(0, bytes, 0, StlConstants.BINARY_HEADER_BYTES);
    231         Assertions.assertEquals(3, readAsInt(bytes, StlConstants.BINARY_HEADER_BYTES, Integer.BYTES));
    232
    233         int offset = StlConstants.BINARY_HEADER_BYTES + 4;
    234
    235         final List<Vector3D> tri1 = readVectors(bytes, offset, 4);
    236
    237         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 1), tri1.get(0), TEST_EPS);
    238         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 0), tri1.get(1), TEST_EPS);
    239         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 0, 0), tri1.get(2), TEST_EPS);
    240         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), tri1.get(3), TEST_EPS);
    241         offset += 4 * VECTOR_SIZE;
    242
    243         Assertions.assertEquals(0, readAsInt(bytes, offset, 2));
    244         offset += 2;
    245
    246         final List<Vector3D> tri2 = readVectors(bytes, offset, 4);
    247
    248         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, -1), tri2.get(0), TEST_EPS);
    249         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 0), tri2.get(1), TEST_EPS);
    250         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), tri2.get(2), TEST_EPS);
    251         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 0, 0), tri2.get(3), TEST_EPS);
    252         offset += 4 * VECTOR_SIZE;
    253
    254         Assertions.assertEquals(512, readAsInt(bytes, offset, 2));
    255         offset += 2;
    256
    257         final List<Vector3D> tri3 = readVectors(bytes, offset, 4);
    258
    259         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 0), tri3.get(0), TEST_EPS);
    260         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 0), tri3.get(1), TEST_EPS);
    261         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 0), tri3.get(2), TEST_EPS);
    262         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, 1), tri3.get(3), TEST_EPS);
    263         offset += 4 * VECTOR_SIZE;
    264
    265         Assertions.assertEquals(0, readAsInt(bytes, offset, 2));
    266     }
    267
    268     private static void assertBytes(final int expected, final byte[] actual, final int offset, final int len) {
    269         for (int i = 0; i < len; ++i) {
    270             Assertions.assertEquals(expected, actual[i + offset]);
    271         }
    272     }
    273
    274     private static int readAsInt(final byte[] bytes, final int offset, final int count) {
    275         int result = 0;
    276
    277         for (int i = 0; i < count; ++i) {
    278             result |= Byte.toUnsignedInt(bytes[i + offset]) << (i * Byte.SIZE);
    279         }
    280
    281         return result;
    282     }
    283
    284     private static float readFloat(final byte[] bytes, final int offset) {
    285         final int value = readAsInt(bytes, offset, Float.BYTES);
    286         return Float.intBitsToFloat(value);
    287     }
    288
    289     private static Vector3D readVector(final byte[] bytes, final int offset) {
    290         final double x = readFloat(bytes, offset);
    291         final double y = readFloat(bytes, offset + Float.BYTES);
    292         final double z = readFloat(bytes, offset + (2 * Float.BYTES));
    293
    294         return Vector3D.of(x, y, z);
    295     }
    296
    297     private static List<Vector3D> readVectors(final byte[] bytes, final int offset, final int vectorCount) {
    298         final List<Vector3D> vectors = new ArrayList<>(vectorCount);
    299
    300         for (int i = 0; i < vectorCount; ++i) {
    301             vectors.add(readVector(bytes, (i * VECTOR_SIZE) + offset));
    302         }
    303
    304         return vectors;
    305     }
    306 }