001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.geometry.io.euclidean.threed.stl; 018 019import java.io.Closeable; 020import java.io.OutputStream; 021import java.nio.ByteBuffer; 022 023import org.apache.commons.geometry.euclidean.threed.Vector3D; 024import org.apache.commons.geometry.io.core.internal.GeometryIOUtils; 025 026/** Low-level class for writing binary STL content. 027 */ 028public class BinaryStlWriter implements Closeable { 029 030 /** Output stream to write to. */ 031 private final OutputStream out; 032 033 /** Buffer used to construct triangle definitions. */ 034 private final ByteBuffer triangleBuffer = StlUtils.byteBuffer(StlConstants.BINARY_TRIANGLE_BYTES); 035 036 /** Construct a new instance for writing to the given output. 037 * @param out output stream to write to 038 */ 039 public BinaryStlWriter(final OutputStream out) { 040 this.out = out; 041 } 042 043 /** Write binary STL header content. If {@code headerContent} is null, the written header 044 * will consist entirely of zeros. Otherwise, up to 80 bytes from {@code headerContent} 045 * are written to the header, with any remaining bytes of the header filled with zeros. 046 * @param headerContent bytes to include in the header; may be null 047 * @param triangleCount number of triangles to be included in the content 048 * @throws java.io.UncheckedIOException if an I/O error occurs 049 */ 050 public void writeHeader(final byte[] headerContent, final int triangleCount) { 051 writeHeader(headerContent, triangleCount, out); 052 } 053 054 /** Write a triangle to the output using a default attribute value of 0. 055 * Callers are responsible for ensuring that the number of triangles written 056 * matches the number given in the header. 057 * 058 * <p>If a normal is given, the vertices are ordered using the right-hand rule, 059 * meaning that they will be in a counter-clockwise orientation when looking down 060 * the normal. Thus, the given point ordering may not be the ordering used in 061 * the written content.</p> 062 * @param p1 first point 063 * @param p2 second point 064 * @param p3 third point 065 * @param normal triangle normal; may be null 066 * @throws java.io.UncheckedIOException if an I/O error occurs 067 */ 068 public void writeTriangle(final Vector3D p1, final Vector3D p2, final Vector3D p3, 069 final Vector3D normal) { 070 writeTriangle(p1, p2, p3, normal, 0); 071 } 072 073 /** Write a triangle to the output. Callers are responsible for ensuring 074 * that the number of triangles written matches the number given in the header. 075 * 076 * <p>If a non-zero normal is given, the vertices are ordered using the right-hand rule, 077 * meaning that they will be in a counter-clockwise orientation when looking down 078 * the normal. If no normal is given, or the given value cannot be normalized, a normal 079 * is computed from the triangle vertices, also using the right-hand rule. If this also 080 * fails (for example, if the triangle vertices do not define a plane), then the 081 * zero vector is used.</p> 082 * @param p1 first point 083 * @param p2 second point 084 * @param p3 third point 085 * @param normal triangle normal; may be null 086 * @param attributeValue 2-byte STL triangle attribute value 087 * @throws java.io.UncheckedIOException if an I/O error occurs 088 */ 089 public void writeTriangle(final Vector3D p1, final Vector3D p2, final Vector3D p3, 090 final Vector3D normal, final int attributeValue) { 091 triangleBuffer.rewind(); 092 093 putVector(StlUtils.determineNormal(p1, p2, p3, normal)); 094 putVector(p1); 095 096 if (StlUtils.pointsAreCounterClockwise(p1, p2, p3, normal)) { 097 putVector(p2); 098 putVector(p3); 099 } 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}