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.Closeable;
    20  import java.io.OutputStream;
    21  import java.nio.ByteBuffer;
    22
    23  import org.apache.commons.geometry.euclidean.threed.Vector3D;
    24  import org.apache.commons.geometry.io.core.internal.GeometryIOUtils;
    25
    26  /** Low-level class for writing binary STL content.
    27   */
    28  public class BinaryStlWriter implements Closeable {
    29
    30      /** Output stream to write to. */
    31      private final OutputStream out;
    32
    33      /** Buffer used to construct triangle definitions. */
    34      private final ByteBuffer triangleBuffer = StlUtils.byteBuffer(StlConstants.BINARY_TRIANGLE_BYTES);
    35
    36      /** Construct a new instance for writing to the given output.
    37       * @param out output stream to write to
    38       */
    39      public BinaryStlWriter(final OutputStream out) {
    40          this.out = out;
    41      }
    42
    43      /** Write binary STL header content. If {@code headerContent} is null, the written header
    44       * will consist entirely of zeros. Otherwise, up to 80 bytes from {@code headerContent}
    45       * are written to the header, with any remaining bytes of the header filled with zeros.
    46       * @param headerContent bytes to include in the header; may be null
    47       * @param triangleCount number of triangles to be included in the content
    48       * @throws java.io.UncheckedIOException if an I/O error occurs
    49       */
    50      public void writeHeader(final byte[] headerContent, final int triangleCount) {
    51          writeHeader(headerContent, triangleCount, out);
    52      }
    53
    54      /** Write a triangle to the output using a default attribute value of 0.
    55       * Callers are responsible for ensuring that the number of triangles written
    56       * matches the number given in the header.
    57       *
    58       * <p>If a normal is given, the vertices are ordered using the right-hand rule,
    59       * meaning that they will be in a counter-clockwise orientation when looking down
    60       * the normal. Thus, the given point ordering may not be the ordering used in
    61       * the written content.</p>
    62       * @param p1 first point
    63       * @param p2 second point
    64       * @param p3 third point
    65       * @param normal triangle normal; may be null
    66       * @throws java.io.UncheckedIOException if an I/O error occurs
    67       */
    68      public void writeTriangle(final Vector3D p1, final Vector3D p2, final Vector3D p3,
    69              final Vector3D normal) {
    70          writeTriangle(p1, p2, p3, normal, 0);
    71      }
    72
    73      /** Write a triangle to the output. Callers are responsible for ensuring
    74       * that the number of triangles written matches the number given in the header.
    75       *
    76       * <p>If a non-zero normal is given, the vertices are ordered using the right-hand rule,
    77       * meaning that they will be in a counter-clockwise orientation when looking down
    78       * the normal. If no normal is given, or the given value cannot be normalized, a normal
    79       * is computed from the triangle vertices, also using the right-hand rule. If this also
    80       * fails (for example, if the triangle vertices do not define a plane), then the
    81       * zero vector is used.</p>
    82       * @param p1 first point
    83       * @param p2 second point
    84       * @param p3 third point
    85       * @param normal triangle normal; may be null
    86       * @param attributeValue 2-byte STL triangle attribute value
    87       * @throws java.io.UncheckedIOException if an I/O error occurs
    88       */
    89      public void writeTriangle(final Vector3D p1, final Vector3D p2, final Vector3D p3,
    90              final Vector3D normal, final int attributeValue) {
    91          triangleBuffer.rewind();
    92
    93          putVector(StlUtils.determineNormal(p1, p2, p3, normal));
    94          putVector(p1);
    95
    96          if (StlUtils.pointsAreCounterClockwise(p1, p2, p3, normal)) {
    97              putVector(p2);
    98              putVector(p3);
    99          } else {
    100             putVector(p3);
    101             putVector(p2);
    102         }
    103
    104         triangleBuffer.putShort((short) attributeValue);
    105
    106         GeometryIOUtils.acceptUnchecked(out::write, triangleBuffer.array());
    107     }
    108
    109     /** {@inheritDoc} */
    110     @Override
    111     public void close() {
    112         GeometryIOUtils.closeUnchecked(out);
    113     }
    114
    115     /** Put all double components of {@code vec} into the internal buffer.
    116      * @param vec vector to place into the buffer
    117      */
    118     private void putVector(final Vector3D vec) {
    119         triangleBuffer.putFloat((float) vec.getX());
    120         triangleBuffer.putFloat((float) vec.getY());
    121         triangleBuffer.putFloat((float) vec.getZ());
    122     }
    123
    124     /** Write binary STL header content to the given output stream. If {@code headerContent}
    125      * is null, the written header will consist entirely of zeros. Otherwise, up to 80 bytes
    126      * from {@code headerContent} are written to the header, with any remaining bytes of the
    127      * header filled with zeros.
    128      * @param headerContent
    129      * @param triangleCount
    130      * @param out
    131      * @throws java.io.UncheckedIOException if an I/O error occurs
    132      */
    133     static void writeHeader(final byte[] headerContent, final int triangleCount, final OutputStream out) {
    134         // write the header
    135         final byte[] bytes = new byte[StlConstants.BINARY_HEADER_BYTES];
    136         if (headerContent != null) {
    137             System.arraycopy(
    138                     headerContent, 0,
    139                     bytes, 0,
    140                     Math.min(headerContent.length, StlConstants.BINARY_HEADER_BYTES));
    141         }
    142
    143         GeometryIOUtils.acceptUnchecked(out::write, bytes);
    144
    145         // write the triangle count number
    146         ByteBuffer countBuffer = StlUtils.byteBuffer(Integer.BYTES);
    147         countBuffer.putInt(triangleCount);
    148         countBuffer.flip();
    149
    150         GeometryIOUtils.acceptUnchecked(out::write, countBuffer.array());
    151     }
    152 }