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.obj;
018
019import java.nio.charset.Charset;
020import java.util.Iterator;
021import java.util.function.DoubleFunction;
022import java.util.stream.Stream;
023
024import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
025import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
026import org.apache.commons.geometry.euclidean.threed.mesh.Mesh;
027import org.apache.commons.geometry.io.core.GeometryFormat;
028import org.apache.commons.geometry.io.core.internal.GeometryIOUtils;
029import org.apache.commons.geometry.io.core.output.GeometryOutput;
030import org.apache.commons.geometry.io.euclidean.threed.AbstractBoundaryWriteHandler3D;
031import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition;
032import org.apache.commons.geometry.io.euclidean.threed.GeometryFormat3D;
033
034/** {@link org.apache.commons.geometry.io.euclidean.threed.BoundaryWriteHandler3D BoundaryWriteHandler3D}
035 * implementation for writing OBJ content. Output is written using the UTF-8 charset by default.
036 */
037public class ObjBoundaryWriteHandler3D extends AbstractBoundaryWriteHandler3D {
038
039    /** The default line separator value. */
040    private static final String DEFAULT_LINE_SEPARATOR = "\n";
041
042    /** Default mesh buffer batch size. */
043    private static final int DEFAULT_MESH_BUFFER_BATCH_SIZE = -1;
044
045    /** Charset used for text output. */
046    private Charset defaultCharset = ObjConstants.DEFAULT_CHARSET;
047
048    /** Line separator string. */
049    private String lineSeparator = DEFAULT_LINE_SEPARATOR;
050
051    /** Double format function. */
052    private DoubleFunction<String> doubleFormat = Double::toString;
053
054    /** Batch size used for mesh buffer creation. */
055    private int meshBufferBatchSize = DEFAULT_MESH_BUFFER_BATCH_SIZE;
056
057    /** {@inheritDoc} */
058    @Override
059    public GeometryFormat getFormat() {
060        return GeometryFormat3D.OBJ;
061    }
062
063    /** Get the text output default charset, used if the output does not
064     * specify a charset.
065     * @return text output default charset
066     */
067    public Charset getDefaultCharset() {
068        return defaultCharset;
069    }
070
071    /** Set the text output default charset, used if the output does not
072     * specify a charset.
073     * @param charset text output default charset
074     */
075    public void setDefaultCharset(final Charset charset) {
076        this.defaultCharset = charset;
077    }
078
079    /** Get the line separator. This value defaults to {@value #DEFAULT_LINE_SEPARATOR}.
080     * @return the current line separator
081     */
082    public String getLineSeparator() {
083        return lineSeparator;
084    }
085
086    /** Set the line separator.
087     * @param lineSeparator the line separator to use
088     */
089    public void setLineSeparator(final String lineSeparator) {
090        this.lineSeparator = lineSeparator;
091    }
092
093    /** Get the function used to convert double values to strings.
094     * @return double format function
095     */
096    public DoubleFunction<String> getDoubleFormat() {
097        return doubleFormat;
098    }
099
100    /** Set the function used to convert double values to strings. The given function
101     * must be thread-safe if this handler is to be used in a multi-threaded context.
102     * @param doubleFormat double format function
103     */
104    public void setDoubleFormat(final DoubleFunction<String> doubleFormat) {
105        this.doubleFormat = doubleFormat;
106    }
107
108    /** Get the batch size when generating OBJ mesh content from facet sequences. Larger batch sizes
109     * allow for reuse of vertex definitions but at the cost of more memory usage. The buffer size is
110     * unlimited if set to {@code -1}. Default value is {@value #DEFAULT_MESH_BUFFER_BATCH_SIZE}.
111     * @return mesh buffer batch size
112     * @see ObjWriter#meshBuffer(int)
113     */
114    public int getMeshBufferBatchSize() {
115        return meshBufferBatchSize;
116    }
117
118    /** Set the batch size when generating OBJ mesh content from facet sequences. Larger batch sizes
119     * allow for reuse of vertex definitions but at the cost of more memory usage. Set to {@code -1}
120     * to allow unlimited buffer size. Default value is {@value #DEFAULT_MESH_BUFFER_BATCH_SIZE}.
121     * @param batchSize mesh buffer batch size; set to {@code -1} to allow unlimited buffer sizes
122     * @see ObjWriter#meshBuffer(int)
123     */
124    public void setMeshBufferBatchSize(final int batchSize) {
125        this.meshBufferBatchSize = batchSize;
126    }
127
128    /** {@inheritDoc} */
129    @Override
130    public void write(final BoundarySource3D src, final GeometryOutput out) {
131        // write meshes directly instead of iterating through boundaries
132        if (src instanceof Mesh) {
133            try (ObjWriter writer = createWriter(out)) {
134                writer.writeMesh((Mesh<?>) src);
135            }
136        } else {
137            super.write(src, out);
138        }
139    }
140
141    /** {@inheritDoc} */
142    @Override
143    public void write(final Stream<? extends PlaneConvexSubset> boundaries, final GeometryOutput out) {
144        try (ObjWriter writer = createWriter(out)) {
145            final ObjWriter.MeshBuffer meshBuffer = writer.meshBuffer(meshBufferBatchSize);
146
147            final Iterator<? extends PlaneConvexSubset> it = boundaries.iterator();
148            while (it.hasNext()) {
149                meshBuffer.add(it.next());
150            }
151
152            meshBuffer.flush();
153        }
154    }
155
156    /** {@inheritDoc} */
157    @Override
158    public void writeFacets(final Stream<? extends FacetDefinition> facets, final GeometryOutput out) {
159        try (ObjWriter writer = createWriter(out)) {
160            final ObjWriter.MeshBuffer meshBuffer = writer.meshBuffer(meshBufferBatchSize);
161
162            final Iterator<? extends FacetDefinition> it = facets.iterator();
163            while (it.hasNext()) {
164                meshBuffer.add(it.next());
165            }
166
167            meshBuffer.flush();
168        }
169    }
170
171    /** Construct a new, configured {@link ObjWriter} instance for writing content to the given
172     * output stream.
173     * @param out output stream to write to
174     * @return new {@code OBJWriter} for writing content to the given output stream
175     * @throws java.io.UncheckedIOException if an I/O error occurs
176     */
177    private ObjWriter createWriter(final GeometryOutput out) {
178        final ObjWriter writer = new ObjWriter(GeometryIOUtils.createBufferedWriter(out, defaultCharset));
179        writer.setLineSeparator(lineSeparator);
180        writer.setDoubleFormat(doubleFormat);
181
182        return writer;
183    }
184}