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 }