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 }