BinaryStlWriter.java

  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. import java.io.Closeable;
  19. import java.io.OutputStream;
  20. import java.nio.ByteBuffer;

  21. import org.apache.commons.geometry.euclidean.threed.Vector3D;
  22. import org.apache.commons.geometry.io.core.internal.GeometryIOUtils;

  23. /** Low-level class for writing binary STL content.
  24.  */
  25. public class BinaryStlWriter implements Closeable {

  26.     /** Output stream to write to. */
  27.     private final OutputStream out;

  28.     /** Buffer used to construct triangle definitions. */
  29.     private final ByteBuffer triangleBuffer = StlUtils.byteBuffer(StlConstants.BINARY_TRIANGLE_BYTES);

  30.     /** Construct a new instance for writing to the given output.
  31.      * @param out output stream to write to
  32.      */
  33.     public BinaryStlWriter(final OutputStream out) {
  34.         this.out = out;
  35.     }

  36.     /** Write binary STL header content. If {@code headerContent} is null, the written header
  37.      * will consist entirely of zeros. Otherwise, up to 80 bytes from {@code headerContent}
  38.      * are written to the header, with any remaining bytes of the header filled with zeros.
  39.      * @param headerContent bytes to include in the header; may be null
  40.      * @param triangleCount number of triangles to be included in the content
  41.      * @throws java.io.UncheckedIOException if an I/O error occurs
  42.      */
  43.     public void writeHeader(final byte[] headerContent, final int triangleCount) {
  44.         writeHeader(headerContent, triangleCount, out);
  45.     }

  46.     /** Write a triangle to the output using a default attribute value of 0.
  47.      * Callers are responsible for ensuring that the number of triangles written
  48.      * matches the number given in the header.
  49.      *
  50.      * <p>If a normal is given, the vertices are ordered using the right-hand rule,
  51.      * meaning that they will be in a counter-clockwise orientation when looking down
  52.      * the normal. Thus, the given point ordering may not be the ordering used in
  53.      * the written content.</p>
  54.      * @param p1 first point
  55.      * @param p2 second point
  56.      * @param p3 third point
  57.      * @param normal triangle normal; may be null
  58.      * @throws java.io.UncheckedIOException if an I/O error occurs
  59.      */
  60.     public void writeTriangle(final Vector3D p1, final Vector3D p2, final Vector3D p3,
  61.             final Vector3D normal) {
  62.         writeTriangle(p1, p2, p3, normal, 0);
  63.     }

  64.     /** Write a triangle to the output. Callers are responsible for ensuring
  65.      * that the number of triangles written matches the number given in the header.
  66.      *
  67.      * <p>If a non-zero normal is given, the vertices are ordered using the right-hand rule,
  68.      * meaning that they will be in a counter-clockwise orientation when looking down
  69.      * the normal. If no normal is given, or the given value cannot be normalized, a normal
  70.      * is computed from the triangle vertices, also using the right-hand rule. If this also
  71.      * fails (for example, if the triangle vertices do not define a plane), then the
  72.      * zero vector is used.</p>
  73.      * @param p1 first point
  74.      * @param p2 second point
  75.      * @param p3 third point
  76.      * @param normal triangle normal; may be null
  77.      * @param attributeValue 2-byte STL triangle attribute value
  78.      * @throws java.io.UncheckedIOException if an I/O error occurs
  79.      */
  80.     public void writeTriangle(final Vector3D p1, final Vector3D p2, final Vector3D p3,
  81.             final Vector3D normal, final int attributeValue) {
  82.         triangleBuffer.rewind();

  83.         putVector(StlUtils.determineNormal(p1, p2, p3, normal));
  84.         putVector(p1);

  85.         if (StlUtils.pointsAreCounterClockwise(p1, p2, p3, normal)) {
  86.             putVector(p2);
  87.             putVector(p3);
  88.         } else {
  89.             putVector(p3);
  90.             putVector(p2);
  91.         }

  92.         triangleBuffer.putShort((short) attributeValue);

  93.         GeometryIOUtils.acceptUnchecked(out::write, triangleBuffer.array());
  94.     }

  95.     /** {@inheritDoc} */
  96.     @Override
  97.     public void close() {
  98.         GeometryIOUtils.closeUnchecked(out);
  99.     }

  100.     /** Put all double components of {@code vec} into the internal buffer.
  101.      * @param vec vector to place into the buffer
  102.      */
  103.     private void putVector(final Vector3D vec) {
  104.         triangleBuffer.putFloat((float) vec.getX());
  105.         triangleBuffer.putFloat((float) vec.getY());
  106.         triangleBuffer.putFloat((float) vec.getZ());
  107.     }

  108.     /** Write binary STL header content to the given output stream. If {@code headerContent}
  109.      * is null, the written header will consist entirely of zeros. Otherwise, up to 80 bytes
  110.      * from {@code headerContent} are written to the header, with any remaining bytes of the
  111.      * header filled with zeros.
  112.      * @param headerContent
  113.      * @param triangleCount
  114.      * @param out
  115.      * @throws java.io.UncheckedIOException if an I/O error occurs
  116.      */
  117.     static void writeHeader(final byte[] headerContent, final int triangleCount, final OutputStream out) {
  118.         // write the header
  119.         final byte[] bytes = new byte[StlConstants.BINARY_HEADER_BYTES];
  120.         if (headerContent != null) {
  121.             System.arraycopy(
  122.                     headerContent, 0,
  123.                     bytes, 0,
  124.                     Math.min(headerContent.length, StlConstants.BINARY_HEADER_BYTES));
  125.         }

  126.         GeometryIOUtils.acceptUnchecked(out::write, bytes);

  127.         // write the triangle count number
  128.         ByteBuffer countBuffer = StlUtils.byteBuffer(Integer.BYTES);
  129.         countBuffer.putInt(triangleCount);
  130.         countBuffer.flip();

  131.         GeometryIOUtils.acceptUnchecked(out::write, countBuffer.array());
  132.     }
  133. }