001/**
002 * BDSGeomReader -- A class that can write out an SAIC bdStudio BDS formatted geometry
003 * file.
004 *
005 * Copyright (C) 2010-2016, Joseph A. Huwaldt. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or modify it under the terms
008 * of the GNU Lesser General Public License as published by the Free Software Foundation;
009 * either version 2.1 of the License, or (at your option) any later version.
010 *
011 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
012 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
013 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
014 *
015 * You should have received a copy of the GNU Lesser General Public License along with
016 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place -
017 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html
018 */
019package geomss.geom.reader;
020
021import geomss.geom.*;
022import java.io.*;
023import java.nio.charset.StandardCharsets;
024import java.util.List;
025import java.util.Locale;
026import static java.util.Objects.requireNonNull;
027import javolution.context.StackContext;
028import javolution.util.FastTable;
029
030/**
031 * A {@link GeomReader} for writing vehicle point geometry out to an SAIC bdStudio BDS
032 * mesh geometry file. The input geometry is assumed to be made up of quadrilateral panels
033 * (with one panel side possibly collapsed).
034 *
035 * <p> Modified by: Joseph A. Huwaldt </p>
036 *
037 * @author Joseph A. Huwaldt, Date: May 3, 2010
038 * @version September 9, 2016
039 */
040public class BDSGeomReader extends AbstractGeomReader {
041
042    //  Debug output flag.
043    //private static final boolean DEBUG = false;
044    
045    //  A brief description of the data read by this reaader.
046    private static final String DESCRIPTION = RESOURCES.getString("bdsDescription");
047
048    //  The preferred file extension for files of this reader's type.
049    public static final String EXTENSION = "bds";
050
051    /**
052     * Returns a string representation of the object. This will return a brief description
053     * of the format read by this reader.
054     *
055     * @return A description of this format type.
056     */
057    @Override
058    public String toString() {
059        return DESCRIPTION;
060    }
061
062    /**
063     * Returns the preferred file extension (not including the ".") for files of this
064     * GeomReader's type.
065     *
066     * @return The preferred file extension for this format type.
067     */
068    @Override
069    public String getExtension() {
070        return EXTENSION;
071    }
072
073    /**
074     * Returns true. This class can write point geometry to a BDS formatted file.
075     *
076     * @return Always returns true.
077     */
078    @Override
079    public boolean canWriteData() {
080        return true;
081    }
082
083    /**
084     * Writes out a bdStudio mesh (BDS) geometry file for the geometry contained in the
085     * supplied {@link PointVehicle} object.
086     * <p>
087     * WARNING: This format is not unit aware. The geometry will be written out in
088     * whatever its current units are! Make sure to convert to the desired units for the
089     * file before calling this method.
090     * </p>
091     *
092     * @param outputFile The output file to which the geometry is to be written.
093     * @param geometry   The {@link PointVehicle} object to be written out. May not be null.
094     * @throws IOException if there is a problem writing to the specified file.
095     */
096    @Override
097    public void write(File outputFile, GeometryList geometry) throws IOException {
098        requireNonNull(outputFile);
099        _warnings.clear();
100        if (!geometry.containsGeometry()) {
101            _warnings.add(RESOURCES.getString("noGeometryWarning"));
102            return;
103        }
104
105        //  Convert the input generic geometry list to a PointVehicle.
106        PointVehicle vehicle = geomList2PointVehicle(geometry);
107
108        if (!vehicle.containsGeometry())
109            return;
110
111        //  Convert all the geometry to the same units.
112        vehicle = vehicle.to(vehicle.getUnit());
113        
114        //  Convert the geometry into BDMeshGroups.
115        List<BDSMeshGroup> meshGroups = convertGeometry(vehicle);
116
117        //  BDS files are required to be in ASCII with U.S. style number formatting.
118        //  Get the default locale and save it.  Then change the default locale to US.
119        Locale defLocale = Locale.getDefault();
120        Locale.setDefault(Locale.US);
121
122        StackContext.enter();
123        // Get a reference to the output stream writer.
124        try (PrintWriter writer = new PrintWriter(outputFile, StandardCharsets.US_ASCII.name())) {
125
126            //  Write the header data.
127            writer.println("bdStudio");
128            writer.println("GROUPS " + meshGroups.size());
129
130            //  Output the mesh group data.
131            for (BDSMeshGroup meshGroup : meshGroups) {
132                meshGroup.save(writer);
133            }
134
135            //  Write the materials section header.
136            writer.println("MATERIALS 0");
137
138            //  Write the textures section header.
139            writer.println("TEXTURES 0");
140
141            //  Write the textures section header.
142            writer.println("SOLID 0");
143
144        } finally {
145            StackContext.exit();
146
147            //  Restore the default locale.
148            Locale.setDefault(defLocale);
149        }
150
151    }
152
153    /**
154     * This method always returns <code>false</code> as BDS files do not encode the units
155     * that are being used. You must call <code>setFileUnits</code> to set the units being
156     * used before reading a file of this format.
157     *
158     * @return Always returns false.
159     * @see #setFileUnits(javax.measure.unit.Unit) 
160     */
161    @Override
162    public boolean isUnitAware() {
163        return false;
164    }
165
166    /**
167     * Convert the geometry from the native format to a list of bdStudio mesh groups.
168     */
169    private List<BDSMeshGroup> convertGeometry(PointVehicle geometry) {
170        List<BDSMeshGroup> meshGroups = FastTable.newInstance();
171
172        //  Loop over the components in this geometry.
173        for (PointComponent comp : geometry) {
174
175            //  Loop over the arrays in this component.
176            for (PointArray array : comp) {
177
178                //  Create a mesh group for this array.
179                BDSMeshGroup meshGroup = new BDSMeshGroup(array);
180                meshGroups.add(meshGroup);
181            }
182        }
183
184        return meshGroups;
185    }
186
187    /**
188     * A bdStudio mesh group object.
189     */
190    private class BDSMeshGroup {
191
192        private final String name;
193        private final BDSVertex[] vertexes;
194        private final List<int[]> faces = FastTable.newInstance();  //  Face vertex indicies.
195
196        public BDSMeshGroup(PointArray<? extends GeomPoint> array) {
197            // Store the name of the mesh.
198            name = array.getName();
199
200            //  Turn the array of points in a list of faces and verticies.
201            //  The array is made up of quadralateral panels, so convert
202            //  those into pairs of triangles.
203            
204            int numRows = array.size();
205            int numPanelRows = numRows - 1;
206            int numCols = array.get(0).size();
207            int numPanelCols = numCols - 1;
208            int vertexCount = numRows * numCols;
209
210            //  Load the PointArray objects into the required vertex list.
211            vertexes = new BDSVertex[vertexCount];
212            int vIdx = 0;
213            for (int i = 0; i < numRows; ++i) {
214                PointString<?> string = array.get(i);
215                for (int j = 0; j < numCols; ++j) {
216                    GeomPoint point = string.get(j);
217                    point = point.toDimension(3);   //  Convert the geometry to 3D.
218                    float[] normal = calcNormal(array, point, i, j);
219                    BDSVertex vertex = new BDSVertex(point, normal);
220                    vertexes[vIdx] = vertex;
221                    ++vIdx;
222                }
223            }
224
225            //  Set up the triangle indices (two per quad panel).
226            vIdx = 0;
227            for (int i = 0; i < numPanelRows; ++i) {
228                for (int j = 0; j < numPanelCols; ++j) {
229                    int[] aFace = new int[3];
230                    aFace[0] = vIdx;
231                    aFace[1] = vIdx + numCols;
232                    aFace[2] = vIdx + numCols + 1;
233                    faces.add(aFace);
234
235                    aFace = new int[3];
236                    aFace[0] = vIdx;
237                    aFace[1] = vIdx + numCols + 1;
238                    aFace[2] = vIdx + 1;
239                    faces.add(aFace);
240                    ++vIdx;
241                }
242                ++vIdx;
243            }
244        }
245
246        public void save(PrintWriter out) {
247
248            int numVerts = vertexes.length;
249            int numFaces = faces.size();
250
251            //  Write the header information for the mesh group.
252            out.println("MATERIAL 0");
253            out.println("TEXTURE 0");
254            out.println("SOLID 0");
255            out.print("GEOM ");
256            out.print(numVerts);
257            out.print(" ");
258            out.print(numFaces);
259            out.print(" ; ");
260            out.println(name);
261
262            //  Output the vertex data.
263            for (BDSVertex vertex : vertexes) {
264                vertex.save(out);
265            }
266
267            //  Output the face data.
268            for (int[] face : faces) {
269                out.print(face[0]);
270                out.print(" ");
271                out.print(face[1]);
272                out.print(" ");
273                out.println(face[2]);
274            }
275        }
276
277        //  Calculate the normal vector for a point by averaging the normals
278        //  of all the surrounding faces.
279        private float[] calcNormal(PointArray<? extends GeomPoint> array, GeomPoint pij, int i, int j) {
280            int numRows = array.size() - 1;
281            int numCols = array.get(0).size() - 1;
282            FastTable<GeomVector> neighbors = FastTable.newInstance();
283            GeomPoint p1 = pij;
284            if (i > 0) {
285                if (j < numCols) {
286                    GeomPoint p2 = array.get(i).get(j + 1).toDimension(3);
287                    GeomPoint p3 = array.get(i - 1).get(j).toDimension(3);
288                    GeomVector v1 = p2.minus(p1).toGeomVector();
289                    GeomVector v2 = p3.minus(p1).toGeomVector();
290                    if (!v1.mag().isApproxZero() && !v2.mag().isApproxZero()) {
291                        GeomVector v1xv2 = v1.cross(v2).toUnitVector();
292                        if (!v1xv2.get(0).isNaN())
293                            neighbors.add(v1xv2);
294                    }
295                }
296                if (j > 0) {
297                    GeomPoint p2 = array.get(i - 1).get(j).toDimension(3);
298                    GeomPoint p3 = array.get(i - 1).get(j - 1).toDimension(3);
299                    GeomPoint p4 = array.get(i).get(j - 1).toDimension(3);
300                    GeomVector v1 = p2.minus(p1).toGeomVector();
301                    GeomVector v2 = p3.minus(p1).toGeomVector();
302                    if (!v1.mag().isApproxZero() && !v2.mag().isApproxZero()) {
303                        GeomVector v1xv2 = v1.cross(v2).toUnitVector();
304                        if (!v1xv2.get(0).isNaN())
305                            neighbors.add(v1xv2);
306                    }
307                    v1 = v2;
308                    v2 = p4.minus(p1).toGeomVector();
309                    if (!v1.mag().isApproxZero() && !v2.mag().isApproxZero()) {
310                        GeomVector v1xv2 = v1.cross(v2).toUnitVector();
311                        if (!v1xv2.get(0).isNaN())
312                            neighbors.add(v1xv2);
313                    }
314                }
315            }
316            if (i < numRows) {
317                if (j < numCols) {
318                    GeomPoint p2 = array.get(i + 1).get(j).toDimension(3);
319                    GeomPoint p3 = array.get(i + 1).get(j + 1).toDimension(3);
320                    GeomPoint p4 = array.get(i).get(j + 1).toDimension(3);
321                    GeomVector v1 = p2.minus(p1).toGeomVector();
322                    GeomVector v2 = p3.minus(p1).toGeomVector();
323                    if (!v1.mag().isApproxZero() && !v2.mag().isApproxZero()) {
324                        GeomVector v1xv2 = v1.cross(v2).toUnitVector();
325                        if (!v1xv2.get(0).isNaN())
326                            neighbors.add(v1xv2);
327                    }
328                    v1 = v2;
329                    v2 = p4.minus(p1).toGeomVector();
330                    if (!v1.mag().isApproxZero() && !v2.mag().isApproxZero()) {
331                        GeomVector v1xv2 = v1.cross(v2).toUnitVector();
332                        if (!v1xv2.get(0).isNaN())
333                            neighbors.add(v1xv2);
334                    }
335                }
336                if (j > 0) {
337                    GeomPoint p2 = array.get(i).get(j - 1).toDimension(3);
338                    GeomPoint p3 = array.get(i + 1).get(j).toDimension(3);
339                    GeomVector v1 = p2.minus(p1).toGeomVector();
340                    GeomVector v2 = p3.minus(p1).toGeomVector();
341                    if (!v1.mag().isApproxZero() && !v2.mag().isApproxZero()) {
342                        GeomVector v1xv2 = v1.cross(v2).toUnitVector();
343                        if (!v1xv2.get(0).isNaN())
344                            neighbors.add(v1xv2);
345                    }
346                }
347            }
348
349            //  Average together the normal vectors from all the neighbors.
350            Vector sum = Vector.valueOf(0, 0, 0);
351            int size = neighbors.size();
352            if (size > 0) {
353                for (int idx = 0; idx < size; ++idx) {
354                    sum = sum.plus(neighbors.get(idx));
355                }
356                sum = sum.divide(size);
357            } else
358                sum = Vector.valueOf(1, 0, 0);
359
360            float[] normal = new float[3];
361            normal[0] = (float)sum.getValue(0);
362            normal[1] = (float)sum.getValue(1);
363            normal[2] = (float)sum.getValue(2);
364
365            FastTable.recycle(neighbors);
366
367            return normal;
368        }
369    }
370
371    /**
372     * A vertex in a BDS mesh.
373     */
374    private class BDSVertex {
375
376        private final float[] pos = new float[3];
377        private final float[] norm;
378
379        public BDSVertex(GeomPoint point, float[] normal) {
380            norm = normal;
381
382            pos[0] = (float)point.getValue(0);
383            pos[1] = (float)point.getValue(1);
384            pos[2] = (float)point.getValue(2);
385        }
386
387        public void save(PrintWriter out) {
388            // Location data.
389            //  Swap Y & Z since bdStudio's CS is different than geomss's.
390            out.print(pos[0]);
391            out.print(" ");
392            out.print(pos[2]);
393            out.print(" ");
394            out.print(pos[1]);
395            out.print(" ");
396
397            //  Normal data.
398            out.print(norm[0]);
399            out.print(" ");
400            out.print(norm[2]);
401            out.print(" ");
402            out.println(norm[1]);
403        }
404    }
405
406}