1 /* 2 * ModelManager.java 4 juil. 07 3 * 4 * Sweet Home 3D, Copyright (c) 2007 Emmanuel PUYBARET / eTeks <info@eteks.com> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 */ 20 package com.eteks.sweethome3d.j3d; 21 22 import java.awt.EventQueue; 23 import java.awt.Shape; 24 import java.awt.geom.AffineTransform; 25 import java.awt.geom.Area; 26 import java.awt.geom.GeneralPath; 27 import java.awt.geom.PathIterator; 28 import java.awt.geom.Rectangle2D; 29 import java.io.File; 30 import java.io.IOException; 31 import java.lang.reflect.Constructor; 32 import java.lang.reflect.InvocationTargetException; 33 import java.lang.reflect.Modifier; 34 import java.net.JarURLConnection; 35 import java.net.URISyntaxException; 36 import java.net.URL; 37 import java.net.URLConnection; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.Comparator; 41 import java.util.Enumeration; 42 import java.util.HashMap; 43 import java.util.HashSet; 44 import java.util.IdentityHashMap; 45 import java.util.Iterator; 46 import java.util.LinkedHashSet; 47 import java.util.List; 48 import java.util.Map; 49 import java.util.Set; 50 import java.util.TreeSet; 51 import java.util.WeakHashMap; 52 import java.util.concurrent.ExecutorService; 53 import java.util.concurrent.Executors; 54 55 import javax.media.j3d.Appearance; 56 import javax.media.j3d.BoundingBox; 57 import javax.media.j3d.Bounds; 58 import javax.media.j3d.BranchGroup; 59 import javax.media.j3d.ColoringAttributes; 60 import javax.media.j3d.Geometry; 61 import javax.media.j3d.GeometryArray; 62 import javax.media.j3d.GeometryStripArray; 63 import javax.media.j3d.Group; 64 import javax.media.j3d.IndexedGeometryArray; 65 import javax.media.j3d.IndexedGeometryStripArray; 66 import javax.media.j3d.IndexedLineArray; 67 import javax.media.j3d.IndexedLineStripArray; 68 import javax.media.j3d.IndexedQuadArray; 69 import javax.media.j3d.IndexedTriangleArray; 70 import javax.media.j3d.IndexedTriangleFanArray; 71 import javax.media.j3d.IndexedTriangleStripArray; 72 import javax.media.j3d.Light; 73 import javax.media.j3d.LineArray; 74 import javax.media.j3d.LineAttributes; 75 import javax.media.j3d.LineStripArray; 76 import javax.media.j3d.Link; 77 import javax.media.j3d.Material; 78 import javax.media.j3d.Node; 79 import javax.media.j3d.PointAttributes; 80 import javax.media.j3d.PolygonAttributes; 81 import javax.media.j3d.QuadArray; 82 import javax.media.j3d.RenderingAttributes; 83 import javax.media.j3d.Shape3D; 84 import javax.media.j3d.SharedGroup; 85 import javax.media.j3d.TexCoordGeneration; 86 import javax.media.j3d.Texture; 87 import javax.media.j3d.TextureAttributes; 88 import javax.media.j3d.Transform3D; 89 import javax.media.j3d.TransformGroup; 90 import javax.media.j3d.TransparencyAttributes; 91 import javax.media.j3d.TriangleArray; 92 import javax.media.j3d.TriangleFanArray; 93 import javax.media.j3d.TriangleStripArray; 94 import javax.vecmath.Color3f; 95 import javax.vecmath.Matrix3f; 96 import javax.vecmath.Point3d; 97 import javax.vecmath.Point3f; 98 import javax.vecmath.TexCoord2f; 99 import javax.vecmath.Vector3d; 100 import javax.vecmath.Vector3f; 101 102 import com.eteks.sweethome3d.model.CatalogTexture; 103 import com.eteks.sweethome3d.model.Content; 104 import com.eteks.sweethome3d.model.HomeMaterial; 105 import com.eteks.sweethome3d.model.HomePieceOfFurniture; 106 import com.eteks.sweethome3d.model.HomeTexture; 107 import com.eteks.sweethome3d.model.Room; 108 import com.eteks.sweethome3d.tools.OperatingSystem; 109 import com.eteks.sweethome3d.tools.SimpleURLContent; 110 import com.eteks.sweethome3d.tools.TemporaryURLContent; 111 import com.eteks.sweethome3d.tools.URLContent; 112 import com.sun.j3d.loaders.IncorrectFormatException; 113 import com.sun.j3d.loaders.Loader; 114 import com.sun.j3d.loaders.ParsingErrorException; 115 import com.sun.j3d.loaders.Scene; 116 import com.sun.j3d.loaders.lw3d.Lw3dLoader; 117 118 /** 119 * Singleton managing 3D models cache. 120 * This manager supports 3D models with an OBJ, DAE, 3DS or LWS format by default. 121 * Additional classes implementing Java 3D <code>Loader</code> interface may be 122 * specified in the <code>com.eteks.sweethome3d.j3d.additionalLoaderClasses</code> 123 * (separated by a space or a colon :) to enable the support of other formats.<br> 124 * Note: this class is compatible with Java 3D 1.3. 125 * @author Emmanuel Puybaret 126 */ 127 public class ModelManager { 128 /** 129 * <code>Shape3D</code> user data prefix for window pane shapes. 130 */ 131 public static final String WINDOW_PANE_SHAPE_PREFIX = "sweethome3d_window_pane"; 132 /** 133 * <code>Shape3D</code> user data prefix for mirror shapes. 134 */ 135 public static final String MIRROR_SHAPE_PREFIX = "sweethome3d_window_mirror"; 136 /** 137 * <code>Shape3D</code> user data prefix for lights. 138 */ 139 public static final String LIGHT_SHAPE_PREFIX = "sweethome3d_light"; 140 /** 141 * <code>Node</code> user data prefix for mannequin parts. 142 */ 143 public static final String MANNEQUIN_ABDOMEN_PREFIX = "sweethome3d_mannequin_abdomen"; 144 public static final String MANNEQUIN_CHEST_PREFIX = "sweethome3d_mannequin_chest"; 145 public static final String MANNEQUIN_PELVIS_PREFIX = "sweethome3d_mannequin_pelvis"; 146 public static final String MANNEQUIN_NECK_PREFIX = "sweethome3d_mannequin_neck"; 147 public static final String MANNEQUIN_HEAD_PREFIX = "sweethome3d_mannequin_head"; 148 public static final String MANNEQUIN_LEFT_SHOULDER_PREFIX = "sweethome3d_mannequin_left_shoulder"; 149 public static final String MANNEQUIN_LEFT_ARM_PREFIX = "sweethome3d_mannequin_left_arm"; 150 public static final String MANNEQUIN_LEFT_ELBOW_PREFIX = "sweethome3d_mannequin_left_elbow"; 151 public static final String MANNEQUIN_LEFT_FOREARM_PREFIX = "sweethome3d_mannequin_left_forearm"; 152 public static final String MANNEQUIN_LEFT_WRIST_PREFIX = "sweethome3d_mannequin_left_wrist"; 153 public static final String MANNEQUIN_LEFT_HAND_PREFIX = "sweethome3d_mannequin_left_hand"; 154 public static final String MANNEQUIN_LEFT_HIP_PREFIX = "sweethome3d_mannequin_left_hip"; 155 public static final String MANNEQUIN_LEFT_THIGH_PREFIX = "sweethome3d_mannequin_left_thigh"; 156 public static final String MANNEQUIN_LEFT_KNEE_PREFIX = "sweethome3d_mannequin_left_knee"; 157 public static final String MANNEQUIN_LEFT_LEG_PREFIX = "sweethome3d_mannequin_left_leg"; 158 public static final String MANNEQUIN_LEFT_ANKLE_PREFIX = "sweethome3d_mannequin_left_ankle"; 159 public static final String MANNEQUIN_LEFT_FOOT_PREFIX = "sweethome3d_mannequin_left_foot"; 160 public static final String MANNEQUIN_RIGHT_SHOULDER_PREFIX = "sweethome3d_mannequin_right_shoulder"; 161 public static final String MANNEQUIN_RIGHT_ARM_PREFIX = "sweethome3d_mannequin_right_arm"; 162 public static final String MANNEQUIN_RIGHT_ELBOW_PREFIX = "sweethome3d_mannequin_right_elbow"; 163 public static final String MANNEQUIN_RIGHT_FOREARM_PREFIX = "sweethome3d_mannequin_right_forearm"; 164 public static final String MANNEQUIN_RIGHT_WRIST_PREFIX = "sweethome3d_mannequin_right_wrist"; 165 public static final String MANNEQUIN_RIGHT_HAND_PREFIX = "sweethome3d_mannequin_right_hand"; 166 public static final String MANNEQUIN_RIGHT_HIP_PREFIX = "sweethome3d_mannequin_right_hip"; 167 public static final String MANNEQUIN_RIGHT_THIGH_PREFIX = "sweethome3d_mannequin_right_thigh"; 168 public static final String MANNEQUIN_RIGHT_KNEE_PREFIX = "sweethome3d_mannequin_right_knee"; 169 public static final String MANNEQUIN_RIGHT_LEG_PREFIX = "sweethome3d_mannequin_right_leg"; 170 public static final String MANNEQUIN_RIGHT_ANKLE_PREFIX = "sweethome3d_mannequin_right_ankle"; 171 public static final String MANNEQUIN_RIGHT_FOOT_PREFIX = "sweethome3d_mannequin_right_foot"; 172 173 public static final String MANNEQUIN_ABDOMEN_CHEST_PREFIX = "sweethome3d_mannequin_abdomen_chest"; 174 public static final String MANNEQUIN_ABDOMEN_PELVIS_PREFIX = "sweethome3d_mannequin_abdomen_pelvis"; 175 /** 176 * <code>Node</code> user data prefix for ball / rotating joints. 177 */ 178 public static final String BALL_PREFIX = "sweethome3d_ball_"; 179 public static final String ARM_ON_BALL_PREFIX = "sweethome3d_arm_on_ball_"; 180 /** 181 * <code>Node</code> user data prefix for hinge / rotating opening joints. 182 */ 183 public static final String HINGE_PREFIX = "sweethome3d_hinge_"; 184 public static final String OPENING_ON_HINGE_PREFIX = "sweethome3d_opening_on_hinge_"; 185 public static final String WINDOW_PANE_ON_HINGE_PREFIX = WINDOW_PANE_SHAPE_PREFIX + "_on_hinge_"; 186 public static final String MIRROR_ON_HINGE_PREFIX = MIRROR_SHAPE_PREFIX + "_on_hinge_"; 187 /** 188 * <code>Node</code> user data prefix for rail / sliding opening joints. 189 */ 190 public static final String UNIQUE_RAIL_PREFIX = "sweethome3d_unique_rail"; 191 public static final String RAIL_PREFIX = "sweethome3d_rail_"; 192 public static final String OPENING_ON_RAIL_PREFIX = "sweethome3d_opening_on_rail_"; 193 public static final String WINDOW_PANE_ON_RAIL_PREFIX = WINDOW_PANE_SHAPE_PREFIX + "_on_rail_"; 194 public static final String MIRROR_ON_RAIL_PREFIX = MIRROR_SHAPE_PREFIX + "_on_rail_"; 195 /** 196 * Deformable group suffix. 197 */ 198 public static final String DEFORMABLE_TRANSFORM_GROUP_SUFFIX = "_transformation"; 199 200 private static final TransparencyAttributes WINDOW_PANE_TRANSPARENCY_ATTRIBUTES = 201 new TransparencyAttributes(TransparencyAttributes.NICEST, 0.5f); 202 203 private static final Material DEFAULT_MATERIAL = new Material(); 204 205 private static final float MINIMUM_SIZE = 0.001f; 206 207 private static final String ADDITIONAL_LOADER_CLASSES = "com.eteks.sweethome3d.j3d.additionalLoaderClasses"; 208 209 private static ModelManager instance; 210 211 // Map storing loaded model nodes 212 private Map<Content, BranchGroup> loadedModelNodes; 213 // Map storing model nodes being loaded 214 private Map<Content, List<ModelObserver>> loadingModelObservers; 215 // Map storing the bounds of transformed model nodes 216 private Map<Content, Map<Transform3D, BoundingBox>> transformedModelNodeBounds; 217 // Executor used to load models 218 private ExecutorService modelsLoader; 219 // List of additional loader classes 220 private Class<Loader> [] additionalLoaderClasses; 221 ModelManager()222 private ModelManager() { 223 // This class is a singleton 224 this.loadedModelNodes = new WeakHashMap<Content, BranchGroup>(); 225 this.loadingModelObservers = new HashMap<Content, List<ModelObserver>>(); 226 this.transformedModelNodeBounds = new WeakHashMap<Content, Map<Transform3D, BoundingBox>>(); 227 // Load other optional Loader classes 228 List<Class<Loader>> loaderClasses = new ArrayList<Class<Loader>>(); 229 String loaderClassNames = System.getProperty(ADDITIONAL_LOADER_CLASSES); 230 if (loaderClassNames != null) { 231 for (String loaderClassName : loaderClassNames.split("\\s|:")) { 232 try { 233 loaderClasses.add(getLoaderClass(loaderClassName)); 234 } catch (IllegalArgumentException ex) { 235 System.err.println("Invalid loader class " + loaderClassName + ":\n" + ex.getMessage()); 236 } 237 } 238 } 239 this.additionalLoaderClasses = loaderClasses.toArray(new Class [loaderClasses.size()]); 240 } 241 242 /** 243 * Returns the class of name <code>loaderClassName</code>. 244 */ 245 @SuppressWarnings("unchecked") getLoaderClass(String loaderClassName)246 private Class<Loader> getLoaderClass(String loaderClassName) { 247 try { 248 Class<Loader> loaderClass = (Class<Loader>)getClass().getClassLoader().loadClass(loaderClassName); 249 if (!Loader.class.isAssignableFrom(loaderClass)) { 250 throw new IllegalArgumentException(loaderClassName + " not a subclass of " + Loader.class.getName()); 251 } else if (Modifier.isAbstract(loaderClass.getModifiers()) || !Modifier.isPublic(loaderClass.getModifiers())) { 252 throw new IllegalArgumentException(loaderClassName + " not a public static class"); 253 } 254 Constructor<Loader> constructor = loaderClass.getConstructor(new Class [0]); 255 // Try to instantiate it now to see if it won't cause any problem 256 constructor.newInstance(new Object [0]); 257 return loaderClass; 258 } catch (ClassNotFoundException ex) { 259 throw new IllegalArgumentException(ex.getMessage(), ex); 260 } catch (NoSuchMethodException ex) { 261 throw new IllegalArgumentException(ex.getMessage(), ex); 262 } catch (InvocationTargetException ex) { 263 throw new IllegalArgumentException(ex.getMessage(), ex); 264 } catch (IllegalAccessException ex) { 265 throw new IllegalArgumentException(loaderClassName + " constructor not accessible"); 266 } catch (InstantiationException ex) { 267 throw new IllegalArgumentException(loaderClassName + " not a public static class"); 268 } 269 } 270 271 /** 272 * Returns an instance of this singleton. 273 */ getInstance()274 public static ModelManager getInstance() { 275 if (instance == null) { 276 instance = new ModelManager(); 277 } 278 return instance; 279 } 280 281 /** 282 * Shutdowns the multithreaded service that load models and clears loaded models cache. 283 */ clear()284 public void clear() { 285 if (this.modelsLoader != null) { 286 this.modelsLoader.shutdownNow(); 287 this.modelsLoader = null; 288 } 289 synchronized (this.loadedModelNodes) { 290 this.loadedModelNodes.clear(); 291 } 292 this.loadingModelObservers.clear(); 293 } 294 295 /** 296 * Returns the minimum size of a model. 297 */ getMinimumSize()298 float getMinimumSize() { 299 return MINIMUM_SIZE; 300 } 301 302 /** 303 * Returns the size of 3D shapes of <code>node</code>. 304 * This method computes the exact box that contains all the shapes, 305 * contrary to <code>node.getBounds()</code> that returns a bounding 306 * sphere for a scene. 307 * @param node the root of a model 308 */ getSize(Node node)309 public Vector3f getSize(Node node) { 310 return getSize(node, new Transform3D()); 311 } 312 313 /** 314 * Returns the size of 3D shapes of <code>node</code> after an additional <code>transformation</code>. 315 * This method computes the exact box that contains all the shapes, 316 * contrary to <code>node.getBounds()</code> that returns a bounding 317 * sphere for a scene. 318 * @param node the root of a model 319 * @param transformation the transformation applied to the model 320 * or <code>null</code> if no transformation should be applied to node. 321 */ getSize(Node node, Transform3D transformation)322 public Vector3f getSize(Node node, Transform3D transformation) { 323 BoundingBox bounds = getBounds(node, transformation); 324 Point3d lower = new Point3d(); 325 bounds.getLower(lower); 326 Point3d upper = new Point3d(); 327 bounds.getUpper(upper); 328 return new Vector3f(Math.max(getMinimumSize(), (float)(upper.x - lower.x)), 329 Math.max(getMinimumSize(), (float)(upper.y - lower.y)), 330 Math.max(getMinimumSize(), (float)(upper.z - lower.z))); 331 } 332 333 /** 334 * Returns the center of the bounds of <code>node</code> 3D shapes. 335 * @param node the root of a model 336 */ getCenter(Node node)337 public Point3f getCenter(Node node) { 338 BoundingBox bounds = getBounds(node); 339 Point3d lower = new Point3d(); 340 bounds.getLower(lower); 341 Point3d upper = new Point3d(); 342 bounds.getUpper(upper); 343 return new Point3f((float)(lower.x + upper.x) / 2, 344 (float)(lower.y + upper.y) / 2, 345 (float)(lower.z + upper.z) / 2); 346 } 347 348 /** 349 * Returns the bounds of the 3D shapes of <code>node</code>. 350 * This method computes the exact box that contains all the shapes, 351 * contrary to <code>node.getBounds()</code> that returns a bounding 352 * sphere for a scene. 353 * @param node the root of a model 354 */ getBounds(Node node)355 public BoundingBox getBounds(Node node) { 356 return getBounds(node, new Transform3D()); 357 } 358 359 /** 360 * Returns the bounds of the 3D shapes of <code>node</code> with an additional <code>transformation</code>. 361 * This method computes the exact box that contains all the shapes, contrary to <code>node.getBounds()</code> 362 * that returns a bounding sphere for a scene. 363 * @param node the root of a model 364 * @param transformation the transformation applied to the model 365 * or <code>null</code> if no transformation should be applied to node. 366 */ getBounds(Node node, Transform3D transformation)367 public BoundingBox getBounds(Node node, Transform3D transformation) { 368 BoundingBox objectBounds = new BoundingBox( 369 new Point3d(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY), 370 new Point3d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY)); 371 computeBounds(node, objectBounds, transformation, !isOrthogonalRotation(transformation), isDeformed(node)); 372 Point3d lower = new Point3d(); 373 objectBounds.getLower(lower); 374 if (lower.x == Double.POSITIVE_INFINITY) { 375 throw new IllegalArgumentException("Node has no bounds"); 376 } 377 return objectBounds; 378 } 379 380 /** 381 * Returns <code>true</code> if the rotation matrix matches only rotations of 382 * a multiple of 90� degrees around x, y or z axis. 383 */ isOrthogonalRotation(Transform3D transformation)384 private boolean isOrthogonalRotation(Transform3D transformation) { 385 Matrix3f matrix = new Matrix3f(); 386 transformation.get(matrix); 387 for (int i = 0; i < 3; i++) { 388 for (int j = 0; j < 3; j++) { 389 // Return false if the matrix contains a value different from 0 1 or -1 390 if (Math.abs(matrix.getElement(i, j)) > 1E-6 391 && Math.abs(matrix.getElement(i, j) - 1) > 1E-6 392 && Math.abs(matrix.getElement(i, j) + 1) > 1E-6) { 393 return false; 394 } 395 } 396 } 397 return true; 398 } 399 computeBounds(Node node, BoundingBox bounds, Transform3D parentTransformation, boolean transformShapeGeometry, boolean deformedGeometry)400 private void computeBounds(Node node, BoundingBox bounds, Transform3D parentTransformation, 401 boolean transformShapeGeometry, boolean deformedGeometry) { 402 if (node instanceof Group) { 403 Map<Transform3D, BoundingBox> modelBounds = null; 404 BoundingBox transformationModelBounds = null; 405 if (node instanceof TransformGroup) { 406 parentTransformation = new Transform3D(parentTransformation); 407 Transform3D transform = new Transform3D(); 408 ((TransformGroup)node).getTransform(transform); 409 parentTransformation.mul(transform); 410 } else if (transformShapeGeometry 411 && !deformedGeometry 412 && node instanceof BranchGroup 413 && node.getUserData() instanceof Content) { 414 // Check if it's the node of a model 415 modelBounds = this.transformedModelNodeBounds.get(node.getUserData()); 416 if (modelBounds != null) { 417 // Retrieve the bounds that may have been previously computed for the requested transformation 418 transformationModelBounds = modelBounds.get(parentTransformation); 419 } 420 } 421 422 if (transformationModelBounds == null) { 423 BoundingBox combinedBounds; 424 if (modelBounds != null) { 425 combinedBounds = new BoundingBox( 426 new Point3d(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY), 427 new Point3d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY)); 428 } else { 429 combinedBounds = bounds; 430 } 431 432 // Compute the bounds of all the node children 433 Enumeration<?> enumeration = ((Group)node).getAllChildren(); 434 while (enumeration.hasMoreElements ()) { 435 computeBounds((Node)enumeration.nextElement(), combinedBounds, parentTransformation, 436 transformShapeGeometry, deformedGeometry); 437 } 438 439 if (modelBounds != null) { 440 // Store the computed bounds of the model 441 modelBounds.put(parentTransformation, transformationModelBounds = combinedBounds); 442 } 443 } 444 445 if (transformationModelBounds != null) { 446 bounds.combine(transformationModelBounds); 447 } 448 } else if (node instanceof Link) { 449 computeBounds(((Link)node).getSharedGroup(), bounds, parentTransformation, 450 transformShapeGeometry, deformedGeometry); 451 } else if (node instanceof Shape3D) { 452 Shape3D shape = (Shape3D)node; 453 Bounds shapeBounds; 454 if (transformShapeGeometry 455 || deformedGeometry 456 && !isOrthogonalRotation(parentTransformation)) { 457 shapeBounds = computeTransformedGeometryBounds(shape, parentTransformation); 458 } else { 459 shapeBounds = shape.getBounds(); 460 shapeBounds.transform(parentTransformation); 461 } 462 bounds.combine(shapeBounds); 463 } 464 } 465 computeTransformedGeometryBounds(Shape3D shape, Transform3D transformation)466 private Bounds computeTransformedGeometryBounds(Shape3D shape, Transform3D transformation) { 467 Point3d lower = new Point3d(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); 468 Point3d upper = new Point3d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); 469 for (int i = 0, n = shape.numGeometries(); i < n; i++) { 470 Geometry geometry = shape.getGeometry(i); 471 if (geometry instanceof GeometryArray) { 472 GeometryArray geometryArray = (GeometryArray)geometry; 473 int vertexCount = geometryArray.getVertexCount(); 474 Point3f vertex = new Point3f(); 475 if ((geometryArray.getVertexFormat() & GeometryArray.BY_REFERENCE) != 0) { 476 if ((geometryArray.getVertexFormat() & GeometryArray.INTERLEAVED) != 0) { 477 float [] vertexData = geometryArray.getInterleavedVertices(); 478 int vertexSize = vertexData.length / vertexCount; 479 for (int index = 0, j = vertexSize - 3; index < vertexCount; j += vertexSize, index++) { 480 vertex.x = vertexData [j]; 481 vertex.y = vertexData [j + 1]; 482 vertex.z = vertexData [j + 2]; 483 updateBounds(vertex, transformation, lower, upper); 484 } 485 } else { 486 float [] vertexCoordinates = geometryArray.getCoordRefFloat(); 487 for (int index = 0, j = 0; index < vertexCount; j += 3, index++) { 488 vertex.x = vertexCoordinates [j]; 489 vertex.y = vertexCoordinates [j + 1]; 490 vertex.z = vertexCoordinates [j + 2]; 491 updateBounds(vertex, transformation, lower, upper); 492 } 493 } 494 } else { 495 for (int index = 0; index < vertexCount; index++) { 496 geometryArray.getCoordinate(index, vertex); 497 updateBounds(vertex, transformation, lower, upper); 498 } 499 } 500 } else { 501 Bounds shapeBounds = shape.getBounds(); 502 shapeBounds.transform(transformation); 503 return shapeBounds; 504 } 505 } 506 Bounds shapeBounds = new BoundingBox(lower, upper); 507 return shapeBounds; 508 } 509 updateBounds(Point3f vertex, Transform3D transformation, Point3d lower, Point3d upper)510 private void updateBounds(Point3f vertex, Transform3D transformation, Point3d lower, Point3d upper) { 511 transformation.transform(vertex); 512 if (lower.x > vertex.x) { 513 lower.x = vertex.x; 514 } 515 if (lower.y > vertex.y) { 516 lower.y = vertex.y; 517 } 518 if (lower.z > vertex.z) { 519 lower.z = vertex.z; 520 } 521 if (upper.x < vertex.x) { 522 upper.x = vertex.x; 523 } 524 if (upper.y < vertex.y) { 525 upper.y = vertex.y; 526 } 527 if (upper.z < vertex.z) { 528 upper.z = vertex.z; 529 } 530 } 531 532 /** 533 * Returns a transform group that will transform the model <code>node</code> 534 * to let it fill a box of the given <code>width</code> centered on the origin. 535 * @param node the root of a model with any size and location 536 * @param modelRotation the rotation applied to the model before normalization 537 * or <code>null</code> if no transformation should be applied to node 538 * @param width the width of the box 539 */ getNormalizedTransformGroup(Node node, float [][] modelRotation, float width)540 public TransformGroup getNormalizedTransformGroup(Node node, float [][] modelRotation, float width) { 541 return new TransformGroup(getNormalizedTransform(node, modelRotation, width, true)); 542 } 543 544 /** 545 * Returns a transform group that will transform the model <code>node</code> 546 * to let it fill a box of the given <code>width</code> centered on the origin. 547 * @param node the root of a model with any size and location 548 * @param modelRotation the rotation applied to the model before normalization 549 * or <code>null</code> if no transformation should be applied to node 550 * @param width the width of the box 551 * @param modelCenteredAtOrigin if <code>true</code> center will be moved to match the origin 552 * after the model rotation is applied 553 */ getNormalizedTransformGroup(Node node, float [][] modelRotation, float width, boolean modelCenteredAtOrigin)554 public TransformGroup getNormalizedTransformGroup(Node node, float [][] modelRotation, float width, 555 boolean modelCenteredAtOrigin) { 556 return new TransformGroup(getNormalizedTransform(node, modelRotation, width, modelCenteredAtOrigin)); 557 } 558 559 /** 560 * Returns a transform that will transform the model <code>node</code> 561 * to let it fill a box of the given <code>width</code> centered on the origin. 562 * @param node the root of a model with any size and location 563 * @param modelRotation the rotation applied to the model before normalization 564 * or <code>null</code> if no transformation should be applied to node 565 * @param width the width of the box 566 */ getNormalizedTransform(Node node, float [][] modelRotation, float width)567 public Transform3D getNormalizedTransform(Node node, float [][] modelRotation, float width) { 568 return getNormalizedTransform(node, modelRotation, width, true); 569 } 570 571 /** 572 * Returns a transform that will transform the model <code>node</code> 573 * to let it fill a box of the given <code>width</code> centered on the origin. 574 * @param node the root of a model with any size and location 575 * @param modelRotation the rotation applied to the model before normalization 576 * or <code>null</code> if no transformation should be applied to node 577 * @param width the width of the box 578 * @param modelCenteredAtOrigin if <code>true</code> center will be moved to match the origin 579 * after the model rotation is applied 580 */ getNormalizedTransform(Node node, float [][] modelRotation, float width, boolean modelCenteredAtOrigin)581 public Transform3D getNormalizedTransform(Node node, float [][] modelRotation, float width, 582 boolean modelCenteredAtOrigin) { 583 // Get model bounding box size 584 BoundingBox modelBounds = getBounds(node); 585 Point3d lower = new Point3d(); 586 modelBounds.getLower(lower); 587 Point3d upper = new Point3d(); 588 modelBounds.getUpper(upper); 589 // Translate model to its center 590 Transform3D translation = new Transform3D(); 591 translation.setTranslation(new Vector3d( 592 -lower.x - (upper.x - lower.x) / 2, 593 -lower.y - (upper.y - lower.y) / 2, 594 -lower.z - (upper.z - lower.z) / 2)); 595 596 Transform3D modelTransform; 597 if (modelRotation != null) { 598 // Get model bounding box size with model rotation 599 Transform3D rotationTransform = getRotationTransformation(modelRotation); 600 rotationTransform.mul(translation); 601 BoundingBox rotatedModelBounds = getBounds(node, rotationTransform); 602 rotatedModelBounds.getLower(lower); 603 rotatedModelBounds.getUpper(upper); 604 modelTransform = new Transform3D(); 605 if (modelCenteredAtOrigin) { 606 // Move model back to its new center 607 modelTransform.setTranslation(new Vector3d( 608 -lower.x - (upper.x - lower.x) / 2, 609 -lower.y - (upper.y - lower.y) / 2, 610 -lower.z - (upper.z - lower.z) / 2)); 611 } 612 modelTransform.mul(rotationTransform); 613 } else { 614 modelTransform = translation; 615 } 616 617 // Scale model to make it fill a 1 unit wide box 618 Transform3D scaleOneTransform = new Transform3D(); 619 scaleOneTransform.setScale ( 620 new Vector3d(width / Math.max(getMinimumSize(), upper.x -lower.x), 621 width / Math.max(getMinimumSize(), upper.y - lower.y), 622 width / Math.max(getMinimumSize(), upper.z - lower.z))); 623 scaleOneTransform.mul(modelTransform); 624 return scaleOneTransform; 625 } 626 627 /** 628 * Returns a transformation matching the given rotation. 629 */ getRotationTransformation(float [][] modelRotation)630 Transform3D getRotationTransformation(float [][] modelRotation) { 631 Matrix3f modelRotationMatrix = new Matrix3f(modelRotation [0][0], modelRotation [0][1], modelRotation [0][2], 632 modelRotation [1][0], modelRotation [1][1], modelRotation [1][2], 633 modelRotation [2][0], modelRotation [2][1], modelRotation [2][2]); 634 Transform3D modelTransform = new Transform3D(); 635 modelTransform.setRotation(modelRotationMatrix); 636 return modelTransform; 637 } 638 639 /** 640 * Returns a transformation able to place in the scene the normalized model 641 * of the given <code>piece</code>. 642 * @param piece a piece of furniture 643 * @param normalizedModelNode the node matching the normalized model of the piece. 644 * This parameter is required only if the piece is rotated horizontally. 645 */ getPieceOfFurnitureNormalizedModelTransformation(HomePieceOfFurniture piece, Node normalizedModelNode)646 Transform3D getPieceOfFurnitureNormalizedModelTransformation(HomePieceOfFurniture piece, Node normalizedModelNode) { 647 // Set piece size 648 Transform3D scale = new Transform3D(); 649 float pieceWidth = piece.getWidth(); 650 // If piece model is mirrored, inverse its width 651 if (piece.isModelMirrored()) { 652 pieceWidth *= -1; 653 } 654 scale.setScale(new Vector3d(pieceWidth, piece.getHeight(), piece.getDepth())); 655 656 Transform3D modelTransform; 657 float height; 658 if (piece.isHorizontallyRotated() && normalizedModelNode != null) { 659 Transform3D horizontalRotationAndScale = new Transform3D(); 660 // Change its angles around horizontal axes 661 if (piece.getPitch() != 0) { 662 horizontalRotationAndScale.rotX(-piece.getPitch()); 663 } 664 if (piece.getRoll() != 0) { 665 Transform3D rollRotation = new Transform3D(); 666 rollRotation.rotZ(-piece.getRoll()); 667 horizontalRotationAndScale.mul(rollRotation, horizontalRotationAndScale); 668 } 669 horizontalRotationAndScale.mul(scale); 670 671 // Compute center location when the piece is rotated around horizontal axes 672 BoundingBox rotatedModelBounds = getBounds(normalizedModelNode, horizontalRotationAndScale); 673 Point3d lower = new Point3d(); 674 rotatedModelBounds.getLower(lower); 675 Point3d upper = new Point3d(); 676 rotatedModelBounds.getUpper(upper); 677 modelTransform = new Transform3D(); 678 modelTransform.setTranslation(new Vector3d( 679 -lower.x - (upper.x - lower.x) / 2, 680 -lower.y - (upper.y - lower.y) / 2, 681 -lower.z - (upper.z - lower.z) / 2)); 682 modelTransform.mul(horizontalRotationAndScale); 683 height = (float)Math.max(getMinimumSize(), upper.y - lower.y); 684 } else { 685 modelTransform = scale; 686 height = piece.getHeight(); 687 } 688 689 // Change its angle around vertical axis 690 Transform3D verticalRotation = new Transform3D(); 691 verticalRotation.rotY(-piece.getAngle()); 692 verticalRotation.mul(modelTransform); 693 694 // Translate it to its location 695 Transform3D pieceTransform = new Transform3D(); 696 float levelElevation; 697 if (piece.getLevel() != null) { 698 levelElevation = piece.getLevel().getElevation(); 699 } else { 700 levelElevation = 0; 701 } 702 pieceTransform.setTranslation(new Vector3f( 703 piece.getX(), 704 piece.getElevation() + height / 2 + levelElevation, 705 piece.getY())); 706 pieceTransform.mul(verticalRotation); 707 return pieceTransform; 708 } 709 710 /** 711 * Reads asynchronously a 3D node from <code>content</code> with supported loaders 712 * and notifies the loaded model to the given <code>modelObserver</code> once available. 713 * @param content an object containing a model 714 * @param modelObserver the observer that will be notified once the model is available 715 * or if an error happens 716 * @throws IllegalStateException if the current thread isn't the Event Dispatch Thread. 717 */ loadModel(Content content, ModelObserver modelObserver)718 public void loadModel(Content content, 719 ModelObserver modelObserver) { 720 loadModel(content, false, modelObserver); 721 } 722 723 /** 724 * Reads a 3D node from <code>content</code> with supported loaders 725 * and notifies the loaded model to the given <code>modelObserver</code> once available. 726 * @param content an object containing a model 727 * @param synchronous if <code>true</code>, this method will return only once model content is loaded 728 * @param modelObserver the observer that will be notified once the model is available 729 * or if an error happens. When the model is loaded synchronously, the observer will be notified 730 * in the same thread as the caller, otherwise the observer will be notified in the Event 731 * Dispatch Thread and this method must be called in Event Dispatch Thread too. 732 * @throws IllegalStateException if synchronous is <code>false</code> and the current thread isn't 733 * the Event Dispatch Thread. 734 */ loadModel(final Content content, boolean synchronous, ModelObserver modelObserver)735 public void loadModel(final Content content, 736 boolean synchronous, 737 ModelObserver modelObserver) { 738 BranchGroup modelRoot; 739 synchronized (this.loadedModelNodes) { 740 modelRoot = this.loadedModelNodes.get(content); 741 } 742 if (modelRoot != null) { 743 // Notify cached model to observer with a clone of the model 744 modelObserver.modelUpdated((BranchGroup)cloneNode(modelRoot)); 745 } else if (synchronous) { 746 try { 747 modelRoot = loadModel(content); 748 synchronized (this.loadedModelNodes) { 749 // Store in cache model node for future copies 750 this.loadedModelNodes.put(content, (BranchGroup)modelRoot); 751 this.transformedModelNodeBounds.put(content, new WeakHashMap<Transform3D, BoundingBox>()); 752 } 753 modelObserver.modelUpdated((BranchGroup)cloneNode(modelRoot)); 754 } catch (IOException ex) { 755 modelObserver.modelError(ex); 756 } 757 } else if (!EventQueue.isDispatchThread()) { 758 throw new IllegalStateException("Asynchronous call out of Event Dispatch Thread"); 759 } else { 760 if (this.modelsLoader == null) { 761 this.modelsLoader = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); 762 } 763 List<ModelObserver> observers = this.loadingModelObservers.get(content); 764 if (observers != null) { 765 // If observers list exists, content model is already being loaded 766 // register observer for future notification 767 observers.add(modelObserver); 768 } else { 769 // Create a list of observers that will be notified once content model is loaded 770 observers = new ArrayList<ModelObserver>(); 771 observers.add(modelObserver); 772 this.loadingModelObservers.put(content, observers); 773 774 // Load the model in an other thread 775 this.modelsLoader.execute(new Runnable() { 776 public void run() { 777 try { 778 final BranchGroup loadedModel = loadModel(content); 779 synchronized (loadedModelNodes) { 780 // Update loaded models cache and notify registered observers 781 loadedModelNodes.put(content, loadedModel); 782 transformedModelNodeBounds.put(content, new WeakHashMap<Transform3D, BoundingBox>()); 783 } 784 EventQueue.invokeLater(new Runnable() { 785 public void run() { 786 List<ModelObserver> observers = loadingModelObservers.remove(content); 787 if (observers != null) { 788 for (final ModelObserver observer : observers) { 789 observer.modelUpdated((BranchGroup)cloneNode(loadedModel)); 790 } 791 } 792 } 793 }); 794 } catch (final IOException ex) { 795 EventQueue.invokeLater(new Runnable() { 796 public void run() { 797 List<ModelObserver> observers = loadingModelObservers.remove(content); 798 if (observers != null) { 799 for (final ModelObserver observer : observers) { 800 observer.modelError(ex); 801 } 802 } 803 } 804 }); 805 } 806 } 807 }); 808 } 809 } 810 } 811 812 /** 813 * Returns a clone of the given <code>node</code>. 814 * All the children and the attributes of the given node are duplicated except the geometries 815 * and the texture images of shapes. 816 */ cloneNode(Node node)817 public Node cloneNode(Node node) { 818 // Clone node in a synchronized block because cloneNodeComponent is not thread safe 819 synchronized (this.loadedModelNodes) { 820 return cloneNode(node, new HashMap<SharedGroup, SharedGroup>()); 821 } 822 } 823 cloneNode(Node node, Map<SharedGroup, SharedGroup> clonedSharedGroups)824 private Node cloneNode(Node node, Map<SharedGroup, SharedGroup> clonedSharedGroups) { 825 if (node instanceof Shape3D) { 826 Shape3D shape = (Shape3D)node; 827 Shape3D clonedShape = (Shape3D)shape.cloneNode(false); 828 Appearance appearance = shape.getAppearance(); 829 if (appearance != null) { 830 // Duplicate node's appearance except its texture 831 Appearance clonedAppearance = (Appearance)appearance.cloneNodeComponent(false); 832 Material material = appearance.getMaterial(); 833 if (material != null) { 834 clonedAppearance.setMaterial((Material)material.cloneNodeComponent(true)); 835 } 836 ColoringAttributes coloringAttributes = appearance.getColoringAttributes(); 837 if (coloringAttributes != null) { 838 clonedAppearance.setColoringAttributes((ColoringAttributes)coloringAttributes.cloneNodeComponent(true)); 839 } 840 TransparencyAttributes transparencyAttributes = appearance.getTransparencyAttributes(); 841 if (transparencyAttributes != null) { 842 clonedAppearance.setTransparencyAttributes((TransparencyAttributes)transparencyAttributes.cloneNodeComponent(true)); 843 } 844 RenderingAttributes renderingAttributes = appearance.getRenderingAttributes(); 845 if (renderingAttributes != null) { 846 clonedAppearance.setRenderingAttributes((RenderingAttributes)renderingAttributes.cloneNodeComponent(true)); 847 } 848 PolygonAttributes polygonAttributes = appearance.getPolygonAttributes(); 849 if (polygonAttributes != null) { 850 clonedAppearance.setPolygonAttributes((PolygonAttributes)polygonAttributes.cloneNodeComponent(true)); 851 } 852 LineAttributes lineAttributes = appearance.getLineAttributes(); 853 if (lineAttributes != null) { 854 clonedAppearance.setLineAttributes((LineAttributes)lineAttributes.cloneNodeComponent(true)); 855 } 856 PointAttributes pointAttributes = appearance.getPointAttributes(); 857 if (pointAttributes != null) { 858 clonedAppearance.setPointAttributes((PointAttributes)pointAttributes.cloneNodeComponent(true)); 859 } 860 TextureAttributes textureAttributes = appearance.getTextureAttributes(); 861 if (textureAttributes != null) { 862 clonedAppearance.setTextureAttributes((TextureAttributes)textureAttributes.cloneNodeComponent(true)); 863 } 864 TexCoordGeneration texCoordGeneration = appearance.getTexCoordGeneration(); 865 if (texCoordGeneration != null) { 866 clonedAppearance.setTexCoordGeneration((TexCoordGeneration)texCoordGeneration.cloneNodeComponent(true)); 867 } 868 869 clonedShape.setAppearance(clonedAppearance); 870 } 871 return clonedShape; 872 } else if (node instanceof Link) { 873 Link clonedLink = (Link)node.cloneNode(true); 874 // Force duplication of shared groups too 875 SharedGroup sharedGroup = clonedLink.getSharedGroup(); 876 if (sharedGroup != null) { 877 SharedGroup clonedSharedGroup = clonedSharedGroups.get(sharedGroup); 878 if (clonedSharedGroup == null) { 879 clonedSharedGroup = (SharedGroup)cloneNode(sharedGroup, clonedSharedGroups); 880 clonedSharedGroups.put(sharedGroup, clonedSharedGroup); 881 } 882 clonedLink.setSharedGroup(clonedSharedGroup); 883 } 884 return clonedLink; 885 } else { 886 Node clonedNode = node.cloneNode(true); 887 if (node instanceof Group) { 888 Group group = (Group)node; 889 Group clonedGroup = (Group)clonedNode; 890 for (int i = 0, n = group.numChildren(); i < n; i++) { 891 Node clonedChild = cloneNode(group.getChild(i), clonedSharedGroups); 892 clonedGroup.addChild(clonedChild); 893 } 894 } 895 return clonedNode; 896 } 897 } 898 899 /** 900 * Returns the node loaded synchronously from <code>content</code> with supported loaders. 901 * This method is threadsafe and may be called from any thread. 902 * @param content an object containing a model 903 */ loadModel(Content content)904 public BranchGroup loadModel(Content content) throws IOException { 905 // Ensure we use a URLContent object 906 URLContent urlContent; 907 if (content instanceof URLContent) { 908 urlContent = (URLContent)content; 909 } else { 910 urlContent = TemporaryURLContent.copyToTemporaryURLContent(content); 911 } 912 Loader [] defaultLoaders = new Loader [] {new OBJLoader(), 913 new DAELoader(), 914 new Max3DSLoader(), 915 new Lw3dLoader()}; 916 Loader [] loaders = new Loader [defaultLoaders.length + this.additionalLoaderClasses.length]; 917 System.arraycopy(defaultLoaders, 0, loaders, 0, defaultLoaders.length); 918 for (int i = 0; i < this.additionalLoaderClasses.length; i++) { 919 try { 920 loaders [defaultLoaders.length + i] = this.additionalLoaderClasses [i].newInstance(); 921 } catch (InstantiationException ex) { 922 // Can't happen: getLoaderClass checked this class is instantiable 923 throw new InternalError(ex.getMessage()); 924 } catch (IllegalAccessException ex) { 925 // Can't happen: getLoaderClass checked this class is instantiable 926 throw new InternalError(ex.getMessage()); 927 } 928 } 929 930 Exception lastException = null; 931 Boolean useCaches = shouldUseCaches(urlContent); 932 for (Loader loader : loaders) { 933 boolean loadSynchronously = false; 934 try { 935 // Call setUseCaches(Boolean) by reflection 936 loader.getClass().getMethod("setUseCaches", Boolean.class).invoke(loader, useCaches); 937 } catch (NoSuchMethodException ex) { 938 // If the method setUseCaches doesn't exist, set default cache use if different 939 // from the required one and load models synchronously 940 URLConnection connection = urlContent.getURL().openConnection(); 941 loadSynchronously = connection.getDefaultUseCaches() != useCaches; 942 } catch (InvocationTargetException ex) { 943 if (ex instanceof Exception) { 944 lastException = (Exception)ex.getTargetException(); 945 continue; 946 } else { 947 ex.printStackTrace(); 948 } 949 } catch (Exception ex) { 950 ex.printStackTrace(); 951 } 952 953 try { 954 // Ask loader to ignore lights, fogs... 955 loader.setFlags(loader.getFlags() 956 & ~(Loader.LOAD_LIGHT_NODES | Loader.LOAD_FOG_NODES 957 | Loader.LOAD_BACKGROUND_NODES | Loader.LOAD_VIEW_GROUPS)); 958 // Return the first scene that can be loaded from model URL content 959 Scene scene; 960 if (loadSynchronously) { 961 synchronized (this) { 962 URLConnection connection = urlContent.getURL().openConnection(); 963 try { 964 connection.setDefaultUseCaches(useCaches); 965 scene = loader.load(urlContent.getURL()); 966 } finally { 967 if (connection.getDefaultUseCaches() == useCaches) { 968 // Restore the default global value only when it didn't change yet, 969 // in case an other thread not synchronized on the same lock changed it 970 connection.setDefaultUseCaches(!useCaches); 971 } 972 } 973 } 974 } else { 975 scene = loader.load(urlContent.getURL()); 976 } 977 978 BranchGroup modelNode = scene.getSceneGroup(); 979 // If model doesn't have any child, consider the file as wrong 980 if (modelNode.numChildren() == 0) { 981 throw new IllegalArgumentException("Empty model"); 982 } 983 984 // Update transparency of scene window panes shapes 985 updateShapeNamesAndWindowPanesTransparency(scene); 986 // Turn off lights because some loaders don't take into account the ~LOAD_LIGHT_NODES flag 987 turnOffLightsShareAndModulateTextures(modelNode, new IdentityHashMap<Texture, Texture>()); 988 updateDeformableModelHierarchy(modelNode); 989 checkAppearancesName(modelNode); 990 replaceMultipleSharedShapes(modelNode); 991 modelNode.setUserData(content); 992 return modelNode; 993 } catch (IllegalArgumentException ex) { 994 lastException = ex; 995 } catch (IncorrectFormatException ex) { 996 lastException = ex; 997 } catch (ParsingErrorException ex) { 998 lastException = ex; 999 } catch (IOException ex) { 1000 lastException = ex; 1001 } catch (RuntimeException ex) { 1002 // Take into account exceptions of Java 3D 1.5 ImageException class 1003 // in such a way program can run in Java 3D 1.3.1 1004 if (ex.getClass().getName().equals("com.sun.j3d.utils.image.ImageException")) { 1005 lastException = ex; 1006 } else { 1007 throw ex; 1008 } 1009 } 1010 } 1011 1012 if (lastException instanceof IOException) { 1013 throw (IOException)lastException; 1014 } else if (lastException instanceof IncorrectFormatException) { 1015 IOException incorrectFormatException = new IOException("Incorrect format"); 1016 incorrectFormatException.initCause(lastException); 1017 throw incorrectFormatException; 1018 } else if (lastException instanceof ParsingErrorException) { 1019 IOException incorrectFormatException = new IOException("Parsing error"); 1020 incorrectFormatException.initCause(lastException); 1021 throw incorrectFormatException; 1022 } else { 1023 IOException otherException = new IOException(); 1024 otherException.initCause(lastException); 1025 throw otherException; 1026 } 1027 } 1028 1029 /** 1030 * Returns <code>true</code> if reading from the given content should be done using caches. 1031 */ shouldUseCaches(URLContent urlContent)1032 private boolean shouldUseCaches(URLContent urlContent) throws IOException { 1033 URLConnection connection = urlContent.getURL().openConnection(); 1034 if (OperatingSystem.isWindows() 1035 && (connection instanceof JarURLConnection)) { 1036 JarURLConnection urlConnection = (JarURLConnection)connection; 1037 URL jarFileUrl = urlConnection.getJarFileURL(); 1038 if (jarFileUrl.getProtocol().equalsIgnoreCase("file")) { 1039 try { 1040 File file; 1041 try { 1042 file = new File(jarFileUrl.toURI()); 1043 } catch (IllegalArgumentException ex) { 1044 // Try a second way to be able to access to files on Windows servers 1045 file = new File(jarFileUrl.getPath()); 1046 } 1047 if (file.canWrite()) { 1048 // Refuse to use caches to be able to delete the writable files accessed with jar protocol under Windows, 1049 // as suggested in http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6962459 1050 return false; 1051 } 1052 } catch (URISyntaxException ex) { 1053 IOException ex2 = new IOException(); 1054 ex2.initCause(ex); 1055 throw ex2; 1056 } 1057 } 1058 } 1059 return connection.getDefaultUseCaches(); 1060 } 1061 1062 /** 1063 * Updates the name of scene shapes and transparency window panes shapes. 1064 */ 1065 @SuppressWarnings("unchecked") updateShapeNamesAndWindowPanesTransparency(Scene scene)1066 private void updateShapeNamesAndWindowPanesTransparency(Scene scene) { 1067 Map<String, Object> namedObjects = scene.getNamedObjects(); 1068 for (Map.Entry<String, Object> entry : namedObjects.entrySet()) { 1069 String name = entry.getKey(); 1070 Object value = entry.getValue(); 1071 if (value instanceof Node) { 1072 // Assign node name to its user data 1073 ((Node)value).setUserData(name); 1074 } 1075 if (value instanceof Shape3D 1076 && name.startsWith(WINDOW_PANE_SHAPE_PREFIX)) { 1077 Shape3D shape = (Shape3D)value; 1078 Appearance appearance = shape.getAppearance(); 1079 if (appearance == null) { 1080 appearance = new Appearance(); 1081 shape.setAppearance(appearance); 1082 } 1083 if (appearance.getTransparencyAttributes() == null) { 1084 appearance.setTransparencyAttributes(WINDOW_PANE_TRANSPARENCY_ATTRIBUTES); 1085 } 1086 } 1087 } 1088 } 1089 1090 /** 1091 * Updates the hierarchy of nodes with intermediate pickable nodes to help deforming models. 1092 */ updateDeformableModelHierarchy(Group group)1093 private void updateDeformableModelHierarchy(Group group) { 1094 // Try to reorganize node hierarchy of mannequin model 1095 if (containsNode(group, MANNEQUIN_ABDOMEN_PREFIX) 1096 && containsNode(group, MANNEQUIN_CHEST_PREFIX) 1097 && containsNode(group, MANNEQUIN_PELVIS_PREFIX) 1098 && containsNode(group, MANNEQUIN_NECK_PREFIX) 1099 && containsNode(group, MANNEQUIN_HEAD_PREFIX) 1100 && containsNode(group, MANNEQUIN_LEFT_SHOULDER_PREFIX) 1101 && containsNode(group, MANNEQUIN_LEFT_ARM_PREFIX) 1102 && containsNode(group, MANNEQUIN_LEFT_ELBOW_PREFIX) 1103 && containsNode(group, MANNEQUIN_LEFT_FOREARM_PREFIX) 1104 && containsNode(group, MANNEQUIN_LEFT_WRIST_PREFIX) 1105 && containsNode(group, MANNEQUIN_LEFT_HAND_PREFIX) 1106 && containsNode(group, MANNEQUIN_LEFT_HIP_PREFIX) 1107 && containsNode(group, MANNEQUIN_LEFT_THIGH_PREFIX) 1108 && containsNode(group, MANNEQUIN_LEFT_KNEE_PREFIX) 1109 && containsNode(group, MANNEQUIN_LEFT_LEG_PREFIX) 1110 && containsNode(group, MANNEQUIN_LEFT_ANKLE_PREFIX) 1111 && containsNode(group, MANNEQUIN_LEFT_FOOT_PREFIX) 1112 && containsNode(group, MANNEQUIN_RIGHT_SHOULDER_PREFIX) 1113 && containsNode(group, MANNEQUIN_RIGHT_ARM_PREFIX) 1114 && containsNode(group, MANNEQUIN_RIGHT_ELBOW_PREFIX) 1115 && containsNode(group, MANNEQUIN_RIGHT_FOREARM_PREFIX) 1116 && containsNode(group, MANNEQUIN_RIGHT_WRIST_PREFIX) 1117 && containsNode(group, MANNEQUIN_RIGHT_HAND_PREFIX) 1118 && containsNode(group, MANNEQUIN_RIGHT_HIP_PREFIX) 1119 && containsNode(group, MANNEQUIN_RIGHT_THIGH_PREFIX) 1120 && containsNode(group, MANNEQUIN_RIGHT_KNEE_PREFIX) 1121 && containsNode(group, MANNEQUIN_RIGHT_LEG_PREFIX) 1122 && containsNode(group, MANNEQUIN_RIGHT_ANKLE_PREFIX) 1123 && containsNode(group, MANNEQUIN_RIGHT_FOOT_PREFIX)) { 1124 // Head 1125 Node head = extractNodes(group, MANNEQUIN_HEAD_PREFIX, null); 1126 TransformGroup headGroup = createPickableTransformGroup(MANNEQUIN_NECK_PREFIX, head); 1127 1128 // Left arm 1129 Node leftHand = extractNodes(group, MANNEQUIN_LEFT_HAND_PREFIX, null); 1130 TransformGroup leftHandGroup = createPickableTransformGroup(MANNEQUIN_LEFT_WRIST_PREFIX, leftHand); 1131 Node leftForearm = extractNodes(group, MANNEQUIN_LEFT_FOREARM_PREFIX, null); 1132 Node leftWrist = extractNodes(group, MANNEQUIN_LEFT_WRIST_PREFIX, null); 1133 TransformGroup leftForearmGroup = createPickableTransformGroup(MANNEQUIN_LEFT_ELBOW_PREFIX, leftForearm, leftWrist, leftHandGroup); 1134 Node leftArm = extractNodes(group, MANNEQUIN_LEFT_ARM_PREFIX, null); 1135 Node leftElbow = extractNodes(group, MANNEQUIN_LEFT_ELBOW_PREFIX, null); 1136 TransformGroup leftArmGroup = createPickableTransformGroup(MANNEQUIN_LEFT_SHOULDER_PREFIX, leftArm, leftElbow, leftForearmGroup); 1137 1138 // Right arm 1139 Node rightHand = extractNodes(group, MANNEQUIN_RIGHT_HAND_PREFIX, null); 1140 TransformGroup rightHandGroup = createPickableTransformGroup(MANNEQUIN_RIGHT_WRIST_PREFIX, rightHand); 1141 Node rightForearm = extractNodes(group, MANNEQUIN_RIGHT_FOREARM_PREFIX, null); 1142 Node rightWrist = extractNodes(group, MANNEQUIN_RIGHT_WRIST_PREFIX, null); 1143 TransformGroup rightForearmGroup = createPickableTransformGroup(MANNEQUIN_RIGHT_ELBOW_PREFIX, rightForearm, rightWrist, rightHandGroup); 1144 Node rightArm = extractNodes(group, MANNEQUIN_RIGHT_ARM_PREFIX, null); 1145 Node rightElbow = extractNodes(group, MANNEQUIN_RIGHT_ELBOW_PREFIX, null); 1146 TransformGroup rightArmGroup = createPickableTransformGroup(MANNEQUIN_RIGHT_SHOULDER_PREFIX, rightArm, rightElbow, rightForearmGroup); 1147 1148 // Chest 1149 Node chest = extractNodes(group, MANNEQUIN_CHEST_PREFIX, null); 1150 Node leftShoulder = extractNodes(group, MANNEQUIN_LEFT_SHOULDER_PREFIX, null); 1151 Node rightShoulder = extractNodes(group, MANNEQUIN_RIGHT_SHOULDER_PREFIX, null); 1152 Node neck = extractNodes(group, MANNEQUIN_NECK_PREFIX, null); 1153 TransformGroup chestGroup = createPickableTransformGroup(MANNEQUIN_ABDOMEN_CHEST_PREFIX, chest, leftShoulder, leftArmGroup, rightShoulder, rightArmGroup, neck, headGroup); 1154 1155 // Left leg 1156 Node leftFoot = extractNodes(group, MANNEQUIN_LEFT_FOOT_PREFIX, null); 1157 TransformGroup leftFootGroup = createPickableTransformGroup(MANNEQUIN_LEFT_ANKLE_PREFIX, leftFoot); 1158 Node leftLeg = extractNodes(group, MANNEQUIN_LEFT_LEG_PREFIX, null); 1159 Node leftAnkle = extractNodes(group, MANNEQUIN_LEFT_ANKLE_PREFIX, null); 1160 TransformGroup leftLegGroup = createPickableTransformGroup(MANNEQUIN_LEFT_KNEE_PREFIX, leftLeg, leftAnkle, leftFootGroup); 1161 Node leftThigh = extractNodes(group, MANNEQUIN_LEFT_THIGH_PREFIX, null); 1162 Node leftKnee = extractNodes(group, MANNEQUIN_LEFT_KNEE_PREFIX, null); 1163 TransformGroup leftThighGroup = createPickableTransformGroup(MANNEQUIN_LEFT_HIP_PREFIX, leftThigh, leftKnee, leftLegGroup); 1164 1165 // Right leg 1166 Node rightFoot = extractNodes(group, MANNEQUIN_RIGHT_FOOT_PREFIX, null); 1167 TransformGroup rightFootGroup = createPickableTransformGroup(MANNEQUIN_RIGHT_ANKLE_PREFIX, rightFoot); 1168 Node rightLeg = extractNodes(group, MANNEQUIN_RIGHT_LEG_PREFIX, null); 1169 Node rightAnkle = extractNodes(group, MANNEQUIN_RIGHT_ANKLE_PREFIX, null); 1170 TransformGroup rightLegGroup = createPickableTransformGroup(MANNEQUIN_RIGHT_KNEE_PREFIX, rightLeg, rightAnkle, rightFootGroup); 1171 Node rightThigh = extractNodes(group, MANNEQUIN_RIGHT_THIGH_PREFIX, null); 1172 Node rightKnee = extractNodes(group, MANNEQUIN_RIGHT_KNEE_PREFIX, null); 1173 TransformGroup rightThighGroup = createPickableTransformGroup(MANNEQUIN_RIGHT_HIP_PREFIX, rightThigh, rightKnee, rightLegGroup); 1174 1175 // Pelvis 1176 Node pelvis = extractNodes(group, MANNEQUIN_PELVIS_PREFIX, null); 1177 Node leftHip = extractNodes(group, MANNEQUIN_LEFT_HIP_PREFIX, null); 1178 Node rightHip = extractNodes(group, MANNEQUIN_RIGHT_HIP_PREFIX, null); 1179 TransformGroup pelvisGroup = createPickableTransformGroup(MANNEQUIN_ABDOMEN_PELVIS_PREFIX, pelvis, leftHip, leftThighGroup, rightHip, rightThighGroup); 1180 1181 Node abdomen = extractNodes(group, MANNEQUIN_ABDOMEN_PREFIX, null); 1182 group.addChild(abdomen); 1183 group.addChild(chestGroup); 1184 group.addChild(pelvisGroup); 1185 } else { 1186 // Reorganize rotating openings 1187 updateSimpleDeformableModelHierarchy(group, null, HINGE_PREFIX, OPENING_ON_HINGE_PREFIX, WINDOW_PANE_ON_HINGE_PREFIX, MIRROR_ON_HINGE_PREFIX); 1188 updateSimpleDeformableModelHierarchy(group, null, BALL_PREFIX, ARM_ON_BALL_PREFIX, null, null); 1189 // Reorganize sliding openings 1190 updateSimpleDeformableModelHierarchy(group, UNIQUE_RAIL_PREFIX, RAIL_PREFIX, OPENING_ON_RAIL_PREFIX, WINDOW_PANE_ON_RAIL_PREFIX, MIRROR_ON_RAIL_PREFIX); 1191 } 1192 } 1193 updateSimpleDeformableModelHierarchy(Group group, String uniqueReferenceNodePrefix, String referenceNodePrefix, String openingPrefix, String openingPanePrefix, String openingMirrorPrefix)1194 private void updateSimpleDeformableModelHierarchy(Group group, String uniqueReferenceNodePrefix, String referenceNodePrefix, 1195 String openingPrefix, String openingPanePrefix, String openingMirrorPrefix) { 1196 if (containsNode(group, openingPrefix + 1) 1197 || (openingPanePrefix != null && containsNode(group, openingPanePrefix + 1)) 1198 || (openingMirrorPrefix != null && containsNode(group, openingMirrorPrefix + 1))) { 1199 if (containsNode(group, referenceNodePrefix + 1)) { 1200 // Reorganize openings with multiple reference nodes 1201 int i = 1; 1202 do { 1203 Node referenceNode = extractNodes(group, referenceNodePrefix + i, null); 1204 Node opening = extractNodes(group, openingPrefix + i, null); 1205 Node openingPane = openingPanePrefix != null ? extractNodes(group, openingPanePrefix + i, null) : null; 1206 Node openingMirror = openingMirrorPrefix != null ? extractNodes(group, openingMirrorPrefix + i, null) : null; 1207 TransformGroup openingGroup = createPickableTransformGroup(referenceNodePrefix + i, opening, openingPane, openingMirror); 1208 group.addChild(referenceNode); 1209 group.addChild(openingGroup); 1210 i++; 1211 } while (containsNode(group, referenceNodePrefix + i) 1212 && (containsNode(group, openingPrefix + i) 1213 || (openingPanePrefix != null && containsNode(group, openingPanePrefix + i)) 1214 || (openingMirrorPrefix != null && containsNode(group, openingMirrorPrefix + i)))); 1215 } else if (uniqueReferenceNodePrefix != null 1216 && containsNode(group, uniqueReferenceNodePrefix)) { 1217 // Reorganize openings with a unique reference node 1218 Node referenceNode = extractNodes(group, uniqueReferenceNodePrefix, null); 1219 group.addChild(referenceNode); 1220 int i = 1; 1221 do { 1222 Node opening = extractNodes(group, openingPrefix + i, null); 1223 Node openingPane = extractNodes(group, openingPanePrefix + i, null); 1224 Node openingMirror = extractNodes(group, openingMirrorPrefix + i, null); 1225 group.addChild(createPickableTransformGroup(referenceNodePrefix + i, opening, openingPane, openingMirror)); 1226 i++; 1227 } while (containsNode(group, openingPrefix + i) 1228 || containsNode(group, openingPanePrefix + i) 1229 || containsNode(group, openingMirrorPrefix + i)); 1230 } 1231 } 1232 } 1233 1234 /** 1235 * Returns <code>true</code> if the given <code>node</code> or a node in its hierarchy 1236 * contains a node which name, stored in user data, starts with <code>prefix</code>. 1237 */ containsNode(Node node, String prefix)1238 public boolean containsNode(Node node, String prefix) { 1239 Object userData = node.getUserData(); 1240 if (userData instanceof String 1241 && ((String)userData).startsWith(prefix)) { 1242 return true; 1243 } 1244 if (node instanceof Group) { 1245 Group group = (Group)node; 1246 for (int i = group.numChildren() - 1; i >= 0; i--) { 1247 if (containsNode((Node)group.getChild(i), prefix)) { 1248 return true; 1249 } 1250 } 1251 } 1252 return false; 1253 } 1254 1255 /** 1256 * Searches among the given <code>node</code> and its children the nodes which name, stored in user data, starts with <code>name</code>, 1257 * then returns a group containing the found nodes. 1258 */ extractNodes(Node node, String name, Group destinationGroup)1259 private Group extractNodes(Node node, String name, Group destinationGroup) { 1260 if (node.getUserData() != null 1261 && ((String)node.getUserData()).startsWith(name)) { 1262 ((Group)node.getParent()).removeChild(node); 1263 if (destinationGroup == null) { 1264 destinationGroup = new Group(); 1265 } 1266 destinationGroup.addChild(node); 1267 } 1268 if (node instanceof Group) { 1269 // Enumerate children 1270 Group group = (Group)node; 1271 for (int i = group.numChildren() - 1; i >= 0; i--) { 1272 destinationGroup = extractNodes((Node)group.getChild(i), name, destinationGroup); 1273 } 1274 } 1275 return destinationGroup; 1276 } 1277 1278 /** 1279 * Returns a pickable group with its <code>children</code> and the given reference node as user data. 1280 */ createPickableTransformGroup(String deformableGroupPrefix, Node ... children)1281 private TransformGroup createPickableTransformGroup(String deformableGroupPrefix, Node ... children) { 1282 TransformGroup transformGroup = new TransformGroup(); 1283 transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); 1284 transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); 1285 transformGroup.setCapability(TransformGroup.ENABLE_PICK_REPORTING); 1286 transformGroup.setCapability(TransformGroup.ALLOW_PARENT_READ); 1287 transformGroup.setUserData(deformableGroupPrefix + DEFORMABLE_TRANSFORM_GROUP_SUFFIX); 1288 // Store the node around which objects should turn 1289 for (Node child : children) { 1290 if (child != null) { 1291 transformGroup.addChild(child); 1292 } 1293 } 1294 return transformGroup; 1295 } 1296 1297 /** 1298 * Return <code>true</code> if the given <code>node</code> or its children contains at least a deformable group. 1299 * @param node the root of a model 1300 */ containsDeformableNode(Node node)1301 public boolean containsDeformableNode(Node node) { 1302 if (node instanceof TransformGroup 1303 && node.getUserData() instanceof String 1304 && ((String)node.getUserData()).endsWith(DEFORMABLE_TRANSFORM_GROUP_SUFFIX)) { 1305 return true; 1306 } else if (node instanceof Group) { 1307 Enumeration<?> enumeration = ((Group)node).getAllChildren(); 1308 while (enumeration.hasMoreElements()) { 1309 if (containsDeformableNode((Node)enumeration.nextElement())) { 1310 return true; 1311 } 1312 } 1313 } 1314 return false; 1315 } 1316 1317 /** 1318 * Return <code>true</code> if the given <code>node</code> or its children contains is a deformed transformed group. 1319 * @param node a node 1320 */ isDeformed(Node node)1321 private boolean isDeformed(Node node) { 1322 if (node instanceof TransformGroup 1323 && node.getUserData() instanceof String 1324 && ((String)node.getUserData()).endsWith(DEFORMABLE_TRANSFORM_GROUP_SUFFIX)) { 1325 Transform3D transform = new Transform3D(); 1326 ((TransformGroup)node).getTransform(transform); 1327 return (transform.getBestType() & Transform3D.IDENTITY) != Transform3D.IDENTITY; 1328 } else if (node instanceof Group) { 1329 Enumeration<?> enumeration = ((Group)node).getAllChildren(); 1330 while (enumeration.hasMoreElements()) { 1331 if (isDeformed((Node)enumeration.nextElement())) { 1332 return true; 1333 } 1334 } 1335 } 1336 return false; 1337 } 1338 1339 /** 1340 * Turns off light nodes of <code>node</code> children, 1341 * modulates textures if needed and allows shapes to change their pickable property. 1342 */ turnOffLightsShareAndModulateTextures(Node node, Map<Texture, Texture> replacedTextures)1343 private void turnOffLightsShareAndModulateTextures(Node node, 1344 Map<Texture, Texture> replacedTextures) { 1345 if (node instanceof Group) { 1346 // Enumerate children 1347 Enumeration<?> enumeration = ((Group)node).getAllChildren(); 1348 while (enumeration.hasMoreElements()) { 1349 turnOffLightsShareAndModulateTextures((Node)enumeration.nextElement(), replacedTextures); 1350 } 1351 } else if (node instanceof Link) { 1352 turnOffLightsShareAndModulateTextures(((Link)node).getSharedGroup(), replacedTextures); 1353 } else if (node instanceof Light) { 1354 ((Light)node).setEnable(false); 1355 } else if (node instanceof Shape3D) { 1356 node.setCapability(Node.ALLOW_PICKABLE_WRITE); 1357 Shape3D shape = ((Shape3D)node); 1358 for (Enumeration<Geometry> it = shape.getAllGeometries(); it.hasMoreElements(); ) { 1359 it.nextElement().setCapability(Geometry.ALLOW_INTERSECT); 1360 } 1361 Appearance appearance = shape.getAppearance(); 1362 if (appearance != null) { 1363 Texture texture = appearance.getTexture(); 1364 if (texture != null) { 1365 // Share textures data as much as possible requesting TextureManager#shareTexture the less often possible 1366 Texture sharedTexture = replacedTextures.get(texture); 1367 if (sharedTexture == null) { 1368 sharedTexture = TextureManager.getInstance().shareTexture(texture); 1369 replacedTextures.put(texture, sharedTexture); 1370 } 1371 if (sharedTexture != texture) { 1372 appearance.setTexture(sharedTexture); 1373 } 1374 TextureAttributes textureAttributes = appearance.getTextureAttributes(); 1375 if (textureAttributes == null) { 1376 // Mix texture and shape color 1377 textureAttributes = new TextureAttributes(); 1378 textureAttributes.setTextureMode(TextureAttributes.MODULATE); 1379 appearance.setTextureAttributes(textureAttributes); 1380 // Check shape color is white 1381 Material material = appearance.getMaterial(); 1382 if (material == null) { 1383 appearance.setMaterial((Material)DEFAULT_MATERIAL.cloneNodeComponent(true)); 1384 } else { 1385 Color3f color = new Color3f(); 1386 DEFAULT_MATERIAL.getDiffuseColor(color); 1387 material.setDiffuseColor(color); 1388 DEFAULT_MATERIAL.getAmbientColor(color); 1389 material.setAmbientColor(color); 1390 } 1391 } 1392 1393 // If texture image supports transparency 1394 if (TextureManager.getInstance().isTextureTransparent(sharedTexture)) { 1395 if (appearance.getTransparencyAttributes() == null) { 1396 // Add transparency attributes to ensure transparency works 1397 appearance.setTransparencyAttributes( 1398 new TransparencyAttributes(TransparencyAttributes.NICEST, 0)); 1399 } 1400 } 1401 } 1402 } 1403 } 1404 } 1405 1406 /** 1407 * Ensures that all the appearance of the children shapes of the 1408 * given <code>node</code> have a name. 1409 */ checkAppearancesName(Node node)1410 public void checkAppearancesName(Node node) { 1411 // Search appearances used by node shapes keeping their enumeration order 1412 Set<Appearance> appearances = new LinkedHashSet<Appearance>(); 1413 searchAppearances(node, appearances); 1414 int i = 0; 1415 for (Appearance appearance : appearances) { 1416 try { 1417 if (appearance.getName() == null) { 1418 appearance.setName("Texture_" + ++i); 1419 } 1420 } catch (NoSuchMethodError ex) { 1421 // Don't support HomeMaterial with Java 3D < 1.4 where appearance name was added 1422 break; 1423 } 1424 } 1425 } 1426 1427 /** 1428 * Returns the materials used by the children shapes of the given <code>node</code>. 1429 */ getMaterials(Node node)1430 public HomeMaterial [] getMaterials(Node node) { 1431 return getMaterials(node, null); 1432 } 1433 1434 /** 1435 * Returns the materials used by the children shapes of the given <code>node</code>, 1436 * attributing their <code>creator</code> to them. 1437 */ getMaterials(Node node, String creator)1438 public HomeMaterial [] getMaterials(Node node, String creator) { 1439 // Search appearances used by node shapes 1440 Set<Appearance> appearances = new HashSet<Appearance>(); 1441 searchAppearances(node, appearances); 1442 Set<HomeMaterial> materials = new TreeSet<HomeMaterial>(new Comparator<HomeMaterial>() { 1443 public int compare(HomeMaterial m1, HomeMaterial m2) { 1444 String name1 = m1.getName(); 1445 String name2 = m2.getName(); 1446 if (name1 != null) { 1447 if (name2 != null) { 1448 return name1.compareTo(name2); 1449 } else { 1450 return 1; 1451 } 1452 } else if (name2 != null) { 1453 return -1; 1454 } else { 1455 return 0; 1456 } 1457 } 1458 }); 1459 for (Appearance appearance : appearances) { 1460 Integer color = null; 1461 Float shininess = null; 1462 Material material = appearance.getMaterial(); 1463 if (material != null) { 1464 Color3f diffuseColor = new Color3f(); 1465 material.getDiffuseColor(diffuseColor); 1466 color = 0xFF000000 1467 | ((int)(diffuseColor.x * 255) << 16) 1468 | ((int)(diffuseColor.y * 255) << 8) 1469 | (int)(diffuseColor.z * 255); 1470 shininess = material.getShininess() / 128; 1471 } 1472 Texture appearanceTexture = appearance.getTexture(); 1473 HomeTexture texture = null; 1474 if (appearanceTexture != null) { 1475 URL textureImageUrl = (URL)appearanceTexture.getUserData(); 1476 if (textureImageUrl != null) { 1477 Content textureImage = new SimpleURLContent(textureImageUrl); 1478 // Extract image name 1479 String textureImageName = textureImageUrl.getFile(); 1480 textureImageName = textureImageName.substring(textureImageName.lastIndexOf('/') + 1); 1481 int lastPoint = textureImageName.lastIndexOf('.'); 1482 if (lastPoint != -1) { 1483 textureImageName = textureImageName.substring(0, lastPoint); 1484 } 1485 texture = new HomeTexture(new CatalogTexture(null, textureImageName, textureImage, -1, -1, creator)); 1486 } 1487 } 1488 try { 1489 materials.add(new HomeMaterial(appearance.getName(), color, texture, shininess)); 1490 } catch (NoSuchMethodError ex) { 1491 // Don't support HomeMaterial with Java 3D < 1.4 where getName was added 1492 return new HomeMaterial [0]; 1493 } 1494 } 1495 return materials.toArray(new HomeMaterial [materials.size()]); 1496 } 1497 searchAppearances(Node node, Set<Appearance> appearances)1498 private void searchAppearances(Node node, Set<Appearance> appearances) { 1499 if (node instanceof Group) { 1500 // Enumerate children 1501 Enumeration<?> enumeration = ((Group)node).getAllChildren(); 1502 while (enumeration.hasMoreElements()) { 1503 searchAppearances((Node)enumeration.nextElement(), appearances); 1504 } 1505 } else if (node instanceof Link) { 1506 searchAppearances(((Link)node).getSharedGroup(), appearances); 1507 } else if (node instanceof Shape3D) { 1508 Appearance appearance = ((Shape3D)node).getAppearance(); 1509 if (appearance != null) { 1510 appearances.add(appearance); 1511 } 1512 } 1513 } 1514 1515 /** 1516 * Replaces multiple shared shapes of the given <code>node</code> with one shape with transformed geometries. 1517 */ replaceMultipleSharedShapes(BranchGroup modelRoot)1518 private void replaceMultipleSharedShapes(BranchGroup modelRoot) { 1519 Map<Shape3D, Integer> sharedShapes = new HashMap<Shape3D, Integer>(); 1520 searchSharedShapes(modelRoot, sharedShapes, false); 1521 for (Iterator<Map.Entry<Shape3D, Integer>> iterator = sharedShapes.entrySet().iterator(); iterator.hasNext();) { 1522 Map.Entry<Shape3D, Integer> shapeSharingCount = (Map.Entry<Shape3D, Integer>)iterator.next(); 1523 if (shapeSharingCount.getValue() > 1) { 1524 List<Transform3D> transformations = new ArrayList<Transform3D>(shapeSharingCount.getValue()); 1525 Shape3D shape = shapeSharingCount.getKey(); 1526 searchShapeTransformations(modelRoot, shape, transformations, new Transform3D()); 1527 // Replace shared shape by a unique shape with transformed geometries 1528 Shape3D newShape = (Shape3D)shape.cloneNode(true); 1529 for (int i = 0; i < newShape.numGeometries(); i++) { 1530 Geometry newGeometry = getTransformedGeometry(newShape.getGeometry(i), transformations); 1531 if (newGeometry == null) { 1532 return; 1533 } 1534 newShape.setGeometry(newGeometry, i); 1535 } 1536 removeSharedShape(modelRoot, shape); 1537 modelRoot.addChild(newShape); 1538 } 1539 } 1540 } 1541 1542 /** 1543 * Searches all the shapes which are shared among the children of the given <code>node</code>. 1544 */ searchSharedShapes(Node node, Map<Shape3D, Integer> sharedShapes, boolean childOfSharedGroup)1545 private void searchSharedShapes(Node node, Map<Shape3D, Integer> sharedShapes, boolean childOfSharedGroup) { 1546 if (node instanceof Group) { 1547 // Enumerate children 1548 Enumeration<?> enumeration = ((Group)node).getAllChildren(); 1549 while (enumeration.hasMoreElements()) { 1550 searchSharedShapes((Node)enumeration.nextElement(), sharedShapes, childOfSharedGroup); 1551 } 1552 } else if (node instanceof Link) { 1553 searchSharedShapes(((Link)node).getSharedGroup(), sharedShapes, true); 1554 } else if (node instanceof Shape3D) { 1555 if (childOfSharedGroup) { 1556 Integer sharingCount = sharedShapes.get(node); 1557 if (sharingCount == null) { 1558 sharedShapes.put((Shape3D)node, 1); 1559 } else { 1560 sharedShapes.put((Shape3D)node, ++sharingCount); 1561 } 1562 } 1563 } 1564 } 1565 1566 /** 1567 * Searches all the transformations applied to a shared <code>shape</code> child of the given <b>node</b>. 1568 */ searchShapeTransformations(Node node, Shape3D shape, List<Transform3D> transformations, Transform3D parentTransformations)1569 private void searchShapeTransformations(Node node, Shape3D shape, List<Transform3D> transformations, Transform3D parentTransformations) { 1570 if (node instanceof Group) { 1571 if (!(node instanceof TransformGroup) 1572 || !isDeformed(node)) { 1573 if (node instanceof TransformGroup) { 1574 parentTransformations = new Transform3D(parentTransformations); 1575 Transform3D transform = new Transform3D(); 1576 ((TransformGroup)node).getTransform(transform); 1577 parentTransformations.mul(transform); 1578 } 1579 // Enumerate children 1580 Enumeration<?> enumeration = ((Group)node).getAllChildren(); 1581 while (enumeration.hasMoreElements()) { 1582 searchShapeTransformations((Node)enumeration.nextElement(), shape, transformations, parentTransformations); 1583 } 1584 } 1585 } else if (node instanceof Link) { 1586 searchShapeTransformations(((Link)node).getSharedGroup(), shape, transformations, parentTransformations); 1587 } else if (node == shape) { 1588 transformations.add(parentTransformations); 1589 } 1590 } 1591 1592 /** 1593 * Returns a new geometry where coordinates are transformed with the given transformations. 1594 */ getTransformedGeometry(Geometry geometry, List<Transform3D> transformations)1595 private Geometry getTransformedGeometry(Geometry geometry, 1596 List<Transform3D> transformations) { 1597 if (geometry instanceof GeometryArray) { 1598 GeometryArray geometryArray = (GeometryArray)geometry; 1599 GeometryArray newGeometryArray = null; 1600 boolean normalsDefined = (geometryArray.getVertexFormat() & GeometryArray.NORMALS) != 0; 1601 boolean textureCoordinatesDefined = (geometryArray.getVertexFormat() & GeometryArray.TEXTURE_COORDINATE_2) != 0; 1602 1603 if (geometryArray instanceof IndexedGeometryArray) { 1604 IndexedGeometryArray indexedGeometryArray = (IndexedGeometryArray)geometryArray; 1605 IndexedGeometryArray newIndexedGeometryArray = null; 1606 if (geometryArray instanceof IndexedLineArray) { 1607 newIndexedGeometryArray = new IndexedLineArray(indexedGeometryArray.getVertexCount() * transformations.size(), indexedGeometryArray.getVertexFormat(), indexedGeometryArray.getIndexCount() * transformations.size()); 1608 } else if (geometryArray instanceof IndexedTriangleArray) { 1609 newIndexedGeometryArray = new IndexedTriangleArray(indexedGeometryArray.getVertexCount() * transformations.size(), indexedGeometryArray.getVertexFormat(), indexedGeometryArray.getIndexCount() * transformations.size()); 1610 } else if (geometryArray instanceof IndexedQuadArray) { 1611 newIndexedGeometryArray = new IndexedQuadArray(indexedGeometryArray.getVertexCount() * transformations.size(), indexedGeometryArray.getVertexFormat(), indexedGeometryArray.getIndexCount() * transformations.size()); 1612 } else if (geometryArray instanceof IndexedGeometryStripArray) { 1613 IndexedGeometryStripArray geometryStripArray = (IndexedGeometryStripArray)geometryArray; 1614 int [] stripIndexCounts = new int [geometryStripArray.getNumStrips()]; 1615 geometryStripArray.getStripIndexCounts(stripIndexCounts); 1616 int [] newStripIndexCounts = new int [stripIndexCounts.length * transformations.size()]; 1617 int offset = 0; 1618 for (int i = 0; i < transformations.size(); i++) { 1619 for (int j = 0, n = stripIndexCounts.length; j < n; j++) { 1620 newStripIndexCounts [offset + j] = stripIndexCounts [j]; 1621 } 1622 offset += stripIndexCounts.length; 1623 } 1624 if (geometryStripArray instanceof IndexedLineStripArray) { 1625 newIndexedGeometryArray = new IndexedLineStripArray(indexedGeometryArray.getVertexCount() * transformations.size(), indexedGeometryArray.getVertexFormat(), indexedGeometryArray.getIndexCount() * transformations.size(), newStripIndexCounts); 1626 } else if (geometryStripArray instanceof IndexedTriangleStripArray) { 1627 newIndexedGeometryArray = new IndexedTriangleStripArray(indexedGeometryArray.getVertexCount() * transformations.size(), indexedGeometryArray.getVertexFormat(), indexedGeometryArray.getIndexCount() * transformations.size(), newStripIndexCounts); 1628 } else if (geometryStripArray instanceof IndexedTriangleFanArray) { 1629 newIndexedGeometryArray = new IndexedTriangleFanArray(indexedGeometryArray.getVertexCount() * transformations.size(), indexedGeometryArray.getVertexFormat(), indexedGeometryArray.getIndexCount(), newStripIndexCounts); 1630 } 1631 } 1632 int offsetIndex = 0; 1633 int offsetVertex = 0; 1634 if (newIndexedGeometryArray != null) { 1635 for (int i = 0; i < transformations.size(); i++) { 1636 for (int j = 0, n = indexedGeometryArray.getIndexCount(); j < n; j++) { 1637 newIndexedGeometryArray.setCoordinateIndex(offsetIndex + j, offsetVertex + indexedGeometryArray.getCoordinateIndex(j)); 1638 if (normalsDefined) { 1639 newIndexedGeometryArray.setNormalIndex(offsetIndex + j, offsetVertex + indexedGeometryArray.getNormalIndex(j)); 1640 } 1641 if (textureCoordinatesDefined) { 1642 newIndexedGeometryArray.setTextureCoordinateIndex(0, offsetIndex + j, offsetVertex + indexedGeometryArray.getTextureCoordinateIndex(0, j)); 1643 } 1644 } 1645 offsetIndex += indexedGeometryArray.getIndexCount(); 1646 offsetVertex += indexedGeometryArray.getVertexCount(); 1647 } 1648 newGeometryArray = newIndexedGeometryArray; 1649 } 1650 } else { 1651 if (geometryArray instanceof LineArray) { 1652 newGeometryArray = new LineArray(geometryArray.getVertexCount() * transformations.size(), geometryArray.getVertexFormat()); 1653 } else if (geometryArray instanceof TriangleArray) { 1654 newGeometryArray = new TriangleArray(geometryArray.getVertexCount() * transformations.size(), geometryArray.getVertexFormat()); 1655 } else if (geometryArray instanceof QuadArray) { 1656 newGeometryArray = new QuadArray(geometryArray.getVertexCount() * transformations.size(), geometryArray.getVertexFormat()); 1657 } else if (geometryArray instanceof GeometryStripArray) { 1658 GeometryStripArray geometryStripArray = (GeometryStripArray)geometryArray; 1659 int [] stripVertexCounts = new int [geometryStripArray.getNumStrips()]; 1660 geometryStripArray.getStripVertexCounts(stripVertexCounts); 1661 int [] newStripVertexCounts = new int [stripVertexCounts.length * transformations.size()]; 1662 int offset = 0; 1663 for (int i = 0; i < transformations.size(); i++) { 1664 for (int j = 0, n = stripVertexCounts.length; j < n; j++) { 1665 newStripVertexCounts [offset + j] = stripVertexCounts [j]; 1666 } 1667 offset += stripVertexCounts.length; 1668 } 1669 if (geometryStripArray instanceof LineStripArray) { 1670 newGeometryArray = new LineStripArray(geometryArray.getVertexCount() * transformations.size(), geometryArray.getVertexFormat(), newStripVertexCounts); 1671 } else if (geometryStripArray instanceof TriangleStripArray) { 1672 newGeometryArray = new TriangleStripArray(geometryArray.getVertexCount() * transformations.size(), geometryArray.getVertexFormat(), newStripVertexCounts); 1673 } else if (geometryStripArray instanceof TriangleFanArray) { 1674 newGeometryArray = new TriangleFanArray(geometryArray.getVertexCount() * transformations.size(), geometryArray.getVertexFormat(), newStripVertexCounts); 1675 } 1676 } 1677 } 1678 1679 if (newGeometryArray != null) { 1680 if ((geometryArray.getVertexFormat() & GeometryArray.BY_REFERENCE) != 0) { 1681 Point3f vertex = new Point3f(); 1682 Vector3f normal = new Vector3f(); 1683 if ((geometryArray.getVertexFormat() & GeometryArray.INTERLEAVED) != 0) { 1684 float [] vertexData = geometryArray.getInterleavedVertices(); 1685 int vertexSize = vertexData.length / geometryArray.getVertexCount(); 1686 float [] newVertexData = new float [vertexData.length * transformations.size()]; 1687 int offset = 0; 1688 for (Transform3D parentTransformations : transformations) { 1689 // Prepare vertices coordinates 1690 for (int index = 0, i = vertexSize - 3, n = geometryArray.getVertexCount(); index < n; index++, i += vertexSize) { 1691 vertex.x = vertexData [i]; 1692 vertex.y = vertexData [i + 1]; 1693 vertex.z = vertexData [i + 2]; 1694 parentTransformations.transform(vertex); 1695 newVertexData [offset + i] = vertex.x; 1696 newVertexData [offset + i + 1] = vertex.y; 1697 newVertexData [offset + i + 2] = vertex.z; 1698 } 1699 // Prepare texture coordinates 1700 if (textureCoordinatesDefined) { 1701 for (int index = 0, i = 0, n = geometryArray.getVertexCount(); index < n; index++, i += vertexSize) { 1702 newVertexData [offset + i] = vertexData [i]; 1703 newVertexData [offset + i + 1] = vertexData [i + 1]; 1704 } 1705 } 1706 // Prepare normals 1707 if (normalsDefined) { 1708 for (int index = 0, i = vertexSize - 6, n = geometryArray.getVertexCount(); index < n; index++, i += vertexSize) { 1709 normal.x = vertexData [i]; 1710 normal.y = vertexData [i + 1]; 1711 normal.z = vertexData [i + 2]; 1712 parentTransformations.transform(normal); 1713 normal.normalize(); 1714 newVertexData [offset + i] = normal.x; 1715 newVertexData [offset + i + 1] = normal.y; 1716 newVertexData [offset + i + 2] = normal.z; 1717 } 1718 } 1719 offset += vertexData.length; 1720 } 1721 newGeometryArray.setInterleavedVertices(newVertexData); 1722 } else { 1723 // Prepare vertices coordinates 1724 float [] vertexCoordinates = geometryArray.getCoordRefFloat(); 1725 float [] newVertexCoordinates = new float [vertexCoordinates.length * transformations.size()]; 1726 int offset = 0; 1727 for (Transform3D parentTransformations : transformations) { 1728 for (int index = 0, i = 0, n = geometryArray.getVertexCount(); index < n; index++, i += 3) { 1729 vertex.x = vertexCoordinates [i]; 1730 vertex.y = vertexCoordinates [i + 1]; 1731 vertex.z = vertexCoordinates [i + 2]; 1732 parentTransformations.transform(vertex); 1733 newVertexCoordinates [offset + i] = vertex.x; 1734 newVertexCoordinates [offset + i + 1] = vertex.y; 1735 newVertexCoordinates [offset + i + 2] = vertex.z; 1736 } 1737 offset += vertexCoordinates.length; 1738 } 1739 newGeometryArray.setCoordRefFloat(newVertexCoordinates); 1740 if (textureCoordinatesDefined) { 1741 // Prepare texture coordinates 1742 float [] textureCoordinatesArray = geometryArray.getTexCoordRefFloat(0); 1743 float [] newTextureCoordinatesArray = new float [textureCoordinatesArray.length * transformations.size()]; 1744 offset = 0; 1745 for (int i = 0; i < transformations.size(); i++) { 1746 for (int index = 0, j = 0, n = geometryArray.getVertexCount(); index < n; index++, j += 2) { 1747 newTextureCoordinatesArray [offset + j] = textureCoordinatesArray [j]; 1748 newTextureCoordinatesArray [offset + j + 1] = textureCoordinatesArray [j + 1]; 1749 } 1750 offset += vertexCoordinates.length; 1751 } 1752 newGeometryArray.setTexCoordRefFloat(0, newTextureCoordinatesArray); 1753 } 1754 if (normalsDefined) { 1755 // Prepare normals 1756 float [] normalCoordinates = geometryArray.getNormalRefFloat(); 1757 float [] newNormalCoordinates = new float [normalCoordinates.length * transformations.size()]; 1758 offset = 0; 1759 for (Transform3D parentTransformations : transformations) { 1760 for (int index = 0, i = 0, n = geometryArray.getVertexCount(); index < n; index++, i += 3) { 1761 normal.x = normalCoordinates [i]; 1762 normal.y = normalCoordinates [i + 1]; 1763 normal.z = normalCoordinates [i + 2]; 1764 parentTransformations.transform(normal); 1765 normal.normalize(); 1766 newNormalCoordinates [offset + i] = normal.x; 1767 newNormalCoordinates [offset + i + 1] = normal.y; 1768 newNormalCoordinates [offset + i + 2] = normal.z; 1769 } 1770 offset += vertexCoordinates.length; 1771 } 1772 newGeometryArray.setNormalRefFloat(newNormalCoordinates); 1773 } 1774 } 1775 } else { 1776 // Prepare vertices coordinates 1777 int offset = 0; 1778 for (Transform3D parentTransformations : transformations) { 1779 for (int index = 0, n = geometryArray.getVertexCount(); index < n; index++) { 1780 Point3f newVertex = new Point3f(); 1781 geometryArray.getCoordinate(index, newVertex); 1782 parentTransformations.transform(newVertex); 1783 newGeometryArray.setCoordinate(offset + index, newVertex); 1784 } 1785 offset += geometryArray.getVertexCount(); 1786 } 1787 if (textureCoordinatesDefined) { 1788 // Prepare texture coordinates 1789 offset = 0; 1790 for (int i = 0; i < transformations.size(); i++) { 1791 for (int index = 0, n = geometryArray.getVertexCount(); index < n; index++) { 1792 TexCoord2f newTextureCoordinates = new TexCoord2f(); 1793 geometryArray.getTextureCoordinate(0, index, newTextureCoordinates); 1794 newGeometryArray.setTextureCoordinate(0, offset + index, newTextureCoordinates); 1795 } 1796 offset += geometryArray.getVertexCount(); 1797 } 1798 } 1799 if (normalsDefined) { 1800 // Prepare normals 1801 offset = 0; 1802 for (Transform3D parentTransformations : transformations) { 1803 for (int index = 0, n = geometryArray.getVertexCount(); index < n; index++) { 1804 Vector3f newNormal = new Vector3f(); 1805 geometryArray.getNormal(index, newNormal); 1806 parentTransformations.transform(newNormal); 1807 newNormal.normalize(); 1808 newGeometryArray.setNormal(offset + index, newNormal); 1809 } 1810 offset += geometryArray.getVertexCount(); 1811 } 1812 } 1813 } 1814 } 1815 return newGeometryArray; 1816 } 1817 return null; 1818 } 1819 1820 /** 1821 * Removes the shared shape from the children of the given <code>node</code>. 1822 */ removeSharedShape(Node node, Shape3D shape)1823 private void removeSharedShape(Node node, Shape3D shape) { 1824 if (node instanceof Group) { 1825 if (!(node instanceof TransformGroup) 1826 || !isDeformed(node)) { 1827 Group group = (Group)node; 1828 for (int i = group.numChildren() - 1; i >= 0; i--) { 1829 removeSharedShape(group.getChild(i), shape); 1830 } 1831 if (group.numChildren() == 0 1832 && group.getParent() instanceof Group) { 1833 ((Group)group.getParent()).removeChild(group); 1834 } 1835 } 1836 } else if (node instanceof Link) { 1837 SharedGroup sharedGroup = ((Link)node).getSharedGroup(); 1838 removeSharedShape(sharedGroup, shape); 1839 if (sharedGroup.numChildren() == 0) { 1840 ((Group)node.getParent()).removeChild(node); 1841 } 1842 } else if (node == shape) { 1843 ((Group)node.getParent()).removeChild(node); 1844 } 1845 } 1846 1847 /** 1848 * Returns the AWT shape matching the given cut out shape if not <code>null</code> 1849 * or the 2D area of the 3D shapes children of the <code>node</code> 1850 * projected on its front side. The returned area is normalized in a 1 unit square 1851 * centered at the origin. 1852 */ getFrontArea(String cutOutShape, Node node)1853 Area getFrontArea(String cutOutShape, Node node) { 1854 Area frontArea; 1855 if (cutOutShape != null) { 1856 frontArea = new Area(getShape(cutOutShape)); 1857 frontArea.transform(AffineTransform.getScaleInstance(1, -1)); 1858 frontArea.transform(AffineTransform.getTranslateInstance(-.5, .5)); 1859 } else { 1860 int vertexCount = getVertexCount(node); 1861 if (vertexCount < 1000000) { 1862 Area frontAreaWithHoles = new Area(); 1863 computeBottomOrFrontArea(node, frontAreaWithHoles, new Transform3D(), false, false); 1864 // Remove holes and duplicated points 1865 frontArea = new Area(); 1866 List<float []> currentPathPoints = new ArrayList<float[]>(); 1867 float [] previousRoomPoint = null; 1868 for (PathIterator it = frontAreaWithHoles.getPathIterator(null, 1); !it.isDone(); it.next()) { 1869 float [] areaPoint = new float[2]; 1870 switch (it.currentSegment(areaPoint)) { 1871 case PathIterator.SEG_MOVETO : 1872 case PathIterator.SEG_LINETO : 1873 if (previousRoomPoint == null 1874 || areaPoint [0] != previousRoomPoint [0] 1875 || areaPoint [1] != previousRoomPoint [1]) { 1876 currentPathPoints.add(areaPoint); 1877 } 1878 previousRoomPoint = areaPoint; 1879 break; 1880 case PathIterator.SEG_CLOSE : 1881 if (currentPathPoints.get(0) [0] == previousRoomPoint [0] 1882 && currentPathPoints.get(0) [1] == previousRoomPoint [1]) { 1883 currentPathPoints.remove(currentPathPoints.size() - 1); 1884 } 1885 if (currentPathPoints.size() > 2) { 1886 float [][] pathPoints = 1887 currentPathPoints.toArray(new float [currentPathPoints.size()][]); 1888 Room subRoom = new Room(pathPoints); 1889 if (subRoom.getArea() > 0) { 1890 if (!subRoom.isClockwise()) { 1891 // Ignore clockwise points that match holes 1892 GeneralPath currentPath = new GeneralPath(); 1893 currentPath.moveTo(pathPoints [0][0], pathPoints [0][1]); 1894 for (int i = 1; i < pathPoints.length; i++) { 1895 currentPath.lineTo(pathPoints [i][0], pathPoints [i][1]); 1896 } 1897 currentPath.closePath(); 1898 frontArea.add(new Area(currentPath)); 1899 } 1900 } 1901 } 1902 currentPathPoints.clear(); 1903 previousRoomPoint = null; 1904 break; 1905 } 1906 } 1907 Rectangle2D bounds = frontAreaWithHoles.getBounds2D(); 1908 frontArea.transform(AffineTransform.getTranslateInstance(-bounds.getCenterX(), -bounds.getCenterY())); 1909 frontArea.transform(AffineTransform.getScaleInstance(1 / bounds.getWidth(), 1 / bounds.getHeight())); 1910 } else { 1911 frontArea = new Area(new Rectangle2D.Float(-.5f, -.5f, 1, 1)); 1912 } 1913 } 1914 return frontArea; 1915 } 1916 1917 1918 /** 1919 * Returns the 2D area of the 3D shapes children of the given <code>node</code> 1920 * projected on the floor (plan y = 0). 1921 */ getAreaOnFloor(Node node)1922 public Area getAreaOnFloor(Node node) { 1923 Area modelAreaOnFloor; 1924 int vertexCount = getVertexCount(node); 1925 if (vertexCount < 10000) { 1926 modelAreaOnFloor = new Area(); 1927 computeBottomOrFrontArea(node, modelAreaOnFloor, new Transform3D(), true, true); 1928 } else { 1929 List<float []> vertices = new ArrayList<float[]>(vertexCount); 1930 computeVerticesOnFloor(node, vertices, new Transform3D()); 1931 if (vertices.size() > 0) { 1932 float [][] surroundingPolygon = getSurroundingPolygon(vertices.toArray(new float [vertices.size()][])); 1933 GeneralPath generalPath = new GeneralPath(GeneralPath.WIND_NON_ZERO, surroundingPolygon.length); 1934 generalPath.moveTo(surroundingPolygon [0][0], surroundingPolygon [0][1]); 1935 for (int i = 0; i < surroundingPolygon.length; i++) { 1936 generalPath.lineTo(surroundingPolygon [i][0], surroundingPolygon [i][1]); 1937 } 1938 generalPath.closePath(); 1939 modelAreaOnFloor = new Area(generalPath); 1940 } else { 1941 modelAreaOnFloor = new Area(); 1942 } 1943 } 1944 return modelAreaOnFloor; 1945 } 1946 1947 /** 1948 * Returns the total count of vertices in all geometries. 1949 */ getVertexCount(Node node)1950 private int getVertexCount(Node node) { 1951 int count = 0; 1952 if (node instanceof Group) { 1953 // Enumerate all children 1954 Enumeration<?> enumeration = ((Group)node).getAllChildren(); 1955 while (enumeration.hasMoreElements()) { 1956 count += getVertexCount((Node)enumeration.nextElement()); 1957 } 1958 } else if (node instanceof Link) { 1959 count = getVertexCount(((Link)node).getSharedGroup()); 1960 } else if (node instanceof Shape3D) { 1961 Shape3D shape = (Shape3D)node; 1962 Appearance appearance = shape.getAppearance(); 1963 RenderingAttributes renderingAttributes = appearance != null 1964 ? appearance.getRenderingAttributes() : null; 1965 if (renderingAttributes == null 1966 || renderingAttributes.getVisible()) { 1967 for (int i = 0, n = shape.numGeometries(); i < n; i++) { 1968 Geometry geometry = shape.getGeometry(i); 1969 if (geometry instanceof GeometryArray) { 1970 count += ((GeometryArray)geometry).getVertexCount(); 1971 } 1972 } 1973 } 1974 } 1975 return count; 1976 } 1977 1978 /** 1979 * Computes the 2D area on floor or on front side of the 3D shapes children of <code>node</code>. 1980 */ computeBottomOrFrontArea(Node node, Area nodeArea, Transform3D parentTransformations, boolean ignoreTransparentShapes, boolean bottom)1981 private void computeBottomOrFrontArea(Node node, 1982 Area nodeArea, 1983 Transform3D parentTransformations, 1984 boolean ignoreTransparentShapes, 1985 boolean bottom) { 1986 if (node instanceof Group) { 1987 if (node instanceof TransformGroup) { 1988 parentTransformations = new Transform3D(parentTransformations); 1989 Transform3D transform = new Transform3D(); 1990 ((TransformGroup)node).getTransform(transform); 1991 parentTransformations.mul(transform); 1992 } 1993 // Compute all children 1994 Enumeration<?> enumeration = ((Group)node).getAllChildren(); 1995 while (enumeration.hasMoreElements()) { 1996 computeBottomOrFrontArea((Node)enumeration.nextElement(), nodeArea, parentTransformations, ignoreTransparentShapes, bottom); 1997 } 1998 } else if (node instanceof Link) { 1999 computeBottomOrFrontArea(((Link)node).getSharedGroup(), nodeArea, parentTransformations, ignoreTransparentShapes, bottom); 2000 } else if (node instanceof Shape3D) { 2001 Shape3D shape = (Shape3D)node; 2002 Appearance appearance = shape.getAppearance(); 2003 RenderingAttributes renderingAttributes = appearance != null 2004 ? appearance.getRenderingAttributes() : null; 2005 TransparencyAttributes transparencyAttributes = appearance != null 2006 ? appearance.getTransparencyAttributes() : null; 2007 if ((renderingAttributes == null 2008 || renderingAttributes.getVisible()) 2009 && (!ignoreTransparentShapes 2010 || transparencyAttributes == null 2011 || transparencyAttributes.getTransparency() < 1)) { 2012 // Compute shape geometries area 2013 for (int i = 0, n = shape.numGeometries(); i < n; i++) { 2014 computeBottomOrFrontGeometryArea(shape.getGeometry(i), nodeArea, parentTransformations, bottom); 2015 } 2016 } 2017 } 2018 } 2019 2020 /** 2021 * Computes the bottom area of a 3D geometry if <code>bottom</code> is <code>true</code>, 2022 * and the front area if not. 2023 */ computeBottomOrFrontGeometryArea(Geometry geometry, Area nodeArea, Transform3D parentTransformations, boolean bottom)2024 private void computeBottomOrFrontGeometryArea(Geometry geometry, 2025 Area nodeArea, 2026 Transform3D parentTransformations, 2027 boolean bottom) { 2028 if (geometry instanceof GeometryArray) { 2029 GeometryArray geometryArray = (GeometryArray)geometry; 2030 int vertexCount = geometryArray.getVertexCount(); 2031 float [] vertices = new float [vertexCount * 2]; 2032 Point3f vertex = new Point3f(); 2033 if ((geometryArray.getVertexFormat() & GeometryArray.BY_REFERENCE) != 0) { 2034 if ((geometryArray.getVertexFormat() & GeometryArray.INTERLEAVED) != 0) { 2035 float [] vertexData = geometryArray.getInterleavedVertices(); 2036 int vertexSize = vertexData.length / vertexCount; 2037 // Store vertices coordinates 2038 for (int index = 0, i = vertexSize - 3; index < vertices.length; i += vertexSize) { 2039 vertex.x = vertexData [i]; 2040 vertex.y = vertexData [i + 1]; 2041 vertex.z = vertexData [i + 2]; 2042 parentTransformations.transform(vertex); 2043 vertices [index++] = vertex.x; 2044 if (bottom) { 2045 vertices [index++] = vertex.z; 2046 } else { 2047 vertices [index++] = vertex.y; 2048 } 2049 } 2050 } else { 2051 // Store vertices coordinates 2052 float [] vertexCoordinates = geometryArray.getCoordRefFloat(); 2053 for (int index = 0, i = 0; index < vertices.length; i += 3) { 2054 vertex.x = vertexCoordinates [i]; 2055 vertex.y = vertexCoordinates [i + 1]; 2056 vertex.z = vertexCoordinates [i + 2]; 2057 parentTransformations.transform(vertex); 2058 vertices [index++] = vertex.x; 2059 if (bottom) { 2060 vertices [index++] = vertex.z; 2061 } else { 2062 vertices [index++] = vertex.y; 2063 } 2064 } 2065 } 2066 } else { 2067 // Store vertices coordinates 2068 for (int index = 0, i = 0; index < vertices.length; i++) { 2069 geometryArray.getCoordinate(i, vertex); 2070 parentTransformations.transform(vertex); 2071 vertices [index++] = vertex.x; 2072 if (bottom) { 2073 vertices [index++] = vertex.z; 2074 } else { 2075 vertices [index++] = vertex.y; 2076 } 2077 } 2078 } 2079 2080 // Create path from triangles or quadrilaterals of geometry 2081 GeneralPath geometryPath = null; 2082 if (geometryArray instanceof IndexedGeometryArray) { 2083 if (geometryArray instanceof IndexedTriangleArray) { 2084 IndexedTriangleArray triangleArray = (IndexedTriangleArray)geometryArray; 2085 geometryPath = new GeneralPath(GeneralPath.WIND_NON_ZERO, 1000); 2086 for (int i = 0, triangleIndex = 0, n = triangleArray.getIndexCount(); i < n; i += 3) { 2087 addIndexedTriangleToPath(triangleArray, i, i + 1, i + 2, vertices, 2088 geometryPath, triangleIndex++, nodeArea); 2089 } 2090 } else if (geometryArray instanceof IndexedQuadArray) { 2091 IndexedQuadArray quadArray = (IndexedQuadArray)geometryArray; 2092 geometryPath = new GeneralPath(GeneralPath.WIND_NON_ZERO, 1000); 2093 for (int i = 0, quadrilateralIndex = 0, n = quadArray.getIndexCount(); i < n; i += 4) { 2094 addIndexedQuadrilateralToPath(quadArray, i, i + 1, i + 2, i + 3, vertices, 2095 geometryPath, quadrilateralIndex++, nodeArea); 2096 } 2097 } else if (geometryArray instanceof IndexedGeometryStripArray) { 2098 IndexedGeometryStripArray geometryStripArray = (IndexedGeometryStripArray)geometryArray; 2099 int [] stripIndexCounts = new int [geometryStripArray.getNumStrips()]; 2100 geometryStripArray.getStripIndexCounts(stripIndexCounts); 2101 geometryPath = new GeneralPath(GeneralPath.WIND_NON_ZERO, 1000); 2102 int initialIndex = 0; 2103 2104 if (geometryStripArray instanceof IndexedTriangleStripArray) { 2105 for (int strip = 0, triangleIndex = 0; strip < stripIndexCounts.length; strip++) { 2106 for (int i = initialIndex, n = initialIndex + stripIndexCounts [strip] - 2, j = 0; i < n; i++, j++) { 2107 if (j % 2 == 0) { 2108 addIndexedTriangleToPath(geometryStripArray, i, i + 1, i + 2, vertices, 2109 geometryPath, triangleIndex++, nodeArea); 2110 } else { // Vertices of odd triangles are in reverse order 2111 addIndexedTriangleToPath(geometryStripArray, i, i + 2, i + 1, vertices, 2112 geometryPath, triangleIndex++, nodeArea); 2113 } 2114 } 2115 initialIndex += stripIndexCounts [strip]; 2116 } 2117 } else if (geometryStripArray instanceof IndexedTriangleFanArray) { 2118 for (int strip = 0, triangleIndex = 0; strip < stripIndexCounts.length; strip++) { 2119 for (int i = initialIndex, n = initialIndex + stripIndexCounts [strip] - 2; i < n; i++) { 2120 addIndexedTriangleToPath(geometryStripArray, initialIndex, i + 1, i + 2, vertices, 2121 geometryPath, triangleIndex++, nodeArea); 2122 } 2123 initialIndex += stripIndexCounts [strip]; 2124 } 2125 } 2126 } 2127 } else { 2128 if (geometryArray instanceof TriangleArray) { 2129 TriangleArray triangleArray = (TriangleArray)geometryArray; 2130 geometryPath = new GeneralPath(GeneralPath.WIND_NON_ZERO, 1000); 2131 for (int i = 0, triangleIndex = 0; i < vertexCount; i += 3) { 2132 addTriangleToPath(triangleArray, i, i + 1, i + 2, vertices, 2133 geometryPath, triangleIndex++, nodeArea); 2134 } 2135 } else if (geometryArray instanceof QuadArray) { 2136 QuadArray quadArray = (QuadArray)geometryArray; 2137 geometryPath = new GeneralPath(GeneralPath.WIND_NON_ZERO, 1000); 2138 for (int i = 0, quadrilateralIndex = 0; i < vertexCount; i += 4) { 2139 addQuadrilateralToPath(quadArray, i, i + 1, i + 2, i + 3, vertices, 2140 geometryPath, quadrilateralIndex++, nodeArea); 2141 } 2142 } else if (geometryArray instanceof GeometryStripArray) { 2143 GeometryStripArray geometryStripArray = (GeometryStripArray)geometryArray; 2144 int [] stripVertexCounts = new int [geometryStripArray.getNumStrips()]; 2145 geometryStripArray.getStripVertexCounts(stripVertexCounts); 2146 geometryPath = new GeneralPath(GeneralPath.WIND_NON_ZERO, 1000); 2147 int initialIndex = 0; 2148 2149 if (geometryStripArray instanceof TriangleStripArray) { 2150 for (int strip = 0, triangleIndex = 0; strip < stripVertexCounts.length; strip++) { 2151 for (int i = initialIndex, n = initialIndex + stripVertexCounts [strip] - 2, j = 0; i < n; i++, j++) { 2152 if (j % 2 == 0) { 2153 addTriangleToPath(geometryStripArray, i, i + 1, i + 2, vertices, 2154 geometryPath, triangleIndex++, nodeArea); 2155 } else { // Vertices of odd triangles are in reverse order 2156 addTriangleToPath(geometryStripArray, i, i + 2, i + 1, vertices, 2157 geometryPath, triangleIndex++, nodeArea); 2158 } 2159 } 2160 initialIndex += stripVertexCounts [strip]; 2161 } 2162 } else if (geometryStripArray instanceof TriangleFanArray) { 2163 for (int strip = 0, triangleIndex = 0; strip < stripVertexCounts.length; strip++) { 2164 for (int i = initialIndex, n = initialIndex + stripVertexCounts [strip] - 2; i < n; i++) { 2165 addTriangleToPath(geometryStripArray, initialIndex, i + 1, i + 2, vertices, 2166 geometryPath, triangleIndex++, nodeArea); 2167 } 2168 initialIndex += stripVertexCounts [strip]; 2169 } 2170 } 2171 } 2172 } 2173 2174 if (geometryPath != null) { 2175 nodeArea.add(new Area(geometryPath)); 2176 } 2177 } 2178 } 2179 2180 /** 2181 * Adds to <code>geometryPath</code> the triangle joining vertices at 2182 * vertexIndex1, vertexIndex2, vertexIndex3 indices. 2183 */ addIndexedTriangleToPath(IndexedGeometryArray geometryArray, int vertexIndex1, int vertexIndex2, int vertexIndex3, float [] vertices, GeneralPath geometryPath, int triangleIndex, Area nodeArea)2184 private void addIndexedTriangleToPath(IndexedGeometryArray geometryArray, 2185 int vertexIndex1, int vertexIndex2, int vertexIndex3, 2186 float [] vertices, 2187 GeneralPath geometryPath, int triangleIndex, Area nodeArea) { 2188 addTriangleToPath(geometryArray, geometryArray.getCoordinateIndex(vertexIndex1), 2189 geometryArray.getCoordinateIndex(vertexIndex2), 2190 geometryArray.getCoordinateIndex(vertexIndex3), vertices, geometryPath, triangleIndex, nodeArea); 2191 } 2192 2193 /** 2194 * Adds to <code>geometryPath</code> the quadrilateral joining vertices at 2195 * vertexIndex1, vertexIndex2, vertexIndex3, vertexIndex4 indices. 2196 */ addIndexedQuadrilateralToPath(IndexedGeometryArray geometryArray, int vertexIndex1, int vertexIndex2, int vertexIndex3, int vertexIndex4, float [] vertices, GeneralPath geometryPath, int quadrilateralIndex, Area nodeArea)2197 private void addIndexedQuadrilateralToPath(IndexedGeometryArray geometryArray, 2198 int vertexIndex1, int vertexIndex2, int vertexIndex3, int vertexIndex4, 2199 float [] vertices, 2200 GeneralPath geometryPath, int quadrilateralIndex, Area nodeArea) { 2201 addQuadrilateralToPath(geometryArray, geometryArray.getCoordinateIndex(vertexIndex1), 2202 geometryArray.getCoordinateIndex(vertexIndex2), 2203 geometryArray.getCoordinateIndex(vertexIndex3), 2204 geometryArray.getCoordinateIndex(vertexIndex4), vertices, geometryPath, quadrilateralIndex, nodeArea); 2205 } 2206 2207 /** 2208 * Adds to <code>geometryPath</code> the triangle joining vertices at 2209 * vertexIndex1, vertexIndex2, vertexIndex3 indices, 2210 * only if the triangle has a positive orientation. 2211 */ addTriangleToPath(GeometryArray geometryArray, int vertexIndex1, int vertexIndex2, int vertexIndex3, float [] vertices, GeneralPath geometryPath, int triangleIndex, Area nodeArea)2212 private void addTriangleToPath(GeometryArray geometryArray, 2213 int vertexIndex1, int vertexIndex2, int vertexIndex3, 2214 float [] vertices, 2215 GeneralPath geometryPath, int triangleIndex, Area nodeArea) { 2216 float xVertex1 = vertices [2 * vertexIndex1]; 2217 float yVertex1 = vertices [2 * vertexIndex1 + 1]; 2218 float xVertex2 = vertices [2 * vertexIndex2]; 2219 float yVertex2 = vertices [2 * vertexIndex2 + 1]; 2220 float xVertex3 = vertices [2 * vertexIndex3]; 2221 float yVertex3 = vertices [2 * vertexIndex3 + 1]; 2222 if ((xVertex2 - xVertex1) * (yVertex3 - yVertex2) - (yVertex2 - yVertex1) * (xVertex3 - xVertex2) > 0) { 2223 if (triangleIndex > 0 && triangleIndex % 1000 == 0) { 2224 // Add now current path to area otherwise area gets too slow 2225 nodeArea.add(new Area(geometryPath)); 2226 geometryPath.reset(); 2227 } 2228 geometryPath.moveTo(xVertex1, yVertex1); 2229 geometryPath.lineTo(xVertex2, yVertex2); 2230 geometryPath.lineTo(xVertex3, yVertex3); 2231 geometryPath.closePath(); 2232 } 2233 } 2234 2235 /** 2236 * Adds to <code>geometryPath</code> the quadrilateral joining vertices at 2237 * vertexIndex1, vertexIndex2, vertexIndex3, vertexIndex4 indices, 2238 * only if the quadrilateral has a positive orientation. 2239 */ addQuadrilateralToPath(GeometryArray geometryArray, int vertexIndex1, int vertexIndex2, int vertexIndex3, int vertexIndex4, float [] vertices, GeneralPath geometryPath, int quadrilateralIndex, Area nodeArea)2240 private void addQuadrilateralToPath(GeometryArray geometryArray, 2241 int vertexIndex1, int vertexIndex2, int vertexIndex3, int vertexIndex4, 2242 float [] vertices, 2243 GeneralPath geometryPath, int quadrilateralIndex, Area nodeArea) { 2244 float xVertex1 = vertices [2 * vertexIndex1]; 2245 float yVertex1 = vertices [2 * vertexIndex1 + 1]; 2246 float xVertex2 = vertices [2 * vertexIndex2]; 2247 float yVertex2 = vertices [2 * vertexIndex2 + 1]; 2248 float xVertex3 = vertices [2 * vertexIndex3]; 2249 float yVertex3 = vertices [2 * vertexIndex3 + 1]; 2250 if ((xVertex2 - xVertex1) * (yVertex3 - yVertex2) - (yVertex2 - yVertex1) * (xVertex3 - xVertex2) > 0) { 2251 if (quadrilateralIndex > 0 && quadrilateralIndex % 1000 == 0) { 2252 // Add now current path to area otherwise area gets too slow 2253 nodeArea.add(new Area(geometryPath)); 2254 geometryPath.reset(); 2255 } 2256 geometryPath.moveTo(xVertex1, yVertex1); 2257 geometryPath.lineTo(xVertex2, yVertex2); 2258 geometryPath.lineTo(xVertex3, yVertex3); 2259 geometryPath.lineTo(vertices [2 * vertexIndex4], vertices [2 * vertexIndex4 + 1]); 2260 geometryPath.closePath(); 2261 } 2262 } 2263 2264 /** 2265 * Computes the vertices coordinates projected on floor of the 3D shapes children of <code>node</code>. 2266 */ computeVerticesOnFloor(Node node, List<float []> vertices, Transform3D parentTransformations)2267 private void computeVerticesOnFloor(Node node, List<float []> vertices, Transform3D parentTransformations) { 2268 if (node instanceof Group) { 2269 if (node instanceof TransformGroup) { 2270 parentTransformations = new Transform3D(parentTransformations); 2271 Transform3D transform = new Transform3D(); 2272 ((TransformGroup)node).getTransform(transform); 2273 parentTransformations.mul(transform); 2274 } 2275 // Compute all children 2276 Enumeration<?> enumeration = ((Group)node).getAllChildren(); 2277 while (enumeration.hasMoreElements()) { 2278 computeVerticesOnFloor((Node)enumeration.nextElement(), vertices, parentTransformations); 2279 } 2280 } else if (node instanceof Link) { 2281 computeVerticesOnFloor(((Link)node).getSharedGroup(), vertices, parentTransformations); 2282 } else if (node instanceof Shape3D) { 2283 Shape3D shape = (Shape3D)node; 2284 Appearance appearance = shape.getAppearance(); 2285 RenderingAttributes renderingAttributes = appearance != null 2286 ? appearance.getRenderingAttributes() : null; 2287 TransparencyAttributes transparencyAttributes = appearance != null 2288 ? appearance.getTransparencyAttributes() : null; 2289 if ((renderingAttributes == null 2290 || renderingAttributes.getVisible()) 2291 && (transparencyAttributes == null 2292 || transparencyAttributes.getTransparency() < 1)) { 2293 // Compute shape geometries area 2294 for (int i = 0, n = shape.numGeometries(); i < n; i++) { 2295 Geometry geometry = shape.getGeometry(i); 2296 if (geometry instanceof GeometryArray) { 2297 GeometryArray geometryArray = (GeometryArray)geometry; 2298 int vertexCount = geometryArray.getVertexCount(); 2299 Point3f vertex = new Point3f(); 2300 if ((geometryArray.getVertexFormat() & GeometryArray.BY_REFERENCE) != 0) { 2301 if ((geometryArray.getVertexFormat() & GeometryArray.INTERLEAVED) != 0) { 2302 float [] vertexData = geometryArray.getInterleavedVertices(); 2303 int vertexSize = vertexData.length / vertexCount; 2304 // Store vertices coordinates 2305 for (int index = 0, j = vertexSize - 3; index < vertexCount; j += vertexSize, index++) { 2306 vertex.x = vertexData [j]; 2307 vertex.y = vertexData [j + 1]; 2308 vertex.z = vertexData [j + 2]; 2309 parentTransformations.transform(vertex); 2310 vertices.add(new float [] {vertex.x, vertex.z}); 2311 } 2312 } else { 2313 // Store vertices coordinates 2314 float [] vertexCoordinates = geometryArray.getCoordRefFloat(); 2315 for (int index = 0, j = 0; index < vertexCount; j += 3, index++) { 2316 vertex.x = vertexCoordinates [j]; 2317 vertex.y = vertexCoordinates [j + 1]; 2318 vertex.z = vertexCoordinates [j + 2]; 2319 parentTransformations.transform(vertex); 2320 vertices.add(new float [] {vertex.x, vertex.z}); 2321 } 2322 } 2323 } else { 2324 // Store vertices coordinates 2325 for (int index = 0, j = 0; index < vertexCount; j++, index++) { 2326 geometryArray.getCoordinate(j, vertex); 2327 parentTransformations.transform(vertex); 2328 vertices.add(new float [] {vertex.x, vertex.z}); 2329 } 2330 } 2331 } 2332 } 2333 } 2334 } 2335 } 2336 2337 /** 2338 * Returns the convex polygon that surrounds the given <code>vertices</code>. 2339 * From Andrew's monotone chain 2D convex hull algorithm described at 2340 * http://softsurfer.com/Archive/algorithm%5F0109/algorithm%5F0109.htm 2341 */ getSurroundingPolygon(float [][] vertices)2342 private float [][] getSurroundingPolygon(float [][] vertices) { 2343 Arrays.sort(vertices, new Comparator<float []> () { 2344 public int compare(float [] vertex1, float [] vertex2) { 2345 if (vertex1 [0] == vertex2 [0]) { 2346 return (int)Math.signum(vertex2 [1] - vertex1 [1]); 2347 } else { 2348 return (int)Math.signum(vertex2 [0] - vertex1 [0]); 2349 } 2350 } 2351 }); 2352 float [][] polygon = new float [vertices.length][]; 2353 // The output array polygon [] will be used as the stack 2354 int bottom = 0, top = -1; // indices for bottom and top of the stack 2355 int i; // array scan index 2356 2357 // Get the indices of points with min x-coord and min|max y-coord 2358 int minMin = 0, minMax; 2359 float xmin = vertices [0][0]; 2360 for (i = 1; i < vertices.length; i++) { 2361 if (vertices [i][0] != xmin) { 2362 break; 2363 } 2364 } 2365 minMax = i - 1; 2366 if (minMax == vertices.length - 1) { 2367 // Degenerate case: all x-coords == xmin 2368 polygon [++top] = vertices [minMin]; 2369 if (vertices [minMax][1] != vertices [minMin][1]) { 2370 // A nontrivial segment 2371 polygon [++top] = vertices [minMax]; 2372 } 2373 // Add polygon end point 2374 polygon [++top] = vertices [minMin]; 2375 float [][] surroundingPolygon = new float [top + 1][]; 2376 System.arraycopy(polygon, 0, surroundingPolygon, 0, surroundingPolygon.length); 2377 return surroundingPolygon; 2378 } 2379 2380 // Get the indices of points with max x-coord and min|max y-coord 2381 int maxMin, maxMax = vertices.length - 1; 2382 float xMax = vertices [vertices.length - 1][0]; 2383 for (i = vertices.length - 2; i >= 0; i--) { 2384 if (vertices [i][0] != xMax) { 2385 break; 2386 } 2387 } 2388 maxMin = i + 1; 2389 2390 // Compute the lower hull on the stack polygon 2391 polygon [++top] = vertices [minMin]; // push minmin point onto stack 2392 i = minMax; 2393 while (++i <= maxMin) { 2394 // The lower line joins points [minmin] with points [maxmin] 2395 if (isLeft(vertices [minMin], vertices [maxMin], vertices [i]) >= 0 && i < maxMin) { 2396 // ignore points [i] above or on the lower line 2397 continue; 2398 } 2399 2400 while (top > 0) // There are at least 2 points on the stack 2401 { 2402 // Test if points [i] is left of the line at the stack top 2403 if (isLeft(polygon [top - 1], polygon [top], vertices [i]) > 0) { 2404 break; // points [i] is a new hull vertex 2405 } else { 2406 top--; // pop top point off stack 2407 } 2408 } 2409 polygon [++top] = vertices [i]; // push points [i] onto stack 2410 } 2411 2412 // Next, compute the upper hull on the stack polygon above the bottom hull 2413 // If distinct xmax points 2414 if (maxMax != maxMin) { 2415 // Push maxmax point onto stack 2416 polygon [++top] = vertices [maxMax]; 2417 } 2418 // The bottom point of the upper hull stack 2419 bottom = top; 2420 i = maxMin; 2421 while (--i >= minMax) { 2422 // The upper line joins points [maxmax] with points [minmax] 2423 if (isLeft(vertices [maxMax], vertices [minMax], vertices [i]) >= 0 && i > minMax) { 2424 // Ignore points [i] below or on the upper line 2425 continue; 2426 } 2427 2428 // At least 2 points on the upper stack 2429 while (top > bottom) 2430 { 2431 // Test if points [i] is left of the line at the stack top 2432 if (isLeft(polygon [top - 1], polygon [top], vertices [i]) > 0) { 2433 // points [i] is a new hull vertex 2434 break; 2435 } else { 2436 // Pop top point off stack 2437 top--; 2438 } 2439 } 2440 // Push points [i] onto stack 2441 polygon [++top] = vertices [i]; 2442 } 2443 if (minMax != minMin) { 2444 // Push joining endpoint onto stack 2445 polygon [++top] = vertices [minMin]; 2446 } 2447 2448 float [][] surroundingPolygon = new float [top + 1][]; 2449 System.arraycopy(polygon, 0, surroundingPolygon, 0, surroundingPolygon.length); 2450 return surroundingPolygon; 2451 } 2452 isLeft(float [] vertex0, float [] vertex1, float [] vertex2)2453 private float isLeft(float [] vertex0, float [] vertex1, float [] vertex2) { 2454 return (vertex1 [0] - vertex0 [0]) * (vertex2 [1] - vertex0 [1]) 2455 - (vertex2 [0] - vertex0 [0]) * (vertex1 [1] - vertex0 [1]); 2456 } 2457 2458 /** 2459 * Returns the area on the floor of the given staircase. 2460 */ getAreaOnFloor(HomePieceOfFurniture staircase)2461 public Area getAreaOnFloor(HomePieceOfFurniture staircase) { 2462 if (staircase.getStaircaseCutOutShape() == null) { 2463 throw new IllegalArgumentException("No cut out shape associated to piece"); 2464 } 2465 Shape shape = getShape(staircase.getStaircaseCutOutShape()); 2466 Area staircaseArea = new Area(shape); 2467 if (staircase.isModelMirrored()) { 2468 staircaseArea = getMirroredArea(staircaseArea); 2469 } 2470 AffineTransform staircaseTransform = AffineTransform.getTranslateInstance( 2471 staircase.getX() - staircase.getWidth() / 2, 2472 staircase.getY() - staircase.getDepth() / 2); 2473 staircaseTransform.concatenate(AffineTransform.getRotateInstance(staircase.getAngle(), 2474 staircase.getWidth() / 2, staircase.getDepth() / 2)); 2475 staircaseTransform.concatenate(AffineTransform.getScaleInstance(staircase.getWidth(), staircase.getDepth())); 2476 staircaseArea.transform(staircaseTransform); 2477 return staircaseArea; 2478 } 2479 2480 /** 2481 * Returns the mirror area of the given <code>area</code>. 2482 */ getMirroredArea(Area area)2483 private Area getMirroredArea(Area area) { 2484 // As applying a -1 scale transform reverses the holes / non holes interpretation of the points, 2485 // we have to create a mirrored shape by parsing points 2486 GeneralPath mirrorPath = new GeneralPath(); 2487 float [] point = new float[6]; 2488 for (PathIterator it = area.getPathIterator(null); !it.isDone(); it.next()) { 2489 switch (it.currentSegment(point)) { 2490 case PathIterator.SEG_MOVETO : 2491 mirrorPath.moveTo(1 - point[0], point[1]); 2492 break; 2493 case PathIterator.SEG_LINETO : 2494 mirrorPath.lineTo(1 - point[0], point[1]); 2495 break; 2496 case PathIterator.SEG_QUADTO : 2497 mirrorPath.quadTo(1 - point[0], point[1], 1 - point[2], point[3]); 2498 break; 2499 case PathIterator.SEG_CUBICTO : 2500 mirrorPath.curveTo(1 - point[0], point[1], 1 - point[2], point[3], 1 - point[4], point[5]); 2501 break; 2502 case PathIterator.SEG_CLOSE : 2503 mirrorPath.closePath(); 2504 break; 2505 } 2506 } 2507 return new Area(mirrorPath); 2508 } 2509 2510 /** 2511 * Returns the AWT shape matching the given <a href="http://www.w3.org/TR/SVG/paths.html">SVG path shape</a>. 2512 */ getShape(String svgPathShape)2513 public Shape getShape(String svgPathShape) { 2514 return ShapeTools.getShape(svgPathShape); 2515 } 2516 2517 /** 2518 * An observer that receives model loading notifications. 2519 */ 2520 public static interface ModelObserver { modelUpdated(BranchGroup modelRoot)2521 public void modelUpdated(BranchGroup modelRoot); 2522 modelError(Exception ex)2523 public void modelError(Exception ex); 2524 } 2525 } 2526