BinaryStlWriter.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.geometry.io.euclidean.threed.stl;
import java.io.Closeable;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import org.apache.commons.geometry.euclidean.threed.Vector3D;
import org.apache.commons.geometry.io.core.internal.GeometryIOUtils;
/** Low-level class for writing binary STL content.
*/
public class BinaryStlWriter implements Closeable {
/** Output stream to write to. */
private final OutputStream out;
/** Buffer used to construct triangle definitions. */
private final ByteBuffer triangleBuffer = StlUtils.byteBuffer(StlConstants.BINARY_TRIANGLE_BYTES);
/** Construct a new instance for writing to the given output.
* @param out output stream to write to
*/
public BinaryStlWriter(final OutputStream out) {
this.out = out;
}
/** Write binary STL header content. If {@code headerContent} is null, the written header
* will consist entirely of zeros. Otherwise, up to 80 bytes from {@code headerContent}
* are written to the header, with any remaining bytes of the header filled with zeros.
* @param headerContent bytes to include in the header; may be null
* @param triangleCount number of triangles to be included in the content
* @throws java.io.UncheckedIOException if an I/O error occurs
*/
public void writeHeader(final byte[] headerContent, final int triangleCount) {
writeHeader(headerContent, triangleCount, out);
}
/** Write a triangle to the output using a default attribute value of 0.
* Callers are responsible for ensuring that the number of triangles written
* matches the number given in the header.
*
* <p>If a normal is given, the vertices are ordered using the right-hand rule,
* meaning that they will be in a counter-clockwise orientation when looking down
* the normal. Thus, the given point ordering may not be the ordering used in
* the written content.</p>
* @param p1 first point
* @param p2 second point
* @param p3 third point
* @param normal triangle normal; may be null
* @throws java.io.UncheckedIOException if an I/O error occurs
*/
public void writeTriangle(final Vector3D p1, final Vector3D p2, final Vector3D p3,
final Vector3D normal) {
writeTriangle(p1, p2, p3, normal, 0);
}
/** Write a triangle to the output. Callers are responsible for ensuring
* that the number of triangles written matches the number given in the header.
*
* <p>If a non-zero normal is given, the vertices are ordered using the right-hand rule,
* meaning that they will be in a counter-clockwise orientation when looking down
* the normal. If no normal is given, or the given value cannot be normalized, a normal
* is computed from the triangle vertices, also using the right-hand rule. If this also
* fails (for example, if the triangle vertices do not define a plane), then the
* zero vector is used.</p>
* @param p1 first point
* @param p2 second point
* @param p3 third point
* @param normal triangle normal; may be null
* @param attributeValue 2-byte STL triangle attribute value
* @throws java.io.UncheckedIOException if an I/O error occurs
*/
public void writeTriangle(final Vector3D p1, final Vector3D p2, final Vector3D p3,
final Vector3D normal, final int attributeValue) {
triangleBuffer.rewind();
putVector(StlUtils.determineNormal(p1, p2, p3, normal));
putVector(p1);
if (StlUtils.pointsAreCounterClockwise(p1, p2, p3, normal)) {
putVector(p2);
putVector(p3);
} else {
putVector(p3);
putVector(p2);
}
triangleBuffer.putShort((short) attributeValue);
GeometryIOUtils.acceptUnchecked(out::write, triangleBuffer.array());
}
/** {@inheritDoc} */
@Override
public void close() {
GeometryIOUtils.closeUnchecked(out);
}
/** Put all double components of {@code vec} into the internal buffer.
* @param vec vector to place into the buffer
*/
private void putVector(final Vector3D vec) {
triangleBuffer.putFloat((float) vec.getX());
triangleBuffer.putFloat((float) vec.getY());
triangleBuffer.putFloat((float) vec.getZ());
}
/** Write binary STL header content to the given output stream. If {@code headerContent}
* is null, the written header will consist entirely of zeros. Otherwise, up to 80 bytes
* from {@code headerContent} are written to the header, with any remaining bytes of the
* header filled with zeros.
* @param headerContent
* @param triangleCount
* @param out
* @throws java.io.UncheckedIOException if an I/O error occurs
*/
static void writeHeader(final byte[] headerContent, final int triangleCount, final OutputStream out) {
// write the header
final byte[] bytes = new byte[StlConstants.BINARY_HEADER_BYTES];
if (headerContent != null) {
System.arraycopy(
headerContent, 0,
bytes, 0,
Math.min(headerContent.length, StlConstants.BINARY_HEADER_BYTES));
}
GeometryIOUtils.acceptUnchecked(out::write, bytes);
// write the triangle count number
ByteBuffer countBuffer = StlUtils.byteBuffer(Integer.BYTES);
countBuffer.putInt(triangleCount);
countBuffer.flip();
GeometryIOUtils.acceptUnchecked(out::write, countBuffer.array());
}
}