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.ByteArrayOutputStream;
020import java.io.IOException;
021import java.io.OutputStream;
022import java.util.Iterator;
023import java.util.List;
024import java.util.stream.Stream;
025
026import org.apache.commons.geometry.euclidean.internal.EuclideanUtils;
027import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
028import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
029import org.apache.commons.geometry.euclidean.threed.Triangle3D;
030import org.apache.commons.geometry.euclidean.threed.Vector3D;
031import org.apache.commons.geometry.euclidean.threed.mesh.TriangleMesh;
032import org.apache.commons.geometry.io.core.GeometryFormat;
033import org.apache.commons.geometry.io.core.internal.GeometryIOUtils;
034import org.apache.commons.geometry.io.core.output.GeometryOutput;
035import org.apache.commons.geometry.io.euclidean.threed.AbstractBoundaryWriteHandler3D;
036import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition;
037import org.apache.commons.geometry.io.euclidean.threed.GeometryFormat3D;
038
039/** {@link org.apache.commons.geometry.io.euclidean.threed.BoundaryWriteHandler3D BoundaryWriteHandler3D}
040 * implementation for writing STL content. Because of its compact nature, all STL content is written in
041 * binary format, as opposed the text (i.e. "ASCII") format. Callers should use the {@link TextStlWriter}
042 * class directly in order to create text STL content.
043 */
044public class StlBoundaryWriteHandler3D extends AbstractBoundaryWriteHandler3D {
045
046    /** Initial size of the data buffer. */
047    private static final int DEFAULT_BUFFER_SIZE = 1024 * StlConstants.BINARY_TRIANGLE_BYTES;
048
049    /** Initial size of data buffers used during write operations. */
050    private int initialBufferSize = DEFAULT_BUFFER_SIZE;
051
052    /** {@inheritDoc} */
053    @Override
054    public GeometryFormat getFormat() {
055        return GeometryFormat3D.STL;
056    }
057
058    /** Get the initial size of the data buffers used by this instance.
059     *
060     * <p>The buffer is used in situations where it is not clear how many
061     * triangles will ultimately be written to the output. In these cases, the
062     * triangle data is first written to an internal buffer. Once all triangles are
063     * written, the STL header containing the total triangle count is written
064     * to the output, followed by the buffered triangle data.</p>
065     * @return initial buffer size
066     */
067    public int getinitialBufferSize() {
068        return initialBufferSize;
069    }
070
071    /** Set the initial size of the data buffers used by this instance.
072     *
073     * <p>The buffer is used in situations where it is not clear how many
074     * triangles will ultimately be written to the output. In these cases, the
075     * triangle data is first written to an internal buffer. Once all triangles are
076     * written, the STL header containing the total triangle count is written
077     * to the output, followed by the buffered triangle data.</p>
078     * @param initialBufferSize initial buffer size
079     */
080    public void setInitialBufferSize(final int initialBufferSize) {
081        if (initialBufferSize < 1) {
082            throw new IllegalArgumentException("Buffer size must be greater than 0");
083        }
084        this.initialBufferSize = initialBufferSize;
085    }
086
087    /** {@inheritDoc} */
088    @Override
089    public void write(final BoundarySource3D src, final GeometryOutput out) {
090        // handle cases where we know the number of triangles to be written up front
091        // and do not need to buffer the content
092        if (src instanceof TriangleMesh) {
093            writeTriangleMesh((TriangleMesh) src, out);
094        } else {
095            // unknown number of triangles; proceed with a buffered write
096            super.write(src, out);
097        }
098    }
099
100    /** {@inheritDoc} */
101    @Override
102    public void write(final Stream<? extends PlaneConvexSubset> boundaries, final GeometryOutput out) {
103
104        // write the triangle data to a buffer and track how many we write
105        int triangleCount = 0;
106        final ByteArrayOutputStream triangleBuffer = new ByteArrayOutputStream(initialBufferSize);
107
108        try (BinaryStlWriter stlWriter = new BinaryStlWriter(triangleBuffer)) {
109            final Iterator<? extends PlaneConvexSubset> it = boundaries.iterator();
110
111            while (it.hasNext()) {
112                for (final Triangle3D tri : it.next().toTriangles()) {
113
114                    stlWriter.writeTriangle(
115                            tri.getPoint1(),
116                            tri.getPoint2(),
117                            tri.getPoint3(),
118                            tri.getPlane().getNormal());
119
120                    ++triangleCount;
121                }
122            }
123        }
124
125        // write the header and copy the data
126        writeWithHeader(triangleBuffer, triangleCount, out);
127    }
128
129    /** {@inheritDoc} */
130    @Override
131    public void writeFacets(final Stream<? extends FacetDefinition> facets, final GeometryOutput out) {
132
133        // write the triangle data to a buffer and track how many we write
134        int triangleCount = 0;
135        final ByteArrayOutputStream triangleBuffer = new ByteArrayOutputStream(initialBufferSize);
136
137        try (BinaryStlWriter dataWriter = new BinaryStlWriter(triangleBuffer)) {
138            final Iterator<? extends FacetDefinition> it = facets.iterator();
139
140            FacetDefinition facet;
141            int attributeValue;
142
143            while (it.hasNext()) {
144                facet = it.next();
145                attributeValue = getFacetAttributeValue(facet);
146
147                for (final List<Vector3D> tri :
148                    EuclideanUtils.convexPolygonToTriangleFan(facet.getVertices(), t -> t)) {
149
150                    dataWriter.writeTriangle(
151                            tri.get(0),
152                            tri.get(1),
153                            tri.get(2),
154                            facet.getNormal(),
155                            attributeValue);
156
157                    ++triangleCount;
158                }
159            }
160        }
161
162        // write the header and copy the data
163        writeWithHeader(triangleBuffer, triangleCount, out);
164    }
165
166    /** Write the given triangle data prefixed with an STL header to the output stream from {@code out}.
167     * @param triangleBuffer buffer containing STL triangle data
168     * @param count number of triangles in {@code triangleBuffer}
169     * @param out output to write to
170     * @throws java.io.UncheckedIOException if an I/O error occurs
171     */
172    private void writeWithHeader(final ByteArrayOutputStream triangleBuffer, final int count,
173            final GeometryOutput out) {
174        // write the header and copy the data
175        try (OutputStream os = out.getOutputStream()) {
176            BinaryStlWriter.writeHeader(null, count, os);
177            triangleBuffer.writeTo(os);
178        } catch (IOException exc) {
179            throw GeometryIOUtils.createUnchecked(exc);
180        }
181    }
182
183    /** Write all triangles in the given mesh to the output using the binary STL
184     * format.
185     * @param mesh mesh to write
186     * @param output output to write to
187     * @throws java.io.UncheckedIOException if an I/O error occurs
188     */
189    private void writeTriangleMesh(final TriangleMesh mesh, final GeometryOutput output) {
190        try (BinaryStlWriter stlWriter = new BinaryStlWriter(output.getOutputStream())) {
191            // write the header
192            stlWriter.writeHeader(null, mesh.getFaceCount());
193
194            // write each triangle
195            Triangle3D tri;
196            for (final TriangleMesh.Face face : mesh.faces()) {
197                tri = face.getPolygon();
198
199                stlWriter.writeTriangle(
200                        tri.getPoint1(),
201                        tri.getPoint2(),
202                        tri.getPoint3(),
203                        tri.getPlane().getNormal());
204            }
205        }
206    }
207
208    /** Get the attribute value that should be used for the given facet.
209     * @param facet facet to get the attribute value for
210     * @return attribute value
211     */
212    private int getFacetAttributeValue(final FacetDefinition facet) {
213        if (facet instanceof BinaryStlFacetDefinition) {
214            return ((BinaryStlFacetDefinition) facet).getAttributeValue();
215        }
216
217        return 0;
218    }
219}