1 /* 2 * ModelManager.js 3 * 4 * Sweet Home 3D, Copyright (c) 2024 Space Mushrooms <info@sweethome3d.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 if (parent !== null) { 974 var nodeIndex = parent.getChildren().indexOf(referenceNode); 975 var pickableGroup = parent.getChild(++nodeIndex); 976 while (!(pickableGroup instanceof TransformGroup3D)) { 977 pickableGroup = parent.getChild(++nodeIndex); 978 } 979 var lastDigitIndex = subTransformationIndex + subTransformationOpeningPrefixes [j].length; 980 while (lastDigitIndex < name.length && name.charAt(lastDigitIndex) >= '0' && name.charAt(lastDigitIndex) <= '9') { 981 lastDigitIndex++; 982 } 983 // Remove node and its sibling group and attach it to parent transformation 984 if (this.attachNodesToPickableTransformGroup(group, 985 referenceNodePrefixes [j] + name.substring(subTransformationIndex + subTransformationOpeningPrefixes [j].length, lastDigitIndex), 986 [referenceNode, pickableGroup])) { 987 return true; 988 } 989 } 990 } 991 } 992 } 993 } 994 } 995 } 996 } 997 if (node instanceof Group3D) { 998 var children = node.getChildren(); 999 for (var i = children.length - 1; i >= 0; i--) { 1000 if (this.updateDeformableModelSubTransformedHierarchy(group, children [i], referenceNodePrefixes, subTransformationOpeningPrefixes, movedNodes)) { 1001 return true; 1002 } 1003 } 1004 } 1005 return false; 1006 } 1007 1008 /** 1009 * @param {Node3D} node the root of a model 1010 * @param {string} groupPrefix 1011 * @param {Array} movedNodes 1012 * @return {boolean} 1013 * @private 1014 */ 1015 ModelManager.prototype.attachNodesToPickableTransformGroup = function(node, groupPrefix, movedNodes) { 1016 if (node instanceof TransformGroup3D 1017 && (groupPrefix + ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX) == node.getName()) { 1018 var group = node; 1019 for (var i = 0; i < movedNodes.length; i++) { 1020 var movedNode = movedNodes [i]; 1021 movedNode.getParent().removeChild(movedNode); 1022 group.addChild(movedNode); 1023 } 1024 return true; 1025 } else if (node instanceof Group3D) { 1026 var children = node.getChildren(); 1027 for (var i = 0; i < children.length; i++) { 1028 if (this.attachNodesToPickableTransformGroup(children [i], groupPrefix, movedNodes)) { 1029 return true; 1030 } 1031 } 1032 } 1033 return false; 1034 } 1035 1036 /** 1037 * Returns <code>true</code> if the given <code>node</code> or its children contains at least a deformable group. 1038 * @param {Node3D} node the root of a model 1039 * @return {boolean} 1040 */ 1041 ModelManager.prototype.containsDeformableNode = function(node) { 1042 if (node instanceof TransformGroup3D 1043 && node.getName() !== null 1044 && node.getName().indexOf(ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX) >= 0 1045 && node.getName().indexOf(ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX) === (node.getName().length - ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX.length)) { 1046 return true; 1047 } else if (node instanceof Group3D) { 1048 var children = node.getChildren(); 1049 for (var i = 0; i < children.length; i++) { 1050 if (this.containsDeformableNode(children [i])) { 1051 return true; 1052 } 1053 } 1054 } 1055 return false; 1056 } 1057 1058 /** 1059 * Returns <code>true</code> if the given <code>node</code> or its children contains is a deformed transformed group. 1060 * @param {Node3D} node a node 1061 * @return {boolean} 1062 * @private 1063 */ 1064 ModelManager.prototype.isDeformed = function(node) { 1065 if (node instanceof TransformGroup3D 1066 && node.getName() !== null 1067 && node.getName().indexOf(ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX) >= 0 1068 && node.getName().indexOf(ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX) === (node.getName().length - ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX.length)) { 1069 var transform = mat4.create(); 1070 node.getTransform(transform); 1071 return !TransformGroup3D.isIdentity(transform); 1072 } else if (node instanceof Group3D) { 1073 var children = node.getChildren(); 1074 for (var i = 0; i < children.length; i++) { 1075 if (this.isDeformed(children [i])) { 1076 return true; 1077 } 1078 } 1079 } 1080 return false; 1081 } 1082 1083 /** 1084 * Returns the materials used by the children shapes of the given <code>node</code>, 1085 * attributing their <code>creator</code> to them. 1086 * @param {Node3D} node 1087 * @param {boolean} ignoreEdgeColorMaterial 1088 * @param {string} [creator] 1089 */ 1090 ModelManager.prototype.getMaterials = function(node, ignoreEdgeColorMaterial, creator) { 1091 if (creator === undefined) { 1092 if (ignoreEdgeColorMaterial === undefined) { 1093 ignoreEdgeColorMaterial = false; 1094 creator = null; 1095 } else { 1096 creator = ignoreEdgeColorMaterial; 1097 } 1098 } 1099 1100 var appearances = []; 1101 this.searchAppearances(node, ignoreEdgeColorMaterial, appearances); 1102 var materials = []; 1103 for (var i = 0; i < appearances.length; i++) { 1104 var appearance = appearances[i]; 1105 var color = null; 1106 var shininess = null; 1107 var diffuseColor = appearance.getDiffuseColor(); 1108 if (diffuseColor != null) { 1109 color = 0xFF000000 1110 | (Math.round(diffuseColor[0] * 255) << 16) 1111 | (Math.round(diffuseColor[1] * 255) << 8) 1112 | Math.round(diffuseColor[2] * 255); 1113 shininess = appearance.getShininess() != null ? appearance.getShininess() / 128 : null; 1114 } 1115 var appearanceTexture = appearance.getTextureImage(); 1116 var texture = null; 1117 if (appearanceTexture != null) { 1118 var textureImageUrl = appearanceTexture.url; 1119 if (textureImageUrl != null) { 1120 var textureImage = new SimpleURLContent(textureImageUrl); 1121 var textureImageName = textureImageUrl.substring(textureImageUrl.lastIndexOf('/') + 1); 1122 var lastPoint = textureImageName.lastIndexOf('.'); 1123 if (lastPoint !== -1) { 1124 textureImageName = textureImageName.substring(0, lastPoint); 1125 } 1126 texture = new HomeTexture( 1127 new CatalogTexture(null, textureImageName, textureImage, -1, -1, creator)); 1128 } 1129 } 1130 var materialName = appearance.getName(); 1131 if (materialName === undefined) { 1132 materialName = null; 1133 } 1134 var homeMaterial = new HomeMaterial(materialName, color, texture, shininess); 1135 for (var j = 0; j < materials.length; j++) { 1136 if (materials [j].getName() == homeMaterial.getName()) { 1137 // Don't add twice materials with the same name 1138 homeMaterial = null; 1139 break; 1140 } 1141 } 1142 if (homeMaterial != null) { 1143 materials.push(homeMaterial); 1144 } 1145 } 1146 materials.sort(function (m1, m2) { 1147 var name1 = m1.getName(); 1148 var name2 = m2.getName(); 1149 if (name1 != null) { 1150 if (name2 != null) { 1151 return name1.localeCompare(name2); 1152 } else { 1153 return 1; 1154 } 1155 } else if (name2 != null) { 1156 return -1; 1157 } else { 1158 return 0; 1159 } 1160 }); 1161 return materials; 1162 } 1163 1164 /** 1165 * @param {Node3D} node 1166 * @param {boolean} ignoreEdgeColorMaterial 1167 * @param {Array} appearances 1168 * @private 1169 */ 1170 ModelManager.prototype.searchAppearances = function(node, ignoreEdgeColorMaterial, appearances) { 1171 if (node instanceof Group3D) { 1172 var children = node.getChildren(); 1173 for (var i = 0; i < children.length; i++) { 1174 this.searchAppearances(children [i], ignoreEdgeColorMaterial, appearances); 1175 } 1176 } else if (node instanceof Link3D) { 1177 this.searchAppearances(node.getSharedGroup(), ignoreEdgeColorMaterial, appearances); 1178 } else if (node instanceof Shape3D) { 1179 var appearance = node.getAppearance(); 1180 if (appearance !== null 1181 && (!ignoreEdgeColorMaterial 1182 || !(appearance.getName().indexOf(ModelManager.EDGE_COLOR_MATERIAL_PREFIX) === 0)) 1183 && appearances.indexOf(appearance) === -1) { 1184 appearances.push(appearance); 1185 } 1186 } 1187 } 1188 1189 /** 1190 * Replaces multiple shared shapes of the given <code>node</code> with one shape with transformed geometries. 1191 * @param {BranchGroup3D} modelRoot 1192 * @private 1193 */ 1194 ModelManager.prototype.replaceMultipleSharedShapes = function(modelRoot) { 1195 var sharedShapes = []; 1196 this.searchSharedShapes(modelRoot, sharedShapes, false); 1197 for (var i = 0; i < sharedShapes.length; i++) { 1198 if (sharedShapes [i].value > 1) { 1199 var transformations = []; 1200 var shape = sharedShapes [i].key; 1201 this.searchShapeTransformations(modelRoot, shape, transformations, mat4.create()); 1202 // Replace shared shape by a unique shape with transformed geometries 1203 var newShape = shape.clone(); 1204 var geometries = newShape.getGeometries(); 1205 for (var j = 0; j < geometries.length; j++) { 1206 var newGeometry = this.getTransformedGeometry(geometries [j], transformations); 1207 if (newGeometry === null) { 1208 return; 1209 } 1210 newShape.setGeometry(newGeometry, j); 1211 } 1212 this.removeSharedShape(modelRoot, shape); 1213 modelRoot.addChild(newShape); 1214 } 1215 } 1216 } 1217 1218 /** 1219 * Searches all the shapes which are shared among the children of the given <code>node</code>. 1220 * @param {Node3D} node a node 1221 * @param {Array} sharedShapes 1222 * @param {boolean} childOfSharedGroup 1223 * @private 1224 */ 1225 ModelManager.prototype.searchSharedShapes = function(node, sharedShapes, childOfSharedGroup) { 1226 if (node instanceof Group3D) { 1227 var children = node.getChildren(); 1228 for (var i = 0; i < children.length; i++) { 1229 this.searchSharedShapes(children [i], sharedShapes, childOfSharedGroup); 1230 } 1231 } else if (node instanceof Link3D) { 1232 this.searchSharedShapes(node.getSharedGroup(), sharedShapes, true); 1233 } else if (node instanceof Shape3D) { 1234 if (childOfSharedGroup) { 1235 for (var i = 0; i < sharedShapes.length; i++) { 1236 if (sharedShapes [i].key === node) { 1237 sharedShapes [i].value++; 1238 return; 1239 } 1240 } 1241 sharedShapes.push({key : node, value : 1}); 1242 } 1243 } 1244 } 1245 1246 /** 1247 * Searches all the transformations applied to a shared <code>shape</code> child of the given <b>node</b>. 1248 * @param {Node3D} node a node 1249 * @param {Shape3D} shape 1250 * @param {mat4[]} transformations 1251 * @param {mat4} parentTransformations 1252 */ 1253 ModelManager.prototype.searchShapeTransformations = function(node, shape, transformations, parentTransformations) { 1254 if (node instanceof Group3D) { 1255 if (!(node instanceof TransformGroup3D) 1256 || !this.isDeformed(node)) { 1257 if (node instanceof TransformGroup3D) { 1258 parentTransformations = mat4.clone(parentTransformations); 1259 var transform = mat4.create(); 1260 node.getTransform(transform); 1261 mat4.mul(parentTransformations, parentTransformations, transform); 1262 } 1263 var children = node.getChildren(); 1264 for (var i = 0; i < children.length; i++) { 1265 this.searchShapeTransformations(children [i], shape, transformations, parentTransformations); 1266 } 1267 } 1268 } else if (node instanceof Link3D) { 1269 this.searchShapeTransformations(node.getSharedGroup(), shape, transformations, parentTransformations); 1270 } else if (node === shape) { 1271 transformations.push(parentTransformations); 1272 } 1273 } 1274 1275 /** 1276 * Returns a new geometry where coordinates are transformed with the given transformations. 1277 * @param {IndexedGeometryArray3D} geometry 1278 * @param {mat4[]} transformations 1279 * @return {IndexedGeometryArray3D} 1280 */ 1281 ModelManager.prototype.getTransformedGeometry = function(geometry, transformations) { 1282 var offsetIndex = 0; 1283 var offsetVertex = 0; 1284 var newVertexIndices = new Array(transformations.length * geometry.vertexIndices.length); 1285 for (var i = 0; i < transformations.length; i++) { 1286 for (var j = 0, n = geometry.vertexIndices.length; j < n; j++) { 1287 newVertexIndices [offsetIndex + j] = offsetVertex + geometry.vertexIndices [j]; 1288 } 1289 offsetIndex += geometry.vertexIndices.length; 1290 offsetVertex += geometry.vertices.length; 1291 } 1292 1293 var newTextureCoordinateIndices = new Array(transformations.length * geometry.textureCoordinateIndices.length); 1294 offsetIndex = 0; 1295 for (var i = 0; i < transformations.length; i++) { 1296 for (var j = 0, n = geometry.textureCoordinateIndices.length; j < n; j++) { 1297 newTextureCoordinateIndices [offsetIndex + j] = geometry.textureCoordinateIndices [j]; 1298 } 1299 offsetIndex += geometry.textureCoordinateIndices.length; 1300 } 1301 1302 offsetVertex = 0; 1303 var newVertices = new Array(transformations.length * geometry.vertices.length); 1304 for (var i = 0; i < transformations.length; i++) { 1305 for (var j = 0, n = geometry.vertices.length; j < n; j++) { 1306 var vertex = vec3.clone(geometry.vertices [j]); 1307 vec3.transformMat4(vertex, vertex, transformations [i]); 1308 newVertices [offsetVertex + j] = vertex; 1309 } 1310 offsetVertex += geometry.vertices.length; 1311 } 1312 1313 if (geometry instanceof IndexedLineArray3D) { 1314 return new IndexedLineArray3D(newVertices, newVertexIndices, geometry.textureCoordinates, newTextureCoordinateIndices); 1315 } else if (geometry instanceof IndexedTriangleArray3D) { 1316 var newNormalIndices = new Array(transformations.length * geometry.normalIndices.length); 1317 offsetIndex = 0; 1318 var offsetNormal = 0; 1319 for (var i = 0; i < transformations.length; i++) { 1320 for (var j = 0, n = geometry.normalIndices.length; j < n; j++) { 1321 newNormalIndices [offsetIndex + j] = offsetNormal + geometry.normalIndices [j]; 1322 } 1323 offsetIndex += geometry.normalIndices.length; 1324 offsetNormal += geometry.normals.length; 1325 } 1326 1327 var offsetNormal = 0; 1328 var newNormals = new Array(transformations.length * geometry.normals.length); 1329 for (var i = 0; i < transformations.length; i++) { 1330 for (var j = 0, n = geometry.normals.length; j < n; j++) { 1331 var normal = vec3.clone(geometry.normals [j]); 1332 vec3.transformMat4(normal, normal, transformations [i]); 1333 vec3.normalize(normal, normal); 1334 newNormals [offsetNormal + j] = normal; 1335 } 1336 offsetNormal += geometry.normals.length; 1337 } 1338 1339 return new IndexedTriangleArray3D(newVertices, newVertexIndices, geometry.textureCoordinates, newTextureCoordinateIndices, newNormals, newNormalIndices); 1340 } else { 1341 return null; 1342 } 1343 } 1344 1345 /** 1346 * Removes the shared shape from the children of the given <code>node</code>. 1347 * @param {Node3D} node a node 1348 * @param {Shape3D} shape 1349 */ 1350 ModelManager.prototype.removeSharedShape = function(node, shape) { 1351 if (node instanceof Group3D) { 1352 if (!(node instanceof TransformGroup3D) 1353 || !this.isDeformed(node)) { 1354 var children = node.getChildren(); 1355 for (var i = children.length - 1; i >= 0; i--) { 1356 this.removeSharedShape(children [i], shape); 1357 } 1358 if (children.length === 0 1359 && node.getParent() instanceof Group3D) { 1360 node.getParent().removeChild(node); 1361 } 1362 } 1363 } else if (node instanceof Link3D) { 1364 var sharedGroup = node.getSharedGroup(); 1365 this.removeSharedShape(sharedGroup, shape); 1366 if (sharedGroup.children.length == 0) { 1367 node.getParent().removeChild(node); 1368 } 1369 } else if (node === shape) { 1370 node.getParent().removeChild(node); 1371 } 1372 } 1373 1374 /** 1375 * Returns the shape matching the given cut out shape if not <code>null</code> 1376 * or the 2D area of the 3D shapes children of the <code>node</code> 1377 * projected on its front side. The returned area is normalized in a 1 unit square 1378 * centered at the origin. 1379 */ 1380 ModelManager.prototype.getFrontArea = function(cutOutShape, node) { 1381 var frontArea; 1382 if (cutOutShape !== null) { 1383 frontArea = new java.awt.geom.Area(this.getShape(cutOutShape)); 1384 frontArea.transform(java.awt.geom.AffineTransform.getScaleInstance(1, -1)); 1385 frontArea.transform(java.awt.geom.AffineTransform.getTranslateInstance(-0.5, 0.5)); 1386 } else { 1387 var vertexCount = this.getVertexCount(node); 1388 if (vertexCount < 1000000) { 1389 var frontAreaWithHoles = new java.awt.geom.Area(); 1390 this.computeBottomOrFrontArea(node, frontAreaWithHoles, mat4.create(), false, false); 1391 frontArea = new java.awt.geom.Area(); 1392 var currentPathPoints = []; 1393 var previousRoomPoint = null; 1394 for (var it = frontAreaWithHoles.getPathIterator(null, 1); !it.isDone(); it.next()) { 1395 var areaPoint = [0, 0]; 1396 switch (it.currentSegment(areaPoint)) { 1397 case java.awt.geom.PathIterator.SEG_MOVETO : 1398 case java.awt.geom.PathIterator.SEG_LINETO : 1399 if (previousRoomPoint === null 1400 || areaPoint[0] !== previousRoomPoint[0] 1401 || areaPoint[1] !== previousRoomPoint[1]) { 1402 currentPathPoints.push(areaPoint); 1403 } 1404 previousRoomPoint = areaPoint; 1405 break; 1406 case java.awt.geom.PathIterator.SEG_CLOSE : 1407 if (currentPathPoints[0][0] === previousRoomPoint[0] 1408 && currentPathPoints[0][1] === previousRoomPoint[1]) { 1409 currentPathPoints.splice(currentPathPoints.length - 1, 1); 1410 } 1411 if (currentPathPoints.length > 2) { 1412 var pathPoints = currentPathPoints.slice(0); 1413 var subRoom = new Room(pathPoints); 1414 if (subRoom.getArea() > 0) { 1415 if (!subRoom.isClockwise()) { 1416 var currentPath = new java.awt.geom.GeneralPath(); 1417 currentPath.moveTo(pathPoints[0][0], pathPoints[0][1]); 1418 for (var i = 1; i < pathPoints.length; i++) { 1419 currentPath.lineTo(pathPoints[i][0], pathPoints[i][1]); 1420 } 1421 currentPath.closePath(); 1422 frontArea.add(new java.awt.geom.Area(currentPath)); 1423 } 1424 } 1425 } 1426 currentPathPoints.length = 0; 1427 previousRoomPoint = null; 1428 break; 1429 } 1430 } 1431 var bounds = frontAreaWithHoles.getBounds2D(); 1432 frontArea.transform(java.awt.geom.AffineTransform.getTranslateInstance(-bounds.getCenterX(), -bounds.getCenterY())); 1433 frontArea.transform(java.awt.geom.AffineTransform.getScaleInstance(1 / bounds.getWidth(), 1 / bounds.getHeight())); 1434 } 1435 else { 1436 frontArea = new java.awt.geom.Area(new java.awt.geom.Rectangle2D.Float(-0.5, -0.5, 1, 1)); 1437 } 1438 } 1439 return frontArea; 1440 } 1441 1442 /** 1443 * Returns the 2D area of the 3D shapes children of the given scene 3D <code>node</code> 1444 * projected on the floor (plan y = 0), or of the given staircase if <code>node</code> is an 1445 * instance of <code>HomePieceOfFurniture</code>. 1446 * @param {Node3D|HomePieceOfFurniture} node 1447 * @return {Area} 1448 */ 1449 ModelManager.prototype.getAreaOnFloor = function(node) { 1450 if (node instanceof Node3D) { 1451 var modelAreaOnFloor; 1452 var vertexCount = this.getVertexCount(node); 1453 if (vertexCount < 10000) { 1454 modelAreaOnFloor = new java.awt.geom.Area(); 1455 this.computeBottomOrFrontArea(node, modelAreaOnFloor, mat4.create(), true, true); 1456 } else { 1457 var vertices = []; 1458 this.computeVerticesOnFloor(node, vertices, mat4.create()); 1459 if (vertices.length > 0) { 1460 var surroundingPolygon = this.getSurroundingPolygon(vertices.slice(0)); 1461 var generalPath = new java.awt.geom.GeneralPath(java.awt.geom.Path2D.WIND_NON_ZERO, surroundingPolygon.length); 1462 generalPath.moveTo(surroundingPolygon[0][0], surroundingPolygon[0][1]); 1463 for (var i = 0; i < surroundingPolygon.length; i++) { 1464 generalPath.lineTo(surroundingPolygon[i][0], surroundingPolygon[i][1]); 1465 } 1466 generalPath.closePath(); 1467 modelAreaOnFloor = new java.awt.geom.Area(generalPath); 1468 } else { 1469 modelAreaOnFloor = new java.awt.geom.Area(); 1470 } 1471 } 1472 return modelAreaOnFloor; 1473 } else { 1474 var staircase = node; 1475 if (staircase.getStaircaseCutOutShape() === null) { 1476 throw new IllegalArgumentException("No cut out shape associated to piece"); 1477 } 1478 var shape = this.getShape(staircase.getStaircaseCutOutShape()); 1479 var staircaseArea = new java.awt.geom.Area(shape); 1480 if (staircase.isModelMirrored()) { 1481 staircaseArea = this.getMirroredArea(staircaseArea); 1482 } 1483 var staircaseTransform = java.awt.geom.AffineTransform.getTranslateInstance( 1484 staircase.getX() - staircase.getWidth() / 2, 1485 staircase.getY() - staircase.getDepth() / 2); 1486 staircaseTransform.concatenate(java.awt.geom.AffineTransform.getRotateInstance(staircase.getAngle(), 1487 staircase.getWidth() / 2, staircase.getDepth() / 2)); 1488 staircaseTransform.concatenate(java.awt.geom.AffineTransform.getScaleInstance(staircase.getWidth(), staircase.getDepth())); 1489 staircaseArea.transform(staircaseTransform); 1490 return staircaseArea; 1491 } 1492 } 1493 1494 /** 1495 * Returns the total count of vertices in all geometries. 1496 * @param {Node3D} node 1497 * @return {number} 1498 * @private 1499 */ 1500 ModelManager.prototype.getVertexCount = function(node) { 1501 var count = 0; 1502 if (node instanceof Group3D) { 1503 var children = node.getChildren(); 1504 for (var i = 0; i < children.length; i++) { 1505 count += this.getVertexCount(children [i]); 1506 } 1507 } else if (node instanceof Link3D) { 1508 count = this.getVertexCount(node.getSharedGroup()); 1509 } else if (node instanceof Shape3D) { 1510 var appearance = node.getAppearance(); 1511 if (appearance.isVisible()) { 1512 var geometries = node.getGeometries(); 1513 for (var i = 0, n = geometries.length; i < n; i++) { 1514 var geometry = geometries[i]; 1515 count += geometry.vertices.length; 1516 } 1517 } 1518 } 1519 return count; 1520 } 1521 1522 /** 1523 * Computes the 2D area on floor or on front side of the 3D shapes children of <code>node</code>. 1524 * @param {Node3D} node 1525 * @param {Area} nodeArea 1526 * @param {mat4} parentTransformations 1527 * @param {boolean} ignoreTransparentShapes 1528 * @param {boolean} bottom 1529 * @private 1530 */ 1531 ModelManager.prototype.computeBottomOrFrontArea = function(node, nodeArea, parentTransformations, ignoreTransparentShapes, bottom) { 1532 if (node instanceof Group3D) { 1533 if (node instanceof TransformGroup3D) { 1534 parentTransformations = mat4.clone(parentTransformations); 1535 var transform = mat4.create(); 1536 node.getTransform(transform); 1537 mat4.mul(parentTransformations, parentTransformations, transform); 1538 } 1539 var children = node.getChildren(); 1540 for (var i = 0; i < children.length; i++) { 1541 this.computeBottomOrFrontArea(children [i], nodeArea, parentTransformations, ignoreTransparentShapes, bottom); 1542 } 1543 } else if (node instanceof Link3D) { 1544 this.computeBottomOrFrontArea(node.getSharedGroup(), nodeArea, parentTransformations, ignoreTransparentShapes, bottom); 1545 } else if (node instanceof Shape3D) { 1546 var appearance = node.getAppearance(); 1547 if (appearance.isVisible() 1548 && (!ignoreTransparentShapes 1549 || appearance.getTransparency() === undefined 1550 || appearance.getTransparency() < 1)) { 1551 var geometries = node.getGeometries(); 1552 for (var i = 0, n = geometries.length; i < n; i++) { 1553 var geometry = geometries[i]; 1554 this.computeBottomOrFrontGeometryArea(geometry, nodeArea, parentTransformations, bottom); 1555 } 1556 } 1557 } 1558 } 1559 1560 /** 1561 * Computes the bottom area of a 3D geometry if <code>bottom</code> is <code>true</code>, 1562 * and the front area if not. 1563 * @param {IndexedGeometryArray3D} geometryArray 1564 * @param {Area} nodeArea 1565 * @param {mat4} parentTransformations 1566 * @param {boolean} bottom 1567 * @private 1568 */ 1569 ModelManager.prototype.computeBottomOrFrontGeometryArea = function(geometryArray, nodeArea, parentTransformations, bottom) { 1570 if (geometryArray instanceof IndexedTriangleArray3D) { 1571 var vertexCount = geometryArray.vertices.length; 1572 var vertices = new Array(vertexCount * 2); 1573 var vertex = vec3.create(); 1574 for (var index = 0, i = 0; index < vertices.length; i++) { 1575 vec3.copy(vertex, geometryArray.vertices [i]); 1576 vec3.transformMat4(vertex, vertex, parentTransformations); 1577 vertices[index++] = vertex[0]; 1578 if (bottom) { 1579 vertices[index++] = vertex[2]; 1580 } else { 1581 vertices[index++] = vertex[1]; 1582 } 1583 } 1584 1585 geometryPath = new java.awt.geom.GeneralPath(java.awt.geom.Path2D.WIND_NON_ZERO, 1000); 1586 for (var i = 0, triangleIndex = 0, n = geometryArray.vertexIndices.length; i < n; i += 3) { 1587 this.addTriangleToPath(geometryArray, geometryArray.vertexIndices [i], geometryArray.vertexIndices [i + 1], geometryArray.vertexIndices [i + 2], vertices, 1588 geometryPath, triangleIndex++, nodeArea); 1589 } 1590 nodeArea.add(new java.awt.geom.Area(geometryPath)); 1591 } 1592 } 1593 1594 /** 1595 * Adds to <code>nodePath</code> the triangle joining vertices at 1596 * vertexIndex1, vertexIndex2, vertexIndex3 indices, 1597 * only if the triangle has a positive orientation. 1598 * @param {javax.media.j3d.GeometryArray} geometryArray 1599 * @param {number} vertexIndex1 1600 * @param {number} vertexIndex2 1601 * @param {number} vertexIndex3 1602 * @param {Array} vertices 1603 * @param {GeneralPath} geometryPath 1604 * @param {number} triangleIndex 1605 * @param {Area} nodeArea 1606 * @private 1607 */ 1608 ModelManager.prototype.addTriangleToPath = function(geometryArray, vertexIndex1, vertexIndex2, vertexIndex3, vertices, geometryPath, triangleIndex, nodeArea) { 1609 var xVertex1 = vertices[2 * vertexIndex1]; 1610 var yVertex1 = vertices[2 * vertexIndex1 + 1]; 1611 var xVertex2 = vertices[2 * vertexIndex2]; 1612 var yVertex2 = vertices[2 * vertexIndex2 + 1]; 1613 var xVertex3 = vertices[2 * vertexIndex3]; 1614 var yVertex3 = vertices[2 * vertexIndex3 + 1]; 1615 if ((xVertex2 - xVertex1) * (yVertex3 - yVertex2) - (yVertex2 - yVertex1) * (xVertex3 - xVertex2) > 0) { 1616 if (triangleIndex > 0 && triangleIndex % 1000 === 0) { 1617 nodeArea.add(new java.awt.geom.Area(geometryPath)); 1618 geometryPath.reset(); 1619 } 1620 geometryPath.moveTo(xVertex1, yVertex1); 1621 geometryPath.lineTo(xVertex2, yVertex2); 1622 geometryPath.lineTo(xVertex3, yVertex3); 1623 geometryPath.closePath(); 1624 } 1625 } 1626 1627 /** 1628 * Computes the vertices coordinates projected on floor of the 3D shapes children of <code>node</code>. 1629 * @param {Node3D} node 1630 * @param {Array} vertices 1631 * @param {mat4} parentTransformations 1632 * @private 1633 */ 1634 ModelManager.prototype.computeVerticesOnFloor = function (node, vertices, parentTransformations) { 1635 if (node instanceof Group3D) { 1636 if (node instanceof TransformGroup3D) { 1637 parentTransformations = mat4.clone(parentTransformations); 1638 var transform = mat4.create(); 1639 node.getTransform(transform); 1640 mat4.mul(parentTransformations, parentTransformations, transform); 1641 } 1642 var children = node.getChildren(); 1643 for (var i = 0; i < children.length; i++) { 1644 this.computeVerticesOnFloor(children [i], vertices, parentTransformations); 1645 } 1646 } else if (node instanceof Link3D) { 1647 this.computeVerticesOnFloor(node.getSharedGroup(), vertices, parentTransformations); 1648 } else if (node instanceof Shape3D) { 1649 var appearance = node.getAppearance(); 1650 if (appearance.isVisible() 1651 && (appearance.getTransparency() === undefined 1652 || appearance.getTransparency() < 1)) { 1653 var geometries = node.getGeometries(); 1654 for (var i = 0, n = geometries.length; i < n; i++) { 1655 var geometryArray = geometries[i]; 1656 var vertexCount = geometryArray.vertices.length; 1657 var vertex = vec3.create(); 1658 for (var index = 0, j = 0; index < vertexCount; j++, index++) { 1659 vec3.copy(vertex, geometryArray.vertices [j]); 1660 vec3.transformMat4(vertex, vertex, parentTransformations); 1661 vertices.push([vertex[0], vertex[2]]); 1662 } 1663 } 1664 } 1665 } 1666 } 1667 1668 /** 1669 * Returns the convex polygon that surrounds the given <code>vertices</code>. 1670 * From Andrew's monotone chain 2D convex hull algorithm described at 1671 * http://softsurfer.com/Archive/algorithm%5F0109/algorithm%5F0109.htm 1672 * @param {Array} vertices 1673 * @return {Array} 1674 * @private 1675 */ 1676 ModelManager.prototype.getSurroundingPolygon = function(vertices) { 1677 vertices.sort(function (vertex1, vertex2) { 1678 var testedValue; 1679 if (vertex1[0] === vertex2[0]) { 1680 testedValue = vertex2[1] - vertex1[1]; 1681 } else { 1682 testedValue = vertex2[0] - vertex1[0]; 1683 } 1684 if (testedValue > 0) { 1685 return 1; 1686 } else if (testedValue < 0) { 1687 return -1; 1688 } else { 1689 return 0; 1690 } 1691 }); 1692 var polygon = new Array(vertices.length); 1693 var bottom = 0; 1694 var top = -1; 1695 var i; 1696 1697 var minMin = 0; 1698 var minMax; 1699 var xmin = vertices[0][0]; 1700 for (i = 1; i < vertices.length; i++) { 1701 if (vertices[i][0] !== xmin) { 1702 break; 1703 } 1704 } 1705 minMax = i - 1; 1706 if (minMax === vertices.length - 1) { 1707 polygon[++top] = vertices[minMin]; 1708 if (vertices[minMax][1] !== vertices[minMin][1]) { 1709 polygon[++top] = vertices[minMax]; 1710 } 1711 polygon[++top] = vertices[minMin]; 1712 var surroundingPolygon = new Array(top + 1); 1713 System.arraycopy(polygon, 0, surroundingPolygon, 0, surroundingPolygon.length); 1714 return surroundingPolygon; 1715 } 1716 1717 var maxMin; 1718 var maxMax = vertices.length - 1; 1719 var xMax = vertices[vertices.length - 1][0]; 1720 for (i = vertices.length - 2; i >= 0; i--) { 1721 if (vertices[i][0] !== xMax) { 1722 break; 1723 } 1724 } 1725 maxMin = i + 1; 1726 1727 polygon[++top] = vertices[minMin]; 1728 i = minMax; 1729 while (++i <= maxMin) { 1730 if (this.isLeft(vertices[minMin], vertices[maxMin], vertices[i]) >= 0 && i < maxMin) { 1731 continue; 1732 } 1733 while (top > 0) { 1734 if (this.isLeft(polygon[top - 1], polygon[top], vertices[i]) > 0) { 1735 break; 1736 } else { 1737 top--; 1738 } 1739 } 1740 polygon[++top] = vertices[i]; 1741 } 1742 1743 if (maxMax !== maxMin) { 1744 polygon[++top] = vertices[maxMax]; 1745 } 1746 bottom = top; 1747 i = maxMin; 1748 while (--i >= minMax) { 1749 if (this.isLeft(vertices[maxMax], vertices[minMax], vertices[i]) >= 0 && i > minMax) { 1750 continue; 1751 } 1752 while (top > bottom) { 1753 if (this.isLeft(polygon[top - 1], polygon[top], vertices[i]) > 0) { 1754 break; 1755 } else { 1756 top--; 1757 } 1758 } 1759 polygon[++top] = vertices[i]; 1760 } 1761 if (minMax !== minMin) { 1762 polygon[++top] = vertices[minMin]; 1763 } 1764 var surroundingPolygon = new Array(top + 1); 1765 System.arraycopy(polygon, 0, surroundingPolygon, 0, surroundingPolygon.length); 1766 return surroundingPolygon; 1767 } 1768 1769 ModelManager.prototype.isLeft = function(vertex0, vertex1, vertex2) { 1770 return (vertex1[0] - vertex0[0]) * (vertex2[1] - vertex0[1]) 1771 - (vertex2[0] - vertex0[0]) * (vertex1[1] - vertex0[1]); 1772 } 1773 1774 /** 1775 * Returns the mirror area of the given <code>area</code>. 1776 * @param {Area} area 1777 * @return {Area} 1778 * @private 1779 */ 1780 ModelManager.prototype.getMirroredArea = function (area) { 1781 var mirrorPath = new java.awt.geom.GeneralPath(); 1782 var point = [0, 0, 0, 0, 0, 0]; 1783 for (var it = area.getPathIterator(null); !it.isDone(); it.next()) { 1784 switch (it.currentSegment(point)) { 1785 case java.awt.geom.PathIterator.SEG_MOVETO : 1786 mirrorPath.moveTo(1 - point[0], point[1]); 1787 break; 1788 case java.awt.geom.PathIterator.SEG_LINETO : 1789 mirrorPath.lineTo(1 - point[0], point[1]); 1790 break; 1791 case java.awt.geom.PathIterator.SEG_QUADTO : 1792 mirrorPath.quadTo(1 - point[0], point[1], 1 - point[2], point[3]); 1793 break; 1794 case java.awt.geom.PathIterator.SEG_CUBICTO : 1795 mirrorPath.curveTo(1 - point[0], point[1], 1 - point[2], point[3], 1 - point[4], point[5]); 1796 break; 1797 case java.awt.geom.PathIterator.SEG_CLOSE : 1798 mirrorPath.closePath(); 1799 break; 1800 } 1801 } 1802 return new java.awt.geom.Area(mirrorPath); 1803 } 1804 1805 /** 1806 * Returns the shape matching the given <a href="http://www.w3.org/TR/SVG/paths.html">SVG path shape</a>. 1807 * @param {string} svgPathShape 1808 * @return {Shape} 1809 */ 1810 ModelManager.prototype.getShape = function(svgPathShape) { 1811 return ShapeTools.getShape(svgPathShape); 1812 } 1813