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}