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 }