1 /* 2 * ModelManager.js 3 * 4 * Sweet Home 3D, Copyright (c) 2015 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 21 // Requires URLContent.js 22 // scene3d.js 23 // ModelLoader.js 24 // OBJLoader.js 25 // Uses HomeObject.js 26 // HomePieceOfFurniture.js 27 // HomeMaterial.js 28 // HomeTexture.js 29 // CatalogTexture.js 30 // ShapeTools.js 31 // (used classes are not needed to view 3D models) 32 33 /** 34 * Singleton managing 3D models cache. 35 * @constructor 36 * @author Emmanuel Puybaret 37 */ 38 function ModelManager() { 39 this.loadedModelNodes = {}; 40 this.loadingModelObservers = {}; 41 } 42 43 /** 44 * Special shapes prefix; 45 */ 46 ModelManager.SPECIAL_SHAPE_PREFIX = "sweethome3d_"; 47 /** 48 * <code>Shape3D</code> name prefix for window pane shapes. 49 */ 50 ModelManager.WINDOW_PANE_SHAPE_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "window_pane"; 51 /** 52 * <code>Shape3D</code> name prefix for mirror shapes. 53 */ 54 ModelManager.MIRROR_SHAPE_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "window_mirror"; 55 /** 56 * <code>Shape3D</code> name prefix for lights. 57 */ 58 ModelManager.LIGHT_SHAPE_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "light"; 59 /** 60 * <code>Node</code> user data prefix for mannequin parts. 61 */ 62 ModelManager.MANNEQUIN_ABDOMEN_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_abdomen"; 63 ModelManager.MANNEQUIN_CHEST_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_chest"; 64 ModelManager.MANNEQUIN_PELVIS_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_pelvis"; 65 ModelManager.MANNEQUIN_NECK_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_neck"; 66 ModelManager.MANNEQUIN_HEAD_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_head"; 67 ModelManager.MANNEQUIN_LEFT_SHOULDER_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_left_shoulder"; 68 ModelManager.MANNEQUIN_LEFT_ARM_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_left_arm"; 69 ModelManager.MANNEQUIN_LEFT_ELBOW_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_left_elbow"; 70 ModelManager.MANNEQUIN_LEFT_FOREARM_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_left_forearm"; 71 ModelManager.MANNEQUIN_LEFT_WRIST_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_left_wrist"; 72 ModelManager.MANNEQUIN_LEFT_HAND_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_left_hand"; 73 ModelManager.MANNEQUIN_LEFT_HIP_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_left_hip"; 74 ModelManager.MANNEQUIN_LEFT_THIGH_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_left_thigh"; 75 ModelManager.MANNEQUIN_LEFT_KNEE_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_left_knee"; 76 ModelManager.MANNEQUIN_LEFT_LEG_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_left_leg"; 77 ModelManager.MANNEQUIN_LEFT_ANKLE_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_left_ankle"; 78 ModelManager.MANNEQUIN_LEFT_FOOT_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_left_foot"; 79 ModelManager.MANNEQUIN_RIGHT_SHOULDER_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_right_shoulder"; 80 ModelManager.MANNEQUIN_RIGHT_ARM_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_right_arm"; 81 ModelManager.MANNEQUIN_RIGHT_ELBOW_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_right_elbow"; 82 ModelManager.MANNEQUIN_RIGHT_FOREARM_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_right_forearm"; 83 ModelManager.MANNEQUIN_RIGHT_WRIST_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_right_wrist"; 84 ModelManager.MANNEQUIN_RIGHT_HAND_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_right_hand"; 85 ModelManager.MANNEQUIN_RIGHT_HIP_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_right_hip"; 86 ModelManager.MANNEQUIN_RIGHT_THIGH_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_right_thigh"; 87 ModelManager.MANNEQUIN_RIGHT_KNEE_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_right_knee"; 88 ModelManager.MANNEQUIN_RIGHT_LEG_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_right_leg"; 89 ModelManager.MANNEQUIN_RIGHT_ANKLE_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_right_ankle"; 90 ModelManager.MANNEQUIN_RIGHT_FOOT_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_right_foot"; 91 92 ModelManager.MANNEQUIN_ABDOMEN_CHEST_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_abdomen_chest"; 93 ModelManager.MANNEQUIN_ABDOMEN_PELVIS_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "mannequin_abdomen_pelvis"; 94 /** 95 * <code>Node</code> user data prefix for ball / rotating joints. 96 */ 97 ModelManager.BALL_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "ball_"; 98 ModelManager.ARM_ON_BALL_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "arm_on_ball_"; 99 /** 100 * <code>Node</code> user data prefix for hinge / rotating opening joints. 101 */ 102 ModelManager.HINGE_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "hinge_"; 103 ModelManager.OPENING_ON_HINGE_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "opening_on_hinge_"; 104 ModelManager.WINDOW_PANE_ON_HINGE_PREFIX = ModelManager.WINDOW_PANE_SHAPE_PREFIX + "_on_hinge_"; 105 ModelManager.MIRROR_ON_HINGE_PREFIX = ModelManager.MIRROR_SHAPE_PREFIX + "_on_hinge_"; 106 /** 107 * <code>Node</code> user data prefix for rail / sliding opening joints. 108 */ 109 ModelManager.UNIQUE_RAIL_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "unique_rail"; 110 ModelManager.RAIL_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "rail_"; 111 ModelManager.OPENING_ON_RAIL_PREFIX = ModelManager.SPECIAL_SHAPE_PREFIX + "opening_on_rail_"; 112 ModelManager.WINDOW_PANE_ON_RAIL_PREFIX = ModelManager.WINDOW_PANE_SHAPE_PREFIX + "_on_rail_"; 113 ModelManager.MIRROR_ON_RAIL_PREFIX = ModelManager.MIRROR_SHAPE_PREFIX + "_on_rail_"; 114 /** 115 * <code>Node</code> user data separator for sub transformations. 116 */ 117 ModelManager.SUB_TRANSFORMATION_SEPARATOR = "_and_"; 118 /** 119 * Deformable group suffix. 120 */ 121 ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX = "_transformation"; 122 123 ModelManager.EDGE_COLOR_MATERIAL_PREFIX = "edge_color"; 124 125 // Singleton 126 ModelManager.instance = null; 127 128 /** 129 * Returns an instance of this singleton. 130 * @return {ModelManager} 131 */ 132 ModelManager.getInstance = function() { 133 if (ModelManager.instance == null) { 134 ModelManager.instance = new ModelManager(); 135 } 136 return ModelManager.instance; 137 } 138 139 /** 140 * Clears loaded models cache. 141 */ 142 ModelManager.prototype.clear = function() { 143 this.loadedModelNodes = {}; 144 this.loadingModelObservers = {}; 145 if (this.modelLoaders) { 146 for (var i = 0; i < this.modelLoaders.length; i++) { 147 this.modelLoaders [i].clear(); 148 } 149 } 150 } 151 152 /** 153 * Returns the minimum size of a model. 154 */ 155 ModelManager.prototype.getMinimumSize = function() { 156 return 0.001; 157 } 158 159 /** 160 * Returns the size of 3D shapes of node after an additional optional transformation. 161 * @param {Node3D} node the root of a model 162 * @param {Array} [transformation] the optional transformation applied to the model 163 */ 164 ModelManager.prototype.getSize = function(node, transformation) { 165 if (transformation === undefined) { 166 transformation = mat4.create(); 167 } 168 var bounds = this.getBounds(node, transformation); 169 var lower = vec3.create(); 170 bounds.getLower(lower); 171 var upper = vec3.create(); 172 bounds.getUpper(upper); 173 return vec3.fromValues(Math.max(this.getMinimumSize(), upper[0] - lower[0]), 174 Math.max(this.getMinimumSize(), upper[1] - lower[1]), 175 Math.max(this.getMinimumSize(), upper[2] - lower[2])); 176 } 177 178 /** 179 * Returns the center of the bounds of <code>node</code> 3D shapes. 180 * @param node the root of a model 181 */ 182 ModelManager.prototype.getCenter = function(node) { 183 var bounds = this.getBounds(node); 184 var lower = vec3.create(); 185 bounds.getLower(lower); 186 var upper = vec3.create(); 187 bounds.getUpper(upper); 188 return vec3.fromValues((lower[0] + upper[0]) / 2, (lower[1] + upper[1]) / 2, (lower[2] + upper[2]) / 2); 189 } 190 191 /** 192 * Returns the bounds of the 3D shapes of node with an additional optional transformation. 193 * @param {Node3D} node the root of a model 194 * @param {Array} [transformation] the optional transformation applied to the model 195 */ 196 ModelManager.prototype.getBounds = function(node, transformation) { 197 if (transformation === undefined) { 198 transformation = mat4.create(); 199 } 200 var objectBounds = new BoundingBox3D( 201 vec3.fromValues(Infinity, Infinity, Infinity), 202 vec3.fromValues(-Infinity, -Infinity, -Infinity)); 203 this.computeBounds(node, objectBounds, transformation, !this.isOrthogonalRotation(transformation), this.isDeformed(node)); 204 return objectBounds; 205 } 206 207 /** 208 * Returns true if the rotation matrix matches only rotations of 209 * a multiple of 90° degrees around x, y or z axis. 210 * @private 211 */ 212 ModelManager.prototype.isOrthogonalRotation = function(transformation) { 213 for (var i = 0; i < 3; i++) { 214 for (var j = 0; j < 3; j++) { 215 // Return false if the matrix contains a value different from 0 1 or -1 216 if (Math.abs(transformation[i * 4 + j]) > 1E-6 217 && Math.abs(transformation[i * 4 + j] - 1) > 1E-6 218 && Math.abs(transformation[i * 4 + j] + 1) > 1E-6) { 219 return false; 220 } 221 } 222 } 223 return true; 224 } 225 226 /** 227 * @private 228 */ 229 ModelManager.prototype.computeBounds = function(node, bounds, parentTransformation, transformShapeGeometry, deformedGeometry) { 230 if (node instanceof Group3D) { 231 if (node instanceof TransformGroup3D) { 232 parentTransformation = mat4.clone(parentTransformation); 233 mat4.mul(parentTransformation, parentTransformation, node.transform); 234 } 235 // Compute the bounds of all the node children 236 for (var i = 0; i < node.children.length; i++) { 237 this.computeBounds(node.children [i], bounds, parentTransformation, transformShapeGeometry, deformedGeometry); 238 } 239 } else if (node instanceof Link3D) { 240 this.computeBounds(node.getSharedGroup(), bounds, parentTransformation, transformShapeGeometry, deformedGeometry); 241 } else if (node instanceof Shape3D) { 242 var shapeBounds; 243 if (transformShapeGeometry 244 || deformedGeometry 245 && !this.isOrthogonalRotation(parentTransformation)) { 246 shapeBounds = this.computeTransformedGeometryBounds(node, parentTransformation); 247 } else { 248 shapeBounds = node.getBounds(); 249 shapeBounds.transform(parentTransformation); 250 } 251 bounds.combine(shapeBounds); 252 } 253 } 254 255 /** 256 * @private 257 */ 258 ModelManager.prototype.computeTransformedGeometryBounds = function(shape, transformation) { 259 var lower = vec3.fromValues(Infinity, Infinity, Infinity); 260 var upper = vec3.fromValues(-Infinity, -Infinity, -Infinity); 261 for (var i = 0; i < shape.geometries.length; i++) { 262 // geometry instanceof IndexedGeometryArray3D 263 var geometry = shape.geometries [i]; 264 var vertex = vec3.create(); 265 for (var index = 0; index < geometry.vertexIndices.length; index++) { 266 vec3.copy(vertex, geometry.vertices [geometry.vertexIndices [index]]); 267 this.updateBounds(vertex, transformation, lower, upper); 268 } 269 } 270 return new BoundingBox3D(lower, upper); 271 } 272 273 /** 274 * @private 275 */ 276 ModelManager.prototype.updateBounds = function(vertex, transformation, lower, upper) { 277 if (transformation !== null) { 278 vec3.transformMat4(vertex, vertex, transformation); 279 } 280 vec3.min(lower, lower, vertex); 281 vec3.max(upper, upper, vertex); 282 } 283 284 /** 285 * Returns a transform group that will transform the model node 286 * to let it fill a box of the given width centered on the origin. 287 * @param {Node3D} node the root of a model with any size and location 288 * @param {Array} modelRotation the rotation applied to the model before normalization 289 * or <code>null</code> if no transformation should be applied to node. 290 * @param {number} width the width of the box 291 * @param {boolean} [modelCenteredAtOrigin] if <code>true</code> or missing, center will be moved 292 * to match the origin after the model rotation is applied 293 */ 294 ModelManager.prototype.getNormalizedTransformGroup = function(node, modelRotation, width, modelCenteredAtOrigin) { 295 return new TransformGroup3D(this.getNormalizedTransform( 296 node, modelRotation, width, modelCenteredAtOrigin !== false)); 297 } 298 299 /** 300 * Returns a transformation matrix that will transform the model node 301 * to let it fill a box of the given width centered on the origin. 302 * @param {Node3D} node the root of a model with any size and location 303 * @param {?Array} modelRotation the rotation applied to the model before normalization 304 * or <code>null</code> if no transformation should be applied to node. 305 * @param {number} width the width of the box 306 * @param {boolean} [modelCenteredAtOrigin] if <code>true</code> center will be moved to match the origin 307 * after the model rotation is applied 308 */ 309 ModelManager.prototype.getNormalizedTransform = function(node, modelRotation, width, modelCenteredAtOrigin) { 310 // Get model bounding box size 311 var modelBounds = this.getBounds(node); 312 var lower = vec3.create(); 313 modelBounds.getLower(lower); 314 var upper = vec3.create(); 315 modelBounds.getUpper(upper); 316 // Translate model to its center 317 var translation = mat4.create(); 318 mat4.translate(translation, translation, vec3.fromValues( 319 -lower[0] - (upper[0] - lower[0]) / 2, 320 -lower[1] - (upper[1] - lower[1]) / 2, 321 -lower[2] - (upper[2] - lower[2]) / 2)); 322 323 var modelTransform; 324 if (modelRotation !== undefined && modelRotation !== null) { 325 // Get model bounding box size with model rotation 326 var rotationTransform = this.getRotationTransformation(modelRotation); 327 mat4.mul(rotationTransform, rotationTransform, translation); 328 var rotatedModelBounds = this.getBounds(node, rotationTransform); 329 rotatedModelBounds.getLower(lower); 330 rotatedModelBounds.getUpper(upper); 331 modelTransform = mat4.create(); 332 if (modelCenteredAtOrigin) { 333 // Move model back to its new center 334 mat4.translate(modelTransform, modelTransform, vec3.fromValues( 335 -lower[0] - (upper[0] - lower[0]) / 2, 336 -lower[1] - (upper[1] - lower[1]) / 2, 337 -lower[2] - (upper[2] - lower[2]) / 2)); 338 } 339 mat4.mul(modelTransform, modelTransform, rotationTransform); 340 } else { 341 modelTransform = translation; 342 } 343 344 // Scale model to make it fill a 1 unit wide box 345 var scaleOneTransform = mat4.create(); 346 mat4.scale(scaleOneTransform, scaleOneTransform, 347 vec3.fromValues(width / Math.max(this.getMinimumSize(), upper[0] - lower[0]), 348 width / Math.max(this.getMinimumSize(), upper[1] - lower[1]), 349 width / Math.max(this.getMinimumSize(), upper[2] - lower[2]))); 350 mat4.mul(scaleOneTransform, scaleOneTransform, modelTransform); 351 return scaleOneTransform; 352 } 353 354 /** 355 * Returns a transformation matching the given rotation. 356 * @param {Array} modelRotation the desired rotation. 357 * @ignore 358 */ 359 ModelManager.prototype.getRotationTransformation = function(modelRotation) { 360 var modelTransform = mat4.create(); 361 modelTransform [0] = modelRotation [0][0]; 362 modelTransform [4] = modelRotation [0][1]; 363 modelTransform [8] = modelRotation [0][2]; 364 modelTransform [1] = modelRotation [1][0]; 365 modelTransform [5] = modelRotation [1][1]; 366 modelTransform [9] = modelRotation [1][2]; 367 modelTransform [2] = modelRotation [2][0]; 368 modelTransform [6] = modelRotation [2][1]; 369 modelTransform [10] = modelRotation [2][2]; 370 return modelTransform; 371 } 372 373 /** 374 * Returns a transformation able to place in the scene the normalized model 375 * of the given <code>piece</code>. 376 * @param {HomePieceOfFurniture} piece a piece of furniture 377 * @param {Node3D} [normalizedModelNode] the node matching the normalized model of the piece. 378 * This parameter is required only if the piece is rotated horizontally 379 * @ignore 380 */ 381 ModelManager.prototype.getPieceOfFurnitureNormalizedModelTransformation = function(piece, normalizedModelNode) { 382 // Set piece size 383 var scale = mat4.create(); 384 var pieceWidth = piece.getWidth(); 385 // If piece model is mirrored, inverse its width 386 if (piece.isModelMirrored()) { 387 pieceWidth *= -1; 388 } 389 mat4.scale(scale, scale, vec3.fromValues(pieceWidth, piece.getHeight(), piece.getDepth())); 390 391 var modelTransform; 392 var height; 393 if (piece.isHorizontallyRotated() && normalizedModelNode !== undefined && normalizedModelNode !== null) { 394 var horizontalRotationAndScale = mat4.create(); 395 // Change its angle around horizontal axes 396 if (piece.getPitch() != 0) { 397 mat4.fromXRotation(horizontalRotationAndScale, -piece.getPitch()); 398 } 399 if (piece.getRoll() != 0) { 400 var rollRotation = mat4.create(); 401 mat4.fromZRotation(rollRotation, -piece.getRoll()); 402 mat4.mul(horizontalRotationAndScale, rollRotation, horizontalRotationAndScale); 403 } 404 mat4.mul(horizontalRotationAndScale, horizontalRotationAndScale, scale); 405 406 // Compute center location when the piece is rotated around horizontal axes 407 var rotatedModelBounds = this.getBounds(normalizedModelNode, horizontalRotationAndScale); 408 var lower = vec3.create(); 409 rotatedModelBounds.getLower(lower); 410 var upper = vec3.create(); 411 rotatedModelBounds.getUpper(upper); 412 modelTransform = mat4.create(); 413 mat4.translate(modelTransform, modelTransform, vec3.fromValues( 414 -lower[0] - (upper[0] - lower[0]) / 2, 415 -lower[1] - (upper[1] - lower[1]) / 2, 416 -lower[2] - (upper[2] - lower[2]) / 2)); 417 mat4.mul(modelTransform, modelTransform, horizontalRotationAndScale); 418 height = Math.max(this.getMinimumSize(), upper[1] - lower[1]); 419 } else { 420 modelTransform = scale; 421 height = piece.getHeight(); 422 } 423 424 // Change its angle around y axis 425 var verticalRotation = mat4.create(); 426 mat4.fromYRotation(verticalRotation, -piece.getAngle()); 427 mat4.mul(verticalRotation, verticalRotation, modelTransform); 428 429 // Translate it to its location 430 var pieceTransform = mat4.create(); 431 var levelElevation; 432 if (piece.getLevel() !== null) { 433 levelElevation = piece.getLevel().getElevation(); 434 } else { 435 levelElevation = 0; 436 } 437 mat4.translate(pieceTransform, pieceTransform, vec3.fromValues( 438 piece.getX(), 439 piece.getElevation() + height / 2 + levelElevation, 440 piece.getY())); 441 mat4.mul(pieceTransform, pieceTransform, verticalRotation); 442 return pieceTransform; 443 } 444 445 /** 446 * For backward compatibility. 447 * @deprecated 448 * @ignore 449 */ 450 ModelManager.prototype.getPieceOFFurnitureNormalizedModelTransformation = ModelManager.prototype.getPieceOfFurnitureNormalizedModelTransformation; 451 452 /** 453 * Reads a 3D node from content with supported loaders 454 * and notifies the loaded model to the given <code>modelObserver</code> once available 455 * with its <code>modelUpdated</code> and <code>modelError</code> methods. 456 * @param {URLContent} content an object containing a model 457 * @param {boolean} [synchronous] optional parameter equal to false by default 458 * @param {{modelUpdated, modelError, progression}} modelObserver 459 * the observer containing <code>modelUpdated(model)</code>, <code>modelError(error)</code>, 460 * <code>progression(part, info, percentage)</code> optional methods that will be 461 * notified once the model is available or if an error happens, 462 * with <code>model<code> being an instance of <code>Node3D</code>, 463 * <code>error</code>, <code>part</code>, <code>info</code> strings 464 * and <code>percentage</code> a number. 465 */ 466 ModelManager.prototype.loadModel = function(content, synchronous, modelObserver) { 467 if (modelObserver === undefined) { 468 modelObserver = synchronous; 469 synchronous = false; 470 } 471 var modelManager = this; 472 var contentUrl = content.getURL(); 473 if (contentUrl in this.loadedModelNodes) { 474 // Notify cached model to observer with a clone of the model 475 var model = this.loadedModelNodes [contentUrl]; 476 if (modelObserver.modelUpdated !== undefined) { 477 modelObserver.modelUpdated(this.cloneNode(model)); 478 } 479 } else if (synchronous) { 480 this.load(content, synchronous, { 481 modelLoaded : function(loadedModel) { 482 modelManager.loadedModelNodes [contentUrl] = loadedModel; 483 if (modelObserver.modelUpdated !== undefined) { 484 modelObserver.modelUpdated(modelManager.cloneNode(loadedModel)); 485 } 486 }, 487 modelError : function(err) { 488 if (modelObserver.modelError !== undefined) { 489 modelObserver.modelError(err); 490 } 491 }, 492 progression : function(part, info, percentage) { 493 if (modelObserver.progression !== undefined) { 494 modelObserver.progression(part, info, percentage); 495 } 496 } 497 }); 498 } else { 499 if (contentUrl in this.loadingModelObservers) { 500 // If observers list exists, content model is already being loaded 501 // register observer for future notification 502 this.loadingModelObservers [contentUrl].push(modelObserver); 503 } else { 504 // Create a list of observers that will be notified once content model is loaded 505 var observers = []; 506 observers.push(modelObserver); 507 this.loadingModelObservers [contentUrl] = observers; 508 509 this.load(content, synchronous, { 510 modelLoaded : function(loadedModel) { 511 modelManager.loadedModelNodes [contentUrl] = loadedModel; 512 var observers = modelManager.loadingModelObservers [contentUrl]; 513 if (observers) { 514 for (var i = 0; i < observers.length; i++) { 515 if (observers [i].modelUpdated !== undefined) { 516 observers [i].modelUpdated(modelManager.cloneNode(loadedModel)); 517 } 518 } 519 } 520 }, 521 modelError : function(err) { 522 var observers = modelManager.loadingModelObservers [contentUrl]; 523 if (observers) { 524 delete modelManager.loadingModelObservers [contentUrl]; 525 for (var i = 0; i < observers.length; i++) { 526 if (observers [i].modelError !== undefined) { 527 observers [i].modelError(err); 528 } 529 } 530 } 531 }, 532 progression : function(part, info, percentage) { 533 var observers = modelManager.loadingModelObservers [contentUrl]; 534 if (observers) { 535 for (var i = 0; i < observers.length; i++) { 536 if (observers [i].progression !== undefined) { 537 observers [i].progression(part, info, percentage); 538 } 539 } 540 } 541 } 542 }); 543 } 544 } 545 } 546 547 /** 548 * Removes the model matching the given content from the manager. 549 * @param {URLContent} content an object containing a model 550 * @param {boolean} disposeGeometries if <code>true</code> model geometries will be disposed too 551 */ 552 ModelManager.prototype.unloadModel = function(content, disposeGeometries) { 553 var contentUrl = content.getURL(); 554 var modelRoot = this.loadedModelNodes [contentUrl]; 555 delete this.loadedModelNodes [contentUrl]; 556 delete this.loadingModelObservers [contentUrl]; 557 if (disposeGeometries) { 558 this.disposeGeometries(modelRoot); 559 } 560 } 561 562 /** 563 * Frees geometry data of the given <code>node</code>. 564 * @param {Node3D} node the root of a model 565 * @package 566 */ 567 ModelManager.prototype.disposeGeometries = function(node) { 568 if (node instanceof Group3D) { 569 for (var i = 0; i < node.children.length; i++) { 570 this.disposeGeometries(node.children [i]); 571 } 572 } else if (node instanceof Link3D) { 573 // Not a problem to dispose more than once geometries of a shared group 574 this.disposeGeometries(node.getSharedGroup()); 575 } else if (node instanceof Shape3D) { 576 var geometries = node.getGeometries(); 577 for (var i = 0; i < geometries.length; i++) { 578 geometries [i].disposeCoordinates(); 579 } 580 } 581 } 582 583 /** 584 * Returns a clone of the given <code>node</code>. 585 * All the children and the attributes of the given node are duplicated except the geometries 586 * and the texture images of shapes. 587 * @param {Node3D} node the root of a model 588 * @param {Array} [clonedSharedGroups] 589 */ 590 ModelManager.prototype.cloneNode = function(node, clonedSharedGroups) { 591 if (clonedSharedGroups === undefined) { 592 return this.cloneNode(node, []); 593 } else { 594 var clonedNode = node.clone(); 595 if (node instanceof Shape3D) { 596 var clonedAppearance; 597 if (node.getAppearance()) { 598 clonedNode.setAppearance(node.getAppearance().clone()); 599 } 600 } else if (node instanceof Link3D) { 601 var clonedLink = node.clone(); 602 // Force duplication of shared groups too if not duplicated yet 603 var sharedGroup = clonedLink.getSharedGroup(); 604 if (sharedGroup !== null) { 605 var clonedSharedGroup = null; 606 for (var i = 0; i < clonedSharedGroups.length; i++) { 607 if (clonedSharedGroups [i].sharedGroup === sharedGroup) { 608 clonedSharedGroup = clonedSharedGroups [i].clonedSharedGroup; 609 break; 610 } 611 } 612 if (clonedSharedGroup === null) { 613 clonedSharedGroup = this.cloneNode(sharedGroup, clonedSharedGroups); 614 clonedSharedGroups.push({sharedGroup : sharedGroup, 615 clonedSharedGroup : clonedSharedGroup}); 616 } 617 clonedLink.setSharedGroup(clonedSharedGroup); 618 } 619 return clonedLink; 620 } else { 621 clonedNode = node.clone(); 622 if (node instanceof Group3D) { 623 var children = node.getChildren(); 624 for (var i = 0; i < children.length; i++) { 625 var clonedChild = this.cloneNode(children [i], clonedSharedGroups); 626 clonedNode.addChild(clonedChild); 627 } 628 } 629 } 630 return clonedNode; 631 } 632 } 633 634 /** 635 * Loads the node from <code>content</code> with supported loaders. 636 * @param {URLContent} content an object containing a model 637 * @param {boolean} [synchronous] optional parameter equal to false by default 638 * @param {{modelLoaded, modelError, progression}} modelObserver 639 * the observer that will be notified once the model is available 640 * or if an error happens 641 * @private 642 */ 643 ModelManager.prototype.load = function(content, synchronous, modelObserver) { 644 if (modelObserver === undefined) { 645 // 2 parameters (content, modelObserver) 646 modelObserver = synchronous; 647 synchronous = false; 648 } 649 650 var contentUrl = content.getURL(); 651 if (!this.modelLoaders) { 652 // As model loaders are reentrant, use the same loaders for multiple loading 653 this.modelLoaders = [new OBJLoader()]; 654 // Optional loaders 655 if (typeof DAELoader !== "undefined") { 656 this.modelLoaders.push(new DAELoader()); 657 } 658 if (typeof Max3DSLoader !== "undefined") { 659 this.modelLoaders.push(new Max3DSLoader()); 660 } 661 } 662 var modelManager = this; 663 var modelLoadingObserver = { 664 modelLoaderIndex : 0, 665 modelLoaded : function(model) { 666 var bounds = modelManager.getBounds(model); 667 if (!bounds.isEmpty()) { 668 modelManager.updateWindowPanesTransparency(model); 669 modelManager.updateDeformableModelHierarchy(model); 670 modelManager.replaceMultipleSharedShapes(model); 671 model.setUserData(content); 672 modelObserver.modelLoaded(model); 673 } else if (++this.modelLoaderIndex < modelManager.modelLoaders.length) { 674 modelManager.modelLoaders [this.modelLoaderIndex].load(contentUrl, synchronous, this); 675 } else { 676 this.modelError("Unsupported 3D format"); 677 } 678 }, 679 modelError : function(err) { 680 modelObserver.modelError(err); 681 }, 682 progression : function(part, info, percentage) { 683 modelObserver.progression(part, info, percentage); 684 } 685 }; 686 modelManager.modelLoaders [0].load(contentUrl, synchronous, modelLoadingObserver); 687 } 688 689 /** 690 * Updates the transparency of window panes shapes. 691 * @private 692 */ 693 ModelManager.prototype.updateWindowPanesTransparency = function(node) { 694 if (node instanceof Group3D) { 695 for (var i = 0; i < node.children.length; i++) { 696 this.updateWindowPanesTransparency(node.children [i]); 697 } 698 } else if (node instanceof Link3D) { 699 this.updateWindowPanesTransparency(node.getSharedGroup()); 700 } else if (node instanceof Shape3D) { 701 var name = node.getName(); 702 if (name 703 && name.indexOf(ModelManager.WINDOW_PANE_SHAPE_PREFIX) === 0) { 704 var appearance = node.getAppearance(); 705 if (appearance === null) { 706 appearance = new Appearance3D(); 707 node.setAppearance(appearance); 708 } 709 if (appearance.getTransparency() === undefined) { 710 appearance.setTransparency(0.5); 711 } 712 } 713 } 714 } 715 716 /** 717 * Updates the hierarchy of nodes with intermediate pickable nodes to help deforming models. 718 * @param {Group3D} group 719 * @private 720 */ 721 ModelManager.prototype.updateDeformableModelHierarchy = function(group) { 722 // Try to reorganize node hierarchy of mannequin model 723 if (this.containsNode(group, ModelManager.MANNEQUIN_ABDOMEN_PREFIX) 724 && this.containsNode(group, ModelManager.MANNEQUIN_CHEST_PREFIX) 725 && this.containsNode(group, ModelManager.MANNEQUIN_PELVIS_PREFIX) 726 && this.containsNode(group, ModelManager.MANNEQUIN_NECK_PREFIX) 727 && this.containsNode(group, ModelManager.MANNEQUIN_HEAD_PREFIX) 728 && this.containsNode(group, ModelManager.MANNEQUIN_LEFT_SHOULDER_PREFIX) 729 && this.containsNode(group, ModelManager.MANNEQUIN_LEFT_ARM_PREFIX) 730 && this.containsNode(group, ModelManager.MANNEQUIN_LEFT_ELBOW_PREFIX) 731 && this.containsNode(group, ModelManager.MANNEQUIN_LEFT_FOREARM_PREFIX) 732 && this.containsNode(group, ModelManager.MANNEQUIN_LEFT_WRIST_PREFIX) 733 && this.containsNode(group, ModelManager.MANNEQUIN_LEFT_HAND_PREFIX) 734 && this.containsNode(group, ModelManager.MANNEQUIN_LEFT_HIP_PREFIX) 735 && this.containsNode(group, ModelManager.MANNEQUIN_LEFT_THIGH_PREFIX) 736 && this.containsNode(group, ModelManager.MANNEQUIN_LEFT_KNEE_PREFIX) 737 && this.containsNode(group, ModelManager.MANNEQUIN_LEFT_LEG_PREFIX) 738 && this.containsNode(group, ModelManager.MANNEQUIN_LEFT_ANKLE_PREFIX) 739 && this.containsNode(group, ModelManager.MANNEQUIN_LEFT_FOOT_PREFIX) 740 && this.containsNode(group, ModelManager.MANNEQUIN_RIGHT_SHOULDER_PREFIX) 741 && this.containsNode(group, ModelManager.MANNEQUIN_RIGHT_ARM_PREFIX) 742 && this.containsNode(group, ModelManager.MANNEQUIN_RIGHT_ELBOW_PREFIX) 743 && this.containsNode(group, ModelManager.MANNEQUIN_RIGHT_FOREARM_PREFIX) 744 && this.containsNode(group, ModelManager.MANNEQUIN_RIGHT_WRIST_PREFIX) 745 && this.containsNode(group, ModelManager.MANNEQUIN_RIGHT_HAND_PREFIX) 746 && this.containsNode(group, ModelManager.MANNEQUIN_RIGHT_HIP_PREFIX) 747 && this.containsNode(group, ModelManager.MANNEQUIN_RIGHT_THIGH_PREFIX) 748 && this.containsNode(group, ModelManager.MANNEQUIN_RIGHT_KNEE_PREFIX) 749 && this.containsNode(group, ModelManager.MANNEQUIN_RIGHT_LEG_PREFIX) 750 && this.containsNode(group, ModelManager.MANNEQUIN_RIGHT_ANKLE_PREFIX) 751 && this.containsNode(group, ModelManager.MANNEQUIN_RIGHT_FOOT_PREFIX)) { 752 // Head 753 var head = this.extractNodes(group, ModelManager.MANNEQUIN_HEAD_PREFIX, null); 754 var headGroup = this.createPickableTransformGroup(ModelManager.MANNEQUIN_NECK_PREFIX, [head]); 755 756 // Left arm 757 var leftHand = this.extractNodes(group, ModelManager.MANNEQUIN_LEFT_HAND_PREFIX, null); 758 var leftHandGroup = this.createPickableTransformGroup(ModelManager.MANNEQUIN_LEFT_WRIST_PREFIX, [leftHand]); 759 var leftForearm = this.extractNodes(group, ModelManager.MANNEQUIN_LEFT_FOREARM_PREFIX, null); 760 var leftWrist = this.extractNodes(group, ModelManager.MANNEQUIN_LEFT_WRIST_PREFIX, null); 761 var leftForearmGroup = this.createPickableTransformGroup(ModelManager.MANNEQUIN_LEFT_ELBOW_PREFIX, [leftForearm, leftWrist, leftHandGroup]); 762 var leftArm = this.extractNodes(group, ModelManager.MANNEQUIN_LEFT_ARM_PREFIX, null); 763 var leftElbow = this.extractNodes(group, ModelManager.MANNEQUIN_LEFT_ELBOW_PREFIX, null); 764 var leftArmGroup = this.createPickableTransformGroup(ModelManager.MANNEQUIN_LEFT_SHOULDER_PREFIX, [leftArm, leftElbow, leftForearmGroup]); 765 766 // Right arm 767 var rightHand = this.extractNodes(group, ModelManager.MANNEQUIN_RIGHT_HAND_PREFIX, null); 768 var rightHandGroup = this.createPickableTransformGroup(ModelManager.MANNEQUIN_RIGHT_WRIST_PREFIX, [rightHand]); 769 var rightForearm = this.extractNodes(group, ModelManager.MANNEQUIN_RIGHT_FOREARM_PREFIX, null); 770 var rightWrist = this.extractNodes(group, ModelManager.MANNEQUIN_RIGHT_WRIST_PREFIX, null); 771 var rightForearmGroup = this.createPickableTransformGroup(ModelManager.MANNEQUIN_RIGHT_ELBOW_PREFIX, [rightForearm, rightWrist, rightHandGroup]); 772 var rightArm = this.extractNodes(group, ModelManager.MANNEQUIN_RIGHT_ARM_PREFIX, null); 773 var rightElbow = this.extractNodes(group, ModelManager.MANNEQUIN_RIGHT_ELBOW_PREFIX, null); 774 var rightArmGroup = this.createPickableTransformGroup(ModelManager.MANNEQUIN_RIGHT_SHOULDER_PREFIX, [rightArm, rightElbow, rightForearmGroup]); 775 776 // Chest 777 var chest = this.extractNodes(group, ModelManager.MANNEQUIN_CHEST_PREFIX, null); 778 var leftShoulder = this.extractNodes(group, ModelManager.MANNEQUIN_LEFT_SHOULDER_PREFIX, null); 779 var rightShoulder = this.extractNodes(group, ModelManager.MANNEQUIN_RIGHT_SHOULDER_PREFIX, null); 780 var neck = this.extractNodes(group, ModelManager.MANNEQUIN_NECK_PREFIX, null); 781 var chestGroup = this.createPickableTransformGroup(ModelManager.MANNEQUIN_ABDOMEN_CHEST_PREFIX, [chest, leftShoulder, leftArmGroup, rightShoulder, rightArmGroup, neck, headGroup]); 782 783 // Left leg 784 var leftFoot = this.extractNodes(group, ModelManager.MANNEQUIN_LEFT_FOOT_PREFIX, null); 785 var leftFootGroup = this.createPickableTransformGroup(ModelManager.MANNEQUIN_LEFT_ANKLE_PREFIX, [leftFoot]); 786 var leftLeg = this.extractNodes(group, ModelManager.MANNEQUIN_LEFT_LEG_PREFIX, null); 787 var leftAnkle = this.extractNodes(group, ModelManager.MANNEQUIN_LEFT_ANKLE_PREFIX, null); 788 var leftLegGroup = this.createPickableTransformGroup(ModelManager.MANNEQUIN_LEFT_KNEE_PREFIX, [leftLeg, leftAnkle, leftFootGroup]); 789 var leftThigh = this.extractNodes(group, ModelManager.MANNEQUIN_LEFT_THIGH_PREFIX, null); 790 var leftKnee = this.extractNodes(group, ModelManager.MANNEQUIN_LEFT_KNEE_PREFIX, null); 791 var leftThighGroup = this.createPickableTransformGroup(ModelManager.MANNEQUIN_LEFT_HIP_PREFIX, [leftThigh, leftKnee, leftLegGroup]); 792 793 // Right leg 794 var rightFoot = this.extractNodes(group, ModelManager.MANNEQUIN_RIGHT_FOOT_PREFIX, null); 795 var rightFootGroup = this.createPickableTransformGroup(ModelManager.MANNEQUIN_RIGHT_ANKLE_PREFIX, [rightFoot]); 796 var rightLeg = this.extractNodes(group, ModelManager.MANNEQUIN_RIGHT_LEG_PREFIX, null); 797 var rightAnkle = this.extractNodes(group, ModelManager.MANNEQUIN_RIGHT_ANKLE_PREFIX, null); 798 var rightLegGroup = this.createPickableTransformGroup(ModelManager.MANNEQUIN_RIGHT_KNEE_PREFIX, [rightLeg, rightAnkle, rightFootGroup]); 799 var rightThigh = this.extractNodes(group, ModelManager.MANNEQUIN_RIGHT_THIGH_PREFIX, null); 800 var rightKnee = this.extractNodes(group, ModelManager.MANNEQUIN_RIGHT_KNEE_PREFIX, null); 801 var rightThighGroup = this.createPickableTransformGroup(ModelManager.MANNEQUIN_RIGHT_HIP_PREFIX, [rightThigh, rightKnee, rightLegGroup]); 802 803 // Pelvis 804 var pelvis = this.extractNodes(group, ModelManager.MANNEQUIN_PELVIS_PREFIX, null); 805 var leftHip = this.extractNodes(group, ModelManager.MANNEQUIN_LEFT_HIP_PREFIX, null); 806 var rightHip = this.extractNodes(group, ModelManager.MANNEQUIN_RIGHT_HIP_PREFIX, null); 807 var pelvisGroup = this.createPickableTransformGroup(ModelManager.MANNEQUIN_ABDOMEN_PELVIS_PREFIX, [pelvis, leftHip, leftThighGroup, rightHip, rightThighGroup]); 808 809 var abdomen = this.extractNodes(group, ModelManager.MANNEQUIN_ABDOMEN_PREFIX, null); 810 group.addChild(abdomen); 811 group.addChild(chestGroup); 812 group.addChild(pelvisGroup); 813 } else { 814 // Reorganize rotating openings 815 this.updateSimpleDeformableModelHierarchy(group, null, ModelManager.HINGE_PREFIX, ModelManager.OPENING_ON_HINGE_PREFIX, ModelManager.WINDOW_PANE_ON_HINGE_PREFIX, ModelManager.MIRROR_ON_HINGE_PREFIX); 816 this.updateSimpleDeformableModelHierarchy(group, null, ModelManager.BALL_PREFIX, ModelManager.ARM_ON_BALL_PREFIX, null, null); 817 // Reorganize sliding openings 818 this.updateSimpleDeformableModelHierarchy(group, ModelManager.UNIQUE_RAIL_PREFIX, ModelManager.RAIL_PREFIX, ModelManager.OPENING_ON_RAIL_PREFIX, ModelManager.WINDOW_PANE_ON_RAIL_PREFIX, ModelManager.MIRROR_ON_RAIL_PREFIX); 819 // Reorganize sub hierarchies 820 var movedNodes = []; 821 while (this.updateDeformableModelSubTransformedHierarchy(group, group, [ModelManager.HINGE_PREFIX, ModelManager.BALL_PREFIX, ModelManager.RAIL_PREFIX], 822 [ModelManager.OPENING_ON_HINGE_PREFIX, ModelManager.ARM_ON_BALL_PREFIX, ModelManager.OPENING_ON_RAIL_PREFIX], movedNodes)) { 823 } 824 } 825 } 826 827 /** 828 * @param {Group3D} group 829 * @param {string} uniqueReferenceNodePrefix 830 * @param {string} referenceNodePrefix 831 * @param {string} openingPrefix 832 * @param {string} openingPanePrefix 833 * @param {string} openingMirrorPrefix 834 * @private 835 */ 836 ModelManager.prototype.updateSimpleDeformableModelHierarchy = function(group, uniqueReferenceNodePrefix, referenceNodePrefix, 837 openingPrefix, openingPanePrefix, openingMirrorPrefix) { 838 if (this.containsNode(group, openingPrefix + 1) 839 || (openingPanePrefix !== null && this.containsNode(group, openingPanePrefix + 1)) 840 || (openingMirrorPrefix !== null && this.containsNode(group, openingMirrorPrefix + 1))) { 841 if (this.containsNode(group, referenceNodePrefix + 1)) { 842 // Reorganize openings with multiple reference nodes 843 var i = 1; 844 do { 845 var referenceNode = this.extractNodes(group, referenceNodePrefix + i, null); 846 var opening = this.extractNodes(group, openingPrefix + i, null); 847 var openingPane = openingPanePrefix !== null ? this.extractNodes(group, openingPanePrefix + i, null) : null; 848 var openingMirror = openingMirrorPrefix !== null ? this.extractNodes(group, openingMirrorPrefix + i, null) : null; 849 var openingGroup = this.createPickableTransformGroup(referenceNodePrefix + i, [opening, openingPane, openingMirror]); 850 group.addChild(referenceNode); 851 group.addChild(openingGroup); 852 i++; 853 } while (this.containsNode(group, referenceNodePrefix + i) 854 && (this.containsNode(group, openingPrefix + i) 855 || (openingPanePrefix !== null && this.containsNode(group, openingPanePrefix + i)) 856 || (openingMirrorPrefix !== null && this.containsNode(group, openingMirrorPrefix + i)))); 857 } else if (uniqueReferenceNodePrefix !== null 858 && this.containsNode(group, uniqueReferenceNodePrefix)) { 859 // Reorganize openings with a unique reference node 860 var referenceNode = this.extractNodes(group, uniqueReferenceNodePrefix, null); 861 group.addChild(referenceNode); 862 var i = 1; 863 do { 864 var opening = this.extractNodes(group, openingPrefix + i, null); 865 var openingPane = this.extractNodes(group, openingPanePrefix + i, null); 866 var openingMirror = this.extractNodes(group, openingMirrorPrefix + i, null); 867 group.addChild(this.createPickableTransformGroup(referenceNodePrefix + i, [opening, openingPane, openingMirror])); 868 i++; 869 } while (this.containsNode(group, openingPrefix + i) 870 || this.containsNode(group, openingPanePrefix + i) 871 || this.containsNode(group, openingMirrorPrefix + i)); 872 } 873 } 874 } 875 876 /** 877 * Returns <code>true</code> if the given <code>node</code> or a node in its hierarchy 878 * contains a node which name, stored in user data, starts with <code>prefix</code>. 879 * @param {Node3D} node a node 880 * @param {string} prefix a string 881 */ 882 ModelManager.prototype.containsNode = function(node, prefix) { 883 var name = node.getName(); 884 if (name !== null 885 && name.indexOf(prefix) === 0) { 886 return true; 887 } 888 if (node instanceof Group3D) { 889 for (var i = node.getChildren().length - 1; i >= 0; i--) { 890 if (this.containsNode(node.getChild(i), prefix)) { 891 return true; 892 } 893 } 894 } 895 return false; 896 } 897 898 /** 899 * Searches among the given <code>node</code> and its children the nodes which name, stored in user data, starts with <code>name</code>, 900 * then returns a group containing the found nodes. 901 * @param {Node3D} node 902 * @param {string} name 903 * @param {Group3D} destinationGroup 904 * @private 905 */ 906 ModelManager.prototype.extractNodes = function(node, name, destinationGroup) { 907 if (node.getName() !== null 908 && node.getName().indexOf(name) === 0) { 909 node.getParent().removeChild(node); 910 if (destinationGroup === null) { 911 destinationGroup = new Group3D(); 912 } 913 destinationGroup.addChild(node); 914 } 915 if (node instanceof Group3D) { 916 // Enumerate children 917 for (var i = node.getChildren().length - 1; i >= 0; i--) { 918 destinationGroup = this.extractNodes(node.getChild(i), name, destinationGroup); 919 } 920 } 921 return destinationGroup; 922 } 923 924 /** 925 * Returns a pickable group with its <code>children</code> and the given reference node as user data. 926 * @param {string} deformableGroupPrefix 927 * @param {Array} children 928 * @private 929 */ 930 ModelManager.prototype.createPickableTransformGroup = function(deformableGroupPrefix, children) { 931 var transformGroup = new TransformGroup3D(); 932 transformGroup.setCapability(TransformGroup3D.ALLOW_TRANSFORM_WRITE); 933 transformGroup.setName(deformableGroupPrefix + ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX); 934 // Store the node around which objects should turn 935 for (var i = 0; i < children.length; i++) { 936 if (children [i] !== null) { 937 transformGroup.addChild(children [i]); 938 } 939 } 940 return transformGroup; 941 } 942 943 /** 944 * Updates the first node found in the given <code>group</code> which specifies a transformation 945 * which should depend on another transformed node. 946 * @param {Group3D} group 947 * @param {Node3D} node 948 * @param {Array} referenceNodePrefixes 949 * @param {Array} subTransformationOpeningPrefixes 950 * @param {Array} movedNodes 951 * @return {boolean} <code>true</code> if such a node was found and attached to another transformation 952 * @private 953 */ 954 ModelManager.prototype.updateDeformableModelSubTransformedHierarchy = function(group, node, referenceNodePrefixes, subTransformationOpeningPrefixes, 955 movedNodes) { 956 if (group !== node 957 && movedNodes.indexOf(node) < 0) { 958 var name = node.getName(); 959 if (name !== null) { 960 for (var i = 0; i < referenceNodePrefixes.length; i++) { 961 var prefix = referenceNodePrefixes [i]; 962 if (name.indexOf(prefix) === 0) { 963 var index = name.indexOf(ModelManager.SUB_TRANSFORMATION_SEPARATOR); 964 if (index > 0) { 965 for (var j = 0; j < subTransformationOpeningPrefixes.length; j++) { 966 var subTransformationIndex = name.indexOf(subTransformationOpeningPrefixes [j], index + ModelManager.SUB_TRANSFORMATION_SEPARATOR.length); 967 if (subTransformationIndex >= 0) { 968 if (movedNodes.indexOf(node) < 0) { 969 movedNodes.push(node); 970 } 971 var referenceNode = node.getParent(); 972 var parent = referenceNode.getParent(); 973 var nodeIndex = parent.getChildren().indexOf(referenceNode); 974 var pickableGroup = parent.getChild(++nodeIndex); 975 while (!(pickableGroup instanceof TransformGroup3D)) { 976 pickableGroup = parent.getChild(++nodeIndex); 977 } 978 var lastDigitIndex = subTransformationIndex + subTransformationOpeningPrefixes [j].length; 979 while (lastDigitIndex < name.length && name.charAt(lastDigitIndex) >= '0' && name.charAt(lastDigitIndex) <= '9') { 980 lastDigitIndex++; 981 } 982 // Remove node and its sibling group and attach it to parent transformation 983 if (this.attachNodesToPickableTransformGroup(group, 984 referenceNodePrefixes [j] + name.substring(subTransformationIndex + subTransformationOpeningPrefixes [j].length, lastDigitIndex), 985 [referenceNode, pickableGroup])) { 986 return true; 987 } 988 } 989 } 990 } 991 } 992 } 993 } 994 } 995 if (node instanceof Group3D) { 996 var children = node.getChildren(); 997 for (var i = children.length - 1; i >= 0; i--) { 998 if (this.updateDeformableModelSubTransformedHierarchy(group, children [i], referenceNodePrefixes, subTransformationOpeningPrefixes, movedNodes)) { 999 return true; 1000 } 1001 } 1002 } 1003 return false; 1004 } 1005 1006 /** 1007 * @param {Node3D} node the root of a model 1008 * @param {string} groupPrefix 1009 * @param {Array} movedNodes 1010 * @return {boolean} 1011 * @private 1012 */ 1013 ModelManager.prototype.attachNodesToPickableTransformGroup = function(node, groupPrefix, movedNodes) { 1014 if (node instanceof TransformGroup3D 1015 && (groupPrefix + ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX) == node.getName()) { 1016 var group = node; 1017 for (var i = 0; i < movedNodes.length; i++) { 1018 var movedNode = movedNodes [i]; 1019 movedNode.getParent().removeChild(movedNode); 1020 group.addChild(movedNode); 1021 } 1022 return true; 1023 } else if (node instanceof Group3D) { 1024 var children = node.getChildren(); 1025 for (var i = 0; i < children.length; i++) { 1026 if (this.attachNodesToPickableTransformGroup(children [i], groupPrefix, movedNodes)) { 1027 return true; 1028 } 1029 } 1030 } 1031 return false; 1032 } 1033 1034 /** 1035 * Returns <code>true</code> if the given <code>node</code> or its children contains at least a deformable group. 1036 * @param {Node3D} node the root of a model 1037 * @return {boolean} 1038 */ 1039 ModelManager.prototype.containsDeformableNode = function(node) { 1040 if (node instanceof TransformGroup3D 1041 && node.getName() !== null 1042 && node.getName().indexOf(ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX) >= 0 1043 && node.getName().indexOf(ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX) === (node.getName().length - ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX.length)) { 1044 return true; 1045 } else if (node instanceof Group3D) { 1046 var children = node.getChildren(); 1047 for (var i = 0; i < children.length; i++) { 1048 if (this.containsDeformableNode(children [i])) { 1049 return true; 1050 } 1051 } 1052 } 1053 return false; 1054 } 1055 1056 /** 1057 * Returns <code>true</code> if the given <code>node</code> or its children contains is a deformed transformed group. 1058 * @param {Node3D} node a node 1059 * @return {boolean} 1060 * @private 1061 */ 1062 ModelManager.prototype.isDeformed = function(node) { 1063 if (node instanceof TransformGroup3D 1064 && node.getName() !== null 1065 && node.getName().indexOf(ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX) >= 0 1066 && node.getName().indexOf(ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX) === (node.getName().length - ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX.length)) { 1067 var transform = mat4.create(); 1068 node.getTransform(transform); 1069 return !TransformGroup3D.isIdentity(transform); 1070 } else if (node instanceof Group3D) { 1071 var children = node.getChildren(); 1072 for (var i = 0; i < children.length; i++) { 1073 if (this.isDeformed(children [i])) { 1074 return true; 1075 } 1076 } 1077 } 1078 return false; 1079 } 1080 1081 /** 1082 * Returns the materials used by the children shapes of the given <code>node</code>, 1083 * attributing their <code>creator</code> to them. 1084 * @param {Node3D} node 1085 * @param {boolean} ignoreEdgeColorMaterial 1086 * @param {string} [creator] 1087 */ 1088 ModelManager.prototype.getMaterials = function(node, ignoreEdgeColorMaterial, creator) { 1089 if (creator === undefined) { 1090 if (ignoreEdgeColorMaterial === undefined) { 1091 ignoreEdgeColorMaterial = false; 1092 creator = null; 1093 } else { 1094 creator = ignoreEdgeColorMaterial; 1095 } 1096 } 1097 1098 var appearances = []; 1099 this.searchAppearances(node, ignoreEdgeColorMaterial, appearances); 1100 var materials = []; 1101 for (var i = 0; i < appearances.length; i++) { 1102 var appearance = appearances[i]; 1103 var color = null; 1104 var shininess = null; 1105 var diffuseColor = appearance.getDiffuseColor(); 1106 if (diffuseColor != null) { 1107 color = 0xFF000000 1108 | (Math.round(diffuseColor[0] * 255) << 16) 1109 | (Math.round(diffuseColor[1] * 255) << 8) 1110 | Math.round(diffuseColor[2] * 255); 1111 shininess = appearance.getShininess() != null ? appearance.getShininess() / 128 : null; 1112 } 1113 var appearanceTexture = appearance.getTextureImage(); 1114 var texture = null; 1115 if (appearanceTexture != null) { 1116 var textureImageUrl = appearanceTexture.url; 1117 if (textureImageUrl != null) { 1118 var textureImage = new SimpleURLContent(textureImageUrl); 1119 var textureImageName = textureImageUrl.substring(textureImageUrl.lastIndexOf('/') + 1); 1120 var lastPoint = textureImageName.lastIndexOf('.'); 1121 if (lastPoint !== -1) { 1122 textureImageName = textureImageName.substring(0, lastPoint); 1123 } 1124 texture = new HomeTexture( 1125 new CatalogTexture(null, textureImageName, textureImage, -1, -1, creator)); 1126 } 1127 } 1128 var materialName = appearance.getName(); 1129 if (materialName === undefined) { 1130 materialName = null; 1131 } 1132 var homeMaterial = new HomeMaterial(materialName, color, texture, shininess); 1133 for (var j = 0; j < materials.length; j++) { 1134 if (materials [j].getName() == homeMaterial.getName()) { 1135 // Don't add twice materials with the same name 1136 homeMaterial = null; 1137 break; 1138 } 1139 } 1140 if (homeMaterial != null) { 1141 materials.push(homeMaterial); 1142 } 1143 } 1144 materials.sort(function (m1, m2) { 1145 var name1 = m1.getName(); 1146 var name2 = m2.getName(); 1147 if (name1 != null) { 1148 if (name2 != null) { 1149 return name1.localeCompare(name2); 1150 } else { 1151 return 1; 1152 } 1153 } else if (name2 != null) { 1154 return -1; 1155 } else { 1156 return 0; 1157 } 1158 }); 1159 return materials; 1160 } 1161 1162 /** 1163 * @param {Node3D} node 1164 * @param {boolean} ignoreEdgeColorMaterial 1165 * @param {Array} appearances 1166 * @private 1167 */ 1168 ModelManager.prototype.searchAppearances = function(node, ignoreEdgeColorMaterial, appearances) { 1169 if (node instanceof Group3D) { 1170 var children = node.getChildren(); 1171 for (var i = 0; i < children.length; i++) { 1172 this.searchAppearances(children [i], ignoreEdgeColorMaterial, appearances); 1173 } 1174 } else if (node instanceof Link3D) { 1175 this.searchAppearances(node.getSharedGroup(), ignoreEdgeColorMaterial, appearances); 1176 } else if (node instanceof Shape3D) { 1177 var appearance = node.getAppearance(); 1178 if (appearance !== null 1179 && (!ignoreEdgeColorMaterial 1180 || !(appearance.getName().indexOf(ModelManager.EDGE_COLOR_MATERIAL_PREFIX) === 0)) 1181 && appearances.indexOf(appearance) === -1) { 1182 appearances.push(appearance); 1183 } 1184 } 1185 } 1186 1187 /** 1188 * Replaces multiple shared shapes of the given <code>node</code> with one shape with transformed geometries. 1189 * @param {BranchGroup3D} modelRoot 1190 * @private 1191 */ 1192 ModelManager.prototype.replaceMultipleSharedShapes = function(modelRoot) { 1193 var sharedShapes = []; 1194 this.searchSharedShapes(modelRoot, sharedShapes, false); 1195 for (var i = 0; i < sharedShapes.length; i++) { 1196 if (sharedShapes [i].value > 1) { 1197 var transformations = []; 1198 var shape = sharedShapes [i].key; 1199 this.searchShapeTransformations(modelRoot, shape, transformations, mat4.create()); 1200 // Replace shared shape by a unique shape with transformed geometries 1201 var newShape = shape.clone(); 1202 var geometries = newShape.getGeometries(); 1203 for (var j = 0; j < geometries.length; j++) { 1204 var newGeometry = this.getTransformedGeometry(geometries [j], transformations); 1205 if (newGeometry === null) { 1206 return; 1207 } 1208 newShape.setGeometry(newGeometry, j); 1209 } 1210 this.removeSharedShape(modelRoot, shape); 1211 modelRoot.addChild(newShape); 1212 } 1213 } 1214 } 1215 1216 /** 1217 * Searches all the shapes which are shared among the children of the given <code>node</code>. 1218 * @param {Node3D} node a node 1219 * @param {Array} sharedShapes 1220 * @param {boolean} childOfSharedGroup 1221 * @private 1222 */ 1223 ModelManager.prototype.searchSharedShapes = function(node, sharedShapes, childOfSharedGroup) { 1224 if (node instanceof Group3D) { 1225 var children = node.getChildren(); 1226 for (var i = 0; i < children.length; i++) { 1227 this.searchSharedShapes(children [i], sharedShapes, childOfSharedGroup); 1228 } 1229 } else if (node instanceof Link3D) { 1230 this.searchSharedShapes(node.getSharedGroup(), sharedShapes, true); 1231 } else if (node instanceof Shape3D) { 1232 if (childOfSharedGroup) { 1233 for (var i = 0; i < sharedShapes.length; i++) { 1234 if (sharedShapes [i].key === node) { 1235 sharedShapes [i].value++; 1236 return; 1237 } 1238 } 1239 sharedShapes.push({key : node, value : 1}); 1240 } 1241 } 1242 } 1243 1244 /** 1245 * Searches all the transformations applied to a shared <code>shape</code> child of the given <b>node</b>. 1246 * @param {Node3D} node a node 1247 * @param {Shape3D} shape 1248 * @param {mat4[]} transformations 1249 * @param {mat4} parentTransformations 1250 */ 1251 ModelManager.prototype.searchShapeTransformations = function(node, shape, transformations, parentTransformations) { 1252 if (node instanceof Group3D) { 1253 if (!(node instanceof TransformGroup3D) 1254 || !this.isDeformed(node)) { 1255 if (node instanceof TransformGroup3D) { 1256 parentTransformations = mat4.clone(parentTransformations); 1257 var transform = mat4.create(); 1258 node.getTransform(transform); 1259 mat4.mul(parentTransformations, parentTransformations, transform); 1260 } 1261 var children = node.getChildren(); 1262 for (var i = 0; i < children.length; i++) { 1263 this.searchShapeTransformations(children [i], shape, transformations, parentTransformations); 1264 } 1265 } 1266 } else if (node instanceof Link3D) { 1267 this.searchShapeTransformations(node.getSharedGroup(), shape, transformations, parentTransformations); 1268 } else if (node === shape) { 1269 transformations.push(parentTransformations); 1270 } 1271 } 1272 1273 /** 1274 * Returns a new geometry where coordinates are transformed with the given transformations. 1275 * @param {IndexedGeometryArray3D} geometry 1276 * @param {mat4[]} transformations 1277 * @return {IndexedGeometryArray3D} 1278 */ 1279 ModelManager.prototype.getTransformedGeometry = function(geometry, transformations) { 1280 var offsetIndex = 0; 1281 var offsetVertex = 0; 1282 var newVertexIndices = new Array(transformations.length * geometry.vertexIndices.length); 1283 for (var i = 0; i < transformations.length; i++) { 1284 for (var j = 0, n = geometry.vertexIndices.length; j < n; j++) { 1285 newVertexIndices [offsetIndex + j] = offsetVertex + geometry.vertexIndices [j]; 1286 } 1287 offsetIndex += geometry.vertexIndices.length; 1288 offsetVertex += geometry.vertices.length; 1289 } 1290 1291 var newTextureCoordinateIndices = new Array(transformations.length * geometry.textureCoordinateIndices.length); 1292 offsetIndex = 0; 1293 for (var i = 0; i < transformations.length; i++) { 1294 for (var j = 0, n = geometry.textureCoordinateIndices.length; j < n; j++) { 1295 newTextureCoordinateIndices [offsetIndex + j] = geometry.textureCoordinateIndices [j]; 1296 } 1297 offsetIndex += geometry.textureCoordinateIndices.length; 1298 } 1299 1300 offsetVertex = 0; 1301 var newVertices = new Array(transformations.length * geometry.vertices.length); 1302 for (var i = 0; i < transformations.length; i++) { 1303 for (var j = 0, n = geometry.vertices.length; j < n; j++) { 1304 var vertex = vec3.clone(geometry.vertices [j]); 1305 vec3.transformMat4(vertex, vertex, transformations [i]); 1306 newVertices [offsetVertex + j] = vertex; 1307 } 1308 offsetVertex += geometry.vertices.length; 1309 } 1310 1311 if (geometry instanceof IndexedLineArray3D) { 1312 return new IndexedLineArray3D(newVertices, newVertexIndices, geometry.textureCoordinates, newTextureCoordinateIndices); 1313 } else if (geometry instanceof IndexedTriangleArray3D) { 1314 var newNormalIndices = new Array(transformations.length * geometry.normalIndices.length); 1315 offsetIndex = 0; 1316 var offsetNormal = 0; 1317 for (var i = 0; i < transformations.length; i++) { 1318 for (var j = 0, n = geometry.normalIndices.length; j < n; j++) { 1319 newNormalIndices [offsetIndex + j] = offsetNormal + geometry.normalIndices [j]; 1320 } 1321 offsetIndex += geometry.normalIndices.length; 1322 offsetNormal += geometry.normals.length; 1323 } 1324 1325 var offsetNormal = 0; 1326 var newNormals = new Array(transformations.length * geometry.normals.length); 1327 for (var i = 0; i < transformations.length; i++) { 1328 for (var j = 0, n = geometry.normals.length; j < n; j++) { 1329 var normal = vec3.clone(geometry.normals [j]); 1330 vec3.transformMat4(normal, normal, transformations [i]); 1331 vec3.normalize(normal, normal); 1332 newNormals [offsetNormal + j] = normal; 1333 } 1334 offsetNormal += geometry.normals.length; 1335 } 1336 1337 return new IndexedTriangleArray3D(newVertices, newVertexIndices, geometry.textureCoordinates, newTextureCoordinateIndices, newNormals, newNormalIndices); 1338 } else { 1339 return null; 1340 } 1341 } 1342 1343 /** 1344 * Removes the shared shape from the children of the given <code>node</code>. 1345 * @param {Node3D} node a node 1346 * @param {Shape3D} shape 1347 */ 1348 ModelManager.prototype.removeSharedShape = function(node, shape) { 1349 if (node instanceof Group3D) { 1350 if (!(node instanceof TransformGroup3D) 1351 || !this.isDeformed(node)) { 1352 var children = node.getChildren(); 1353 for (var i = children.length - 1; i >= 0; i--) { 1354 this.removeSharedShape(children [i], shape); 1355 } 1356 if (children.length === 0 1357 && node.getParent() instanceof Group3D) { 1358 node.getParent().removeChild(node); 1359 } 1360 } 1361 } else if (node instanceof Link3D) { 1362 var sharedGroup = node.getSharedGroup(); 1363 this.removeSharedShape(sharedGroup, shape); 1364 if (sharedGroup.children.length == 0) { 1365 node.getParent().removeChild(node); 1366 } 1367 } else if (node === shape) { 1368 node.getParent().removeChild(node); 1369 } 1370 } 1371 1372 /** 1373 * Returns the shape matching the given cut out shape if not <code>null</code> 1374 * or the 2D area of the 3D shapes children of the <code>node</code> 1375 * projected on its front side. The returned area is normalized in a 1 unit square 1376 * centered at the origin. 1377 */ 1378 ModelManager.prototype.getFrontArea = function(cutOutShape, node) { 1379 var frontArea; 1380 if (cutOutShape !== null) { 1381 frontArea = new java.awt.geom.Area(this.getShape(cutOutShape)); 1382 frontArea.transform(java.awt.geom.AffineTransform.getScaleInstance(1, -1)); 1383 frontArea.transform(java.awt.geom.AffineTransform.getTranslateInstance(-0.5, 0.5)); 1384 } else { 1385 var vertexCount = this.getVertexCount(node); 1386 if (vertexCount < 1000000) { 1387 var frontAreaWithHoles = new java.awt.geom.Area(); 1388 this.computeBottomOrFrontArea(node, frontAreaWithHoles, mat4.create(), false, false); 1389 frontArea = new java.awt.geom.Area(); 1390 var currentPathPoints = []; 1391 var previousRoomPoint = null; 1392 for (var it = frontAreaWithHoles.getPathIterator(null, 1); !it.isDone(); it.next()) { 1393 var areaPoint = [0, 0]; 1394 switch (it.currentSegment(areaPoint)) { 1395 case java.awt.geom.PathIterator.SEG_MOVETO : 1396 case java.awt.geom.PathIterator.SEG_LINETO : 1397 if (previousRoomPoint === null 1398 || areaPoint[0] !== previousRoomPoint[0] 1399 || areaPoint[1] !== previousRoomPoint[1]) { 1400 currentPathPoints.push(areaPoint); 1401 } 1402 previousRoomPoint = areaPoint; 1403 break; 1404 case java.awt.geom.PathIterator.SEG_CLOSE : 1405 if (currentPathPoints[0][0] === previousRoomPoint[0] 1406 && currentPathPoints[0][1] === previousRoomPoint[1]) { 1407 currentPathPoints.splice(currentPathPoints.length - 1, 1); 1408 } 1409 if (currentPathPoints.length > 2) { 1410 var pathPoints = currentPathPoints.slice(0); 1411 var subRoom = new Room(pathPoints); 1412 if (subRoom.getArea() > 0) { 1413 if (!subRoom.isClockwise()) { 1414 var currentPath = new java.awt.geom.GeneralPath(); 1415 currentPath.moveTo(pathPoints[0][0], pathPoints[0][1]); 1416 for (var i = 1; i < pathPoints.length; i++) { 1417 currentPath.lineTo(pathPoints[i][0], pathPoints[i][1]); 1418 } 1419 currentPath.closePath(); 1420 frontArea.add(new java.awt.geom.Area(currentPath)); 1421 } 1422 } 1423 } 1424 currentPathPoints.length = 0; 1425 previousRoomPoint = null; 1426 break; 1427 } 1428 } 1429 var bounds = frontAreaWithHoles.getBounds2D(); 1430 frontArea.transform(java.awt.geom.AffineTransform.getTranslateInstance(-bounds.getCenterX(), -bounds.getCenterY())); 1431 frontArea.transform(java.awt.geom.AffineTransform.getScaleInstance(1 / bounds.getWidth(), 1 / bounds.getHeight())); 1432 } 1433 else { 1434 frontArea = new java.awt.geom.Area(new java.awt.geom.Rectangle2D.Float(-0.5, -0.5, 1, 1)); 1435 } 1436 } 1437 return frontArea; 1438 } 1439 1440 /** 1441 * Returns the 2D area of the 3D shapes children of the given scene 3D <code>node</code> 1442 * projected on the floor (plan y = 0), or of the given staircase if <code>node</code> is an 1443 * instance of <code>HomePieceOfFurniture</code>. 1444 * @param {Node3D|HomePieceOfFurniture} node 1445 * @return {Area} 1446 */ 1447 ModelManager.prototype.getAreaOnFloor = function(node) { 1448 if (node instanceof Node3D) { 1449 var modelAreaOnFloor; 1450 var vertexCount = this.getVertexCount(node); 1451 if (vertexCount < 10000) { 1452 modelAreaOnFloor = new java.awt.geom.Area(); 1453 this.computeBottomOrFrontArea(node, modelAreaOnFloor, mat4.create(), true, true); 1454 } else { 1455 var vertices = []; 1456 this.computeVerticesOnFloor(node, vertices, mat4.create()); 1457 if (vertices.length > 0) { 1458 var surroundingPolygon = this.getSurroundingPolygon(vertices.slice(0)); 1459 var generalPath = new java.awt.geom.GeneralPath(java.awt.geom.Path2D.WIND_NON_ZERO, surroundingPolygon.length); 1460 generalPath.moveTo(surroundingPolygon[0][0], surroundingPolygon[0][1]); 1461 for (var i = 0; i < surroundingPolygon.length; i++) { 1462 generalPath.lineTo(surroundingPolygon[i][0], surroundingPolygon[i][1]); 1463 } 1464 generalPath.closePath(); 1465 modelAreaOnFloor = new java.awt.geom.Area(generalPath); 1466 } else { 1467 modelAreaOnFloor = new java.awt.geom.Area(); 1468 } 1469 } 1470 return modelAreaOnFloor; 1471 } else { 1472 var staircase = node; 1473 if (staircase.getStaircaseCutOutShape() === null) { 1474 throw new IllegalArgumentException("No cut out shape associated to piece"); 1475 } 1476 var shape = this.getShape(staircase.getStaircaseCutOutShape()); 1477 var staircaseArea = new java.awt.geom.Area(shape); 1478 if (staircase.isModelMirrored()) { 1479 staircaseArea = this.getMirroredArea(staircaseArea); 1480 } 1481 var staircaseTransform = java.awt.geom.AffineTransform.getTranslateInstance( 1482 staircase.getX() - staircase.getWidth() / 2, 1483 staircase.getY() - staircase.getDepth() / 2); 1484 staircaseTransform.concatenate(java.awt.geom.AffineTransform.getRotateInstance(staircase.getAngle(), 1485 staircase.getWidth() / 2, staircase.getDepth() / 2)); 1486 staircaseTransform.concatenate(java.awt.geom.AffineTransform.getScaleInstance(staircase.getWidth(), staircase.getDepth())); 1487 staircaseArea.transform(staircaseTransform); 1488 return staircaseArea; 1489 } 1490 } 1491 1492 /** 1493 * Returns the total count of vertices in all geometries. 1494 * @param {Node3D} node 1495 * @return {number} 1496 * @private 1497 */ 1498 ModelManager.prototype.getVertexCount = function(node) { 1499 var count = 0; 1500 if (node instanceof Group3D) { 1501 var children = node.getChildren(); 1502 for (var i = 0; i < children.length; i++) { 1503 count += this.getVertexCount(children [i]); 1504 } 1505 } else if (node instanceof Link3D) { 1506 count = this.getVertexCount(node.getSharedGroup()); 1507 } else if (node instanceof Shape3D) { 1508 var appearance = node.getAppearance(); 1509 if (appearance.isVisible()) { 1510 var geometries = node.getGeometries(); 1511 for (var i = 0, n = geometries.length; i < n; i++) { 1512 var geometry = geometries[i]; 1513 count += geometry.vertices.length; 1514 } 1515 } 1516 } 1517 return count; 1518 } 1519 1520 /** 1521 * Computes the 2D area on floor or on front side of the 3D shapes children of <code>node</code>. 1522 * @param {Node3D} node 1523 * @param {Area} nodeArea 1524 * @param {mat4} parentTransformations 1525 * @param {boolean} ignoreTransparentShapes 1526 * @param {boolean} bottom 1527 * @private 1528 */ 1529 ModelManager.prototype.computeBottomOrFrontArea = function(node, nodeArea, parentTransformations, ignoreTransparentShapes, bottom) { 1530 if (node instanceof Group3D) { 1531 if (node instanceof TransformGroup3D) { 1532 parentTransformations = mat4.clone(parentTransformations); 1533 var transform = mat4.create(); 1534 node.getTransform(transform); 1535 mat4.mul(parentTransformations, parentTransformations, transform); 1536 } 1537 var children = node.getChildren(); 1538 for (var i = 0; i < children.length; i++) { 1539 this.computeBottomOrFrontArea(children [i], nodeArea, parentTransformations, ignoreTransparentShapes, bottom); 1540 } 1541 } else if (node instanceof Link3D) { 1542 this.computeBottomOrFrontArea(node.getSharedGroup(), nodeArea, parentTransformations, ignoreTransparentShapes, bottom); 1543 } else if (node instanceof Shape3D) { 1544 var appearance = node.getAppearance(); 1545 if (appearance.isVisible() 1546 && (!ignoreTransparentShapes 1547 || appearance.getTransparency() === undefined 1548 || appearance.getTransparency() < 1)) { 1549 var geometries = node.getGeometries(); 1550 for (var i = 0, n = geometries.length; i < n; i++) { 1551 var geometry = geometries[i]; 1552 this.computeBottomOrFrontGeometryArea(geometry, nodeArea, parentTransformations, bottom); 1553 } 1554 } 1555 } 1556 } 1557 1558 /** 1559 * Computes the bottom area of a 3D geometry if <code>bottom</code> is <code>true</code>, 1560 * and the front area if not. 1561 * @param {IndexedGeometryArray3D} geometryArray 1562 * @param {Area} nodeArea 1563 * @param {mat4} parentTransformations 1564 * @param {boolean} bottom 1565 * @private 1566 */ 1567 ModelManager.prototype.computeBottomOrFrontGeometryArea = function(geometryArray, nodeArea, parentTransformations, bottom) { 1568 if (geometryArray instanceof IndexedTriangleArray3D) { 1569 var vertexCount = geometryArray.vertices.length; 1570 var vertices = new Array(vertexCount * 2); 1571 var vertex = vec3.create(); 1572 for (var index = 0, i = 0; index < vertices.length; i++) { 1573 vec3.copy(vertex, geometryArray.vertices [i]); 1574 vec3.transformMat4(vertex, vertex, parentTransformations); 1575 vertices[index++] = vertex[0]; 1576 if (bottom) { 1577 vertices[index++] = vertex[2]; 1578 } else { 1579 vertices[index++] = vertex[1]; 1580 } 1581 } 1582 1583 geometryPath = new java.awt.geom.GeneralPath(java.awt.geom.Path2D.WIND_NON_ZERO, 1000); 1584 for (var i = 0, triangleIndex = 0, n = geometryArray.vertexIndices.length; i < n; i += 3) { 1585 this.addTriangleToPath(geometryArray, geometryArray.vertexIndices [i], geometryArray.vertexIndices [i + 1], geometryArray.vertexIndices [i + 2], vertices, 1586 geometryPath, triangleIndex++, nodeArea); 1587 } 1588 nodeArea.add(new java.awt.geom.Area(geometryPath)); 1589 } 1590 } 1591 1592 /** 1593 * Adds to <code>nodePath</code> the triangle joining vertices at 1594 * vertexIndex1, vertexIndex2, vertexIndex3 indices, 1595 * only if the triangle has a positive orientation. 1596 * @param {javax.media.j3d.GeometryArray} geometryArray 1597 * @param {number} vertexIndex1 1598 * @param {number} vertexIndex2 1599 * @param {number} vertexIndex3 1600 * @param {Array} vertices 1601 * @param {GeneralPath} geometryPath 1602 * @param {number} triangleIndex 1603 * @param {Area} nodeArea 1604 * @private 1605 */ 1606 ModelManager.prototype.addTriangleToPath = function(geometryArray, vertexIndex1, vertexIndex2, vertexIndex3, vertices, geometryPath, triangleIndex, nodeArea) { 1607 var xVertex1 = vertices[2 * vertexIndex1]; 1608 var yVertex1 = vertices[2 * vertexIndex1 + 1]; 1609 var xVertex2 = vertices[2 * vertexIndex2]; 1610 var yVertex2 = vertices[2 * vertexIndex2 + 1]; 1611 var xVertex3 = vertices[2 * vertexIndex3]; 1612 var yVertex3 = vertices[2 * vertexIndex3 + 1]; 1613 if ((xVertex2 - xVertex1) * (yVertex3 - yVertex2) - (yVertex2 - yVertex1) * (xVertex3 - xVertex2) > 0) { 1614 if (triangleIndex > 0 && triangleIndex % 1000 === 0) { 1615 nodeArea.add(new java.awt.geom.Area(geometryPath)); 1616 geometryPath.reset(); 1617 } 1618 geometryPath.moveTo(xVertex1, yVertex1); 1619 geometryPath.lineTo(xVertex2, yVertex2); 1620 geometryPath.lineTo(xVertex3, yVertex3); 1621 geometryPath.closePath(); 1622 } 1623 } 1624 1625 /** 1626 * Computes the vertices coordinates projected on floor of the 3D shapes children of <code>node</code>. 1627 * @param {Node3D} node 1628 * @param {Array} vertices 1629 * @param {mat4} parentTransformations 1630 * @private 1631 */ 1632 ModelManager.prototype.computeVerticesOnFloor = function (node, vertices, parentTransformations) { 1633 if (node instanceof Group3D) { 1634 if (node instanceof TransformGroup3D) { 1635 parentTransformations = mat4.clone(parentTransformations); 1636 var transform = mat4.create(); 1637 node.getTransform(transform); 1638 mat4.mul(parentTransformations, parentTransformations, transform); 1639 } 1640 var children = node.getChildren(); 1641 for (var i = 0; i < children.length; i++) { 1642 this.computeVerticesOnFloor(children [i], vertices, parentTransformations); 1643 } 1644 } else if (node instanceof Link3D) { 1645 this.computeVerticesOnFloor(node.getSharedGroup(), vertices, parentTransformations); 1646 } else if (node instanceof Shape3D) { 1647 var appearance = node.getAppearance(); 1648 if (appearance.isVisible() 1649 && (appearance.getTransparency() === undefined 1650 || appearance.getTransparency() < 1)) { 1651 var geometries = node.getGeometries(); 1652 for (var i = 0, n = geometries.length; i < n; i++) { 1653 var geometryArray = geometries[i]; 1654 var vertexCount = geometryArray.vertices.length; 1655 var vertex = vec3.create(); 1656 for (var index = 0, j = 0; index < vertexCount; j++, index++) { 1657 vec3.copy(vertex, geometryArray.vertices [j]); 1658 vec3.transformMat4(vertex, vertex, parentTransformations); 1659 vertices.push([vertex[0], vertex[2]]); 1660 } 1661 } 1662 } 1663 } 1664 } 1665 1666 /** 1667 * Returns the convex polygon that surrounds the given <code>vertices</code>. 1668 * From Andrew's monotone chain 2D convex hull algorithm described at 1669 * http://softsurfer.com/Archive/algorithm%5F0109/algorithm%5F0109.htm 1670 * @param {Array} vertices 1671 * @return {Array} 1672 * @private 1673 */ 1674 ModelManager.prototype.getSurroundingPolygon = function(vertices) { 1675 vertices.sort(function (vertex1, vertex2) { 1676 var testedValue; 1677 if (vertex1[0] === vertex2[0]) { 1678 testedValue = vertex2[1] - vertex1[1]; 1679 } else { 1680 testedValue = vertex2[0] - vertex1[0]; 1681 } 1682 if (testedValue > 0) { 1683 return 1; 1684 } else if (testedValue < 0) { 1685 return -1; 1686 } else { 1687 return 0; 1688 } 1689 }); 1690 var polygon = new Array(vertices.length); 1691 var bottom = 0; 1692 var top = -1; 1693 var i; 1694 1695 var minMin = 0; 1696 var minMax; 1697 var xmin = vertices[0][0]; 1698 for (i = 1; i < vertices.length; i++) { 1699 if (vertices[i][0] !== xmin) { 1700 break; 1701 } 1702 } 1703 minMax = i - 1; 1704 if (minMax === vertices.length - 1) { 1705 polygon[++top] = vertices[minMin]; 1706 if (vertices[minMax][1] !== vertices[minMin][1]) { 1707 polygon[++top] = vertices[minMax]; 1708 } 1709 polygon[++top] = vertices[minMin]; 1710 var surroundingPolygon = new Array(top + 1); 1711 System.arraycopy(polygon, 0, surroundingPolygon, 0, surroundingPolygon.length); 1712 return surroundingPolygon; 1713 } 1714 1715 var maxMin; 1716 var maxMax = vertices.length - 1; 1717 var xMax = vertices[vertices.length - 1][0]; 1718 for (i = vertices.length - 2; i >= 0; i--) { 1719 if (vertices[i][0] !== xMax) { 1720 break; 1721 } 1722 } 1723 maxMin = i + 1; 1724 1725 polygon[++top] = vertices[minMin]; 1726 i = minMax; 1727 while (++i <= maxMin) { 1728 if (this.isLeft(vertices[minMin], vertices[maxMin], vertices[i]) >= 0 && i < maxMin) { 1729 continue; 1730 } 1731 while (top > 0) { 1732 if (this.isLeft(polygon[top - 1], polygon[top], vertices[i]) > 0) { 1733 break; 1734 } else { 1735 top--; 1736 } 1737 } 1738 polygon[++top] = vertices[i]; 1739 } 1740 1741 if (maxMax !== maxMin) { 1742 polygon[++top] = vertices[maxMax]; 1743 } 1744 bottom = top; 1745 i = maxMin; 1746 while (--i >= minMax) { 1747 if (this.isLeft(vertices[maxMax], vertices[minMax], vertices[i]) >= 0 && i > minMax) { 1748 continue; 1749 } 1750 while (top > bottom) { 1751 if (this.isLeft(polygon[top - 1], polygon[top], vertices[i]) > 0) { 1752 break; 1753 } else { 1754 top--; 1755 } 1756 } 1757 polygon[++top] = vertices[i]; 1758 } 1759 if (minMax !== minMin) { 1760 polygon[++top] = vertices[minMin]; 1761 } 1762 var surroundingPolygon = new Array(top + 1); 1763 System.arraycopy(polygon, 0, surroundingPolygon, 0, surroundingPolygon.length); 1764 return surroundingPolygon; 1765 } 1766 1767 ModelManager.prototype.isLeft = function(vertex0, vertex1, vertex2) { 1768 return (vertex1[0] - vertex0[0]) * (vertex2[1] - vertex0[1]) 1769 - (vertex2[0] - vertex0[0]) * (vertex1[1] - vertex0[1]); 1770 } 1771 1772 /** 1773 * Returns the mirror area of the given <code>area</code>. 1774 * @param {Area} area 1775 * @return {Area} 1776 * @private 1777 */ 1778 ModelManager.prototype.getMirroredArea = function (area) { 1779 var mirrorPath = new java.awt.geom.GeneralPath(); 1780 var point = [0, 0, 0, 0, 0, 0]; 1781 for (var it = area.getPathIterator(null); !it.isDone(); it.next()) { 1782 switch (it.currentSegment(point)) { 1783 case java.awt.geom.PathIterator.SEG_MOVETO : 1784 mirrorPath.moveTo(1 - point[0], point[1]); 1785 break; 1786 case java.awt.geom.PathIterator.SEG_LINETO : 1787 mirrorPath.lineTo(1 - point[0], point[1]); 1788 break; 1789 case java.awt.geom.PathIterator.SEG_QUADTO : 1790 mirrorPath.quadTo(1 - point[0], point[1], 1 - point[2], point[3]); 1791 break; 1792 case java.awt.geom.PathIterator.SEG_CUBICTO : 1793 mirrorPath.curveTo(1 - point[0], point[1], 1 - point[2], point[3], 1 - point[4], point[5]); 1794 break; 1795 case java.awt.geom.PathIterator.SEG_CLOSE : 1796 mirrorPath.closePath(); 1797 break; 1798 } 1799 } 1800 return new java.awt.geom.Area(mirrorPath); 1801 } 1802 1803 /** 1804 * Returns the shape matching the given <a href="http://www.w3.org/TR/SVG/paths.html">SVG path shape</a>. 1805 * @param {string} svgPathShape 1806 * @return {Shape} 1807 */ 1808 ModelManager.prototype.getShape = function(svgPathShape) { 1809 return ShapeTools.getShape(svgPathShape); 1810 } 1811