1 /* 2 * HomePieceOfFurniture3D.js 3 * 4 * Sweet Home 3D, Copyright (c) 2024 Space Mushrooms <[email protected]> 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 scene3d.js 22 // Object3DBranch.js 23 // ModelManager.js 24 // HomeObject.js 25 // HomePieceOfFurniture.js 26 27 28 /** 29 * Creates the 3D piece matching the given home <code>piece</code>. 30 * @param {HomePieceOfFurniture} piece 31 * @param {Home} home 32 * @param {UserPreferences} [preferences] 33 * @param {boolean|function} waitModelAndTextureLoadingEnd 34 * @constructor 35 * @extends Object3DBranch 36 * @author Emmanuel Puybaret 37 */ 38 function HomePieceOfFurniture3D(piece, home, preferences, waitModelAndTextureLoadingEnd) { 39 if (waitModelAndTextureLoadingEnd === undefined) { 40 // 3 parameters 41 waitModelAndTextureLoadingEnd = preferences; 42 preferences = null; 43 } 44 Object3DBranch.call(this, piece, home, preferences); 45 46 this.createPieceOfFurnitureNode(piece, waitModelAndTextureLoadingEnd); 47 } 48 HomePieceOfFurniture3D.prototype = Object.create(Object3DBranch.prototype); 49 HomePieceOfFurniture3D.prototype.constructor = HomePieceOfFurniture3D; 50 51 HomePieceOfFurniture3D.DEFAULT_BOX = new Object(); 52 HomePieceOfFurniture3D.SELECTION_BOX_GEOMETRY = new IndexedLineArray3D( 53 [vec3.fromValues(-0.5, -0.5, -0.5), 54 vec3.fromValues(0.5, -0.5, -0.5), 55 vec3.fromValues(0.5, 0.5, -0.5), 56 vec3.fromValues(-0.5, 0.5, -0.5), 57 vec3.fromValues(-0.5, -0.5, 0.5), 58 vec3.fromValues(0.5, -0.5, 0.5), 59 vec3.fromValues(0.5, 0.5, 0.5), 60 vec3.fromValues(-0.5, 0.5, 0.5)], 61 [0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7, 4, 6, 5, 7]); 62 63 /** 64 * Creates the piece node with its transform group and add it to the piece branch. 65 * @private 66 */ 67 HomePieceOfFurniture3D.prototype.createPieceOfFurnitureNode = function(piece, waitModelAndTextureLoadingEnd) { 68 var pieceTransformGroup = new TransformGroup3D(); 69 pieceTransformGroup.setCapability(Group3D.ALLOW_CHILDREN_EXTEND); 70 pieceTransformGroup.setCapability(TransformGroup3D.ALLOW_TRANSFORM_WRITE); 71 this.addChild(pieceTransformGroup); 72 73 this.loadPieceOfFurnitureModel(waitModelAndTextureLoadingEnd); 74 } 75 76 /** 77 * @private 78 */ 79 HomePieceOfFurniture3D.prototype.loadPieceOfFurnitureModel = function(waitModelAndTextureLoadingEnd) { 80 // While loading model use a temporary node that displays a white box 81 var waitBranch = new BranchGroup3D(); 82 var normalization = new TransformGroup3D(); 83 normalization.addChild(this.getModelBox(vec3.fromValues(1, 1, 1))); 84 normalization.setUserData(PieceOfFurniture.IDENTITY_ROTATION); 85 waitBranch.addChild(normalization); 86 87 var transformGroup = this.getChild(0); 88 transformGroup.removeAllChildren(); 89 transformGroup.addChild(waitBranch); 90 91 // Set piece model initial location, orientation and size 92 this.updatePieceOfFurnitureTransform(); 93 94 var piece = this.getUserData(); 95 // Store 3D model for possible future changes 96 var model = piece.getModel(); 97 transformGroup.setUserData(model); 98 // Load piece real 3D model 99 var piece3D = this; 100 ModelManager.getInstance().loadModel(model, 101 typeof waitModelAndTextureLoadingEnd == "function" ? false : waitModelAndTextureLoadingEnd, 102 { 103 modelUpdated : function(modelRoot) { 104 piece3D.updateModelTransformations(modelRoot); 105 106 var modelRotation = piece.getModelRotation(); 107 // Add piece model scene to a normalized transform group 108 var modelTransformGroup = ModelManager.getInstance().getNormalizedTransformGroup( 109 modelRoot, modelRotation, 1, piece.isModelCenteredAtOrigin()); 110 // Store model rotation for possible future changes 111 modelTransformGroup.setUserData(modelRotation); 112 piece3D.updatePieceOfFurnitureModelNode(modelRoot, modelTransformGroup, waitModelAndTextureLoadingEnd); 113 }, 114 modelError : function(ex) { 115 // In case of problem use a default red box 116 piece3D.updatePieceOfFurnitureModelNode(piece3D.getModelBox(vec3.fromValues(1, 0, 0)), 117 new TransformGroup3D(), waitModelAndTextureLoadingEnd); 118 } 119 }); 120 } 121 122 /** 123 * Updates this branch from the home piece it manages. 124 */ 125 HomePieceOfFurniture3D.prototype.update = function() { 126 if (this.isVisible()) { 127 var piece = this.getUserData(); 128 var transformGroup = this.getChild(0); 129 var normalization = transformGroup.getChild(0).getChild(0); 130 if (piece.getModel().equals(transformGroup.getUserData()) 131 && Object3DBranch.areModelRotationsEqual(piece.getModelRotation(), normalization.getUserData())) { 132 this.updatePieceOfFurnitureModelTransformations(); 133 this.updatePieceOfFurnitureTransform(); 134 this.updatePieceOfFurnitureColorAndTexture(false); 135 } else { 136 this.loadPieceOfFurnitureModel(false); 137 } 138 } 139 this.updatePieceOfFurnitureVisibility(); 140 } 141 142 /** 143 * Sets the transformation applied to piece model to match 144 * its location, its angle and its size. 145 * @private 146 */ 147 HomePieceOfFurniture3D.prototype.updatePieceOfFurnitureTransform = function() { 148 var transformGroup = this.getChild(0); 149 var pieceTransform = ModelManager.getInstance().getPieceOfFurnitureNormalizedModelTransformation( 150 this.getUserData(), transformGroup.getChild(0).getChild(0)); 151 // Change model transformation 152 transformGroup.setTransform(pieceTransform); 153 } 154 155 /** 156 * Sets the color and the texture applied to piece model. 157 * @private 158 */ 159 HomePieceOfFurniture3D.prototype.updatePieceOfFurnitureColorAndTexture = function(waitTextureLoadingEnd) { 160 var piece = this.getUserData(); 161 var modelNode = this.getModelNode(); 162 var modelChild = modelNode.getChild(0); 163 if (modelChild.getUserData() !== HomePieceOfFurniture3D.DEFAULT_BOX) { 164 if (piece.getColor() !== null) { 165 this.setColorAndTexture(modelNode, piece.getColor(), null, piece.getShininess(), null, piece.isModelMirrored(), piece.getModelFlags(), false, 166 null, null, []); 167 } else if (piece.getTexture() !== null) { 168 this.setColorAndTexture(modelNode, null, piece.getTexture(), piece.getShininess(), null, piece.isModelMirrored(), piece.getModelFlags(), waitTextureLoadingEnd, 169 vec3.fromValues(piece.getWidth(), piece.getHeight(), piece.getDepth()), ModelManager.getInstance().getBounds(modelChild), 170 []); 171 } else if (piece.getModelMaterials() !== null) { 172 this.setColorAndTexture(modelNode, null, null, null, piece.getModelMaterials(), piece.isModelMirrored(), piece.getModelFlags(), waitTextureLoadingEnd, 173 vec3.fromValues(piece.getWidth(), piece.getHeight(), piece.getDepth()), ModelManager.getInstance().getBounds(modelChild), 174 []); 175 } else { 176 // Set default material and texture of model 177 this.setColorAndTexture(modelNode, null, null, piece.getShininess(), null, piece.isModelMirrored(), piece.getModelFlags(), false, null, null, []); 178 } 179 } 180 } 181 182 /** 183 * Returns the node of the filled model. 184 * @return {Node} 185 * @private 186 */ 187 HomePieceOfFurniture3D.prototype.getModelNode = function() { 188 var transformGroup = this.getChild(0); 189 var branchGroup = transformGroup.getChild(0); 190 return branchGroup.getChild(0); 191 } 192 193 /** 194 * Returns the selection node of the model. 195 * @return {Node} 196 * @private 197 */ 198 HomePieceOfFurniture3D.prototype.getSelectionNode = function() { 199 var transformGroup = this.getChild(0); 200 var branchGroup = transformGroup.getChild(0); 201 if (branchGroup.getChildren().length > 1 202 && branchGroup.getChild(1) instanceof Shape3D) { 203 return branchGroup.getChild(1); 204 } else { 205 return null; 206 } 207 } 208 209 /** 210 * Sets whether this piece model is visible or not. 211 * @private 212 */ 213 HomePieceOfFurniture3D.prototype.updatePieceOfFurnitureVisibility = function() { 214 var piece = this.getUserData(); 215 // Update visibility of filled model shapes 216 var visible = this.isVisible(); 217 var materials = piece.getColor() === null && piece.getTexture() === null 218 ? piece.getModelMaterials() 219 : null; 220 this.setVisible(this.getModelNode(), visible, piece.getModelFlags(), materials); 221 var selectionNode = this.getSelectionNode(); 222 if (selectionNode != null) { 223 this.setVisible(selectionNode, this.getUserPreferences() != null 224 && this.getUserPreferences().isEditingIn3DViewEnabled() 225 && visible && this.getHome() != null 226 && this.isSelected(this.getHome().getSelectedItems()), 0, null); 227 } 228 } 229 230 /** 231 * Sets the transformations applied to piece model parts. 232 * @private 233 */ 234 HomePieceOfFurniture3D.prototype.updatePieceOfFurnitureModelTransformations = function() { 235 var piece = this.getUserData(); 236 var modelNode = this.getModelNode(); 237 if (modelNode.getChild(0).getUserData() !== HomePieceOfFurniture3D.DEFAULT_BOX 238 && this.updateModelTransformations(this)) { 239 // Update normalized transform group 240 var modelTransform = ModelManager.getInstance(). 241 getNormalizedTransform(modelNode.getChild(0), piece.getModelRotation(), 1, piece.isModelCenteredAtOrigin()); 242 modelNode.setTransform(modelTransform); 243 } 244 } 245 246 /** 247 * Sets the transformations applied to <code>node</code> children 248 * and returns <code>true</code> if a transformation was changed. 249 * @param {Node3D} node 250 * @private 251 */ 252 HomePieceOfFurniture3D.prototype.updateModelTransformations = function(node) { 253 var modifiedTransformations = false; 254 var transformations = this.getUserData().getModelTransformations(); 255 var updatedTransformations = null; 256 if (transformations !== null) { 257 for (var i = 0; i < transformations.length; i++) { 258 var transformation = transformations [i]; 259 var transformName = transformation.getName() + ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX; 260 if (updatedTransformations === null) { 261 updatedTransformations = []; 262 } 263 updatedTransformations.push(transformName); 264 modifiedTransformations |= this.updateTransformation(node, transformName, transformation.getMatrix()); 265 } 266 } 267 modifiedTransformations |= this.setNotUpdatedTranformationsToIdentity(node, updatedTransformations); 268 return modifiedTransformations; 269 } 270 271 /** 272 * Sets the transformation matrix of the children which user data is equal to <code>transformGroupName</code>. 273 * @param {Node3D} node 274 * @param {String} transformGroupName 275 * @param {Array} matrix 276 * @private 277 */ 278 HomePieceOfFurniture3D.prototype.updateTransformation = function(node, transformGroupName, matrix) { 279 var modifiedTransformations = false; 280 if (node instanceof Group3D) { 281 if (node instanceof TransformGroup3D 282 && transformGroupName == node.getName()) { 283 var transformMatrix = mat4.create(); 284 node.getTransform(transformMatrix); 285 if (matrix [0][0] !== transformMatrix [0] 286 || matrix [0][1] !== transformMatrix [4] 287 || matrix [0][2] !== transformMatrix [8] 288 || matrix [0][3] !== transformMatrix [12] 289 || matrix [1][0] !== transformMatrix [1] 290 || matrix [1][1] !== transformMatrix [5] 291 || matrix [1][2] !== transformMatrix [9] 292 || matrix [1][3] !== transformMatrix [13] 293 || matrix [2][0] !== transformMatrix [2] 294 || matrix [2][1] !== transformMatrix [6] 295 || matrix [2][2] !== transformMatrix [10] 296 || matrix [2][3] !== transformMatrix [14]) { 297 mat4.set(transformMatrix, 298 matrix[0][0], matrix[1][0], matrix[2][0], 0, 299 matrix[0][1], matrix[1][1], matrix[2][1], 0, 300 matrix[0][2], matrix[1][2], matrix[2][2], 0, 301 matrix[0][3], matrix[1][3], matrix[2][3], 1); 302 node.setTransform(transformMatrix); 303 modifiedTransformations = true; 304 } 305 } else { 306 var children = node.getChildren(); 307 for (var i = 0; i < children.length; i++) { 308 modifiedTransformations |= this.updateTransformation(children [i], transformGroupName, matrix); 309 } 310 } 311 } 312 // No Link parsing 313 314 return modifiedTransformations; 315 } 316 317 /** 318 * Sets the transformation matrix of the children which user data is not in <code>updatedTransformations</code> to identity. 319 * @param {Node3D} node 320 * @param {string[]} updatedTransformations 321 * @private 322 */ 323 HomePieceOfFurniture3D.prototype.setNotUpdatedTranformationsToIdentity = function(node, updatedTransformations) { 324 var modifiedTransformations = false; 325 if (node instanceof Group3D) { 326 if (node instanceof TransformGroup3D 327 && node.getName() !== null 328 && node.getName().indexOf(ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX) >= 0 329 && node.getName().indexOf(ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX) === node.getName().length - ModelManager.DEFORMABLE_TRANSFORM_GROUP_SUFFIX.length 330 && (updatedTransformations === null 331 || updatedTransformations.indexOf(node.getName()) < 0)) { 332 var group = node; 333 var transform = mat4.create(); 334 group.getTransform(transform); 335 if (!TransformGroup3D.isIdentity(transform)) { 336 mat4.identity(transform); 337 group.setTransform(transform); 338 modifiedTransformations = true; 339 } 340 } 341 var children = node.getChildren(); 342 for (var i = 0; i < children.length; i++) { 343 modifiedTransformations |= this.setNotUpdatedTranformationsToIdentity(children [i], updatedTransformations); 344 } 345 } 346 347 return modifiedTransformations; 348 } 349 350 /** 351 * Updates transform group children with <code>modelMode</code>. 352 * @private 353 */ 354 HomePieceOfFurniture3D.prototype.updatePieceOfFurnitureModelNode = function(modelNode, normalization, waitTextureLoadingEnd) { 355 normalization.setCapability(TransformGroup3D.ALLOW_TRANSFORM_WRITE); 356 normalization.addChild(modelNode); 357 // Add model node to branch group 358 var modelBranch = new BranchGroup3D(); 359 modelBranch.addChild(normalization); 360 361 if (this.getHome() != null) { 362 // Add selection box node 363 var selectionBox = new Shape3D(HomePieceOfFurniture3D.SELECTION_BOX_GEOMETRY, this.getSelectionAppearance()); 364 selectionBox.setPickable(false); 365 modelBranch.addChild(selectionBox); 366 } 367 368 var piece = this.getUserData(); 369 if (piece.isDoorOrWindow()) { 370 this.setTransparentShapeNotPickable(modelNode); 371 } 372 373 var transformGroup = this.getChild(0); 374 // Remove previous nodes 375 transformGroup.removeAllChildren(); 376 // Add model branch to live scene 377 transformGroup.addChild(modelBranch); 378 if (piece.isHorizontallyRotated()) { 379 // Update piece transformation to ensure its center is correctly placed 380 this.updatePieceOfFurnitureTransform(); 381 } 382 383 // Flip normals if back faces of model are shown 384 if (piece.isBackFaceShown()) { 385 this.setBackFaceNormalFlip(this.getModelNode(), true); 386 } 387 // Update piece color, visibility and model mirror 388 this.modifiedTexturesCount = 0; 389 this.updatePieceOfFurnitureColorAndTexture(waitTextureLoadingEnd); 390 this.updatePieceOfFurnitureVisibility(); 391 // If no texture is customized, report loading end to waitTextureLoadingEnd 392 if (this.modifiedTexturesCount === 0 393 && typeof waitTextureLoadingEnd == "function") { 394 waitTextureLoadingEnd(this); 395 } 396 } 397 398 /** 399 * Returns a box that may replace model. 400 * @private 401 */ 402 HomePieceOfFurniture3D.prototype.getModelBox = function(color) { 403 var boxAppearance = new Appearance3D(); 404 boxAppearance.setDiffuseColor(color); 405 boxAppearance.setAmbientColor(vec3.scale(vec3.create(), color, 0.7)); 406 var box = new Box3D(0.5, 0.5, 0.5, boxAppearance); 407 box.setUserData(HomePieceOfFurniture3D.DEFAULT_BOX); 408 return box; 409 } 410 411 /** 412 * Sets the material and texture attribute of all <code>Shape3D</code> children nodes of <code>node</code> 413 * from the given <code>color</code> and <code>texture</code>. 414 * @private 415 */ 416 HomePieceOfFurniture3D.prototype.setColorAndTexture = function(node, color, texture, shininess, 417 materials, mirrored, modelFlags, waitTextureLoadingEnd, 418 pieceSize, modelBounds, modifiedAppearances) { 419 if (node instanceof Group3D) { 420 // Set material and texture of all children 421 var children = node.getChildren(); 422 for (var i = 0; i < children.length; i++) { 423 this.setColorAndTexture(children [i], color, 424 texture, shininess, materials, mirrored, modelFlags, waitTextureLoadingEnd, 425 pieceSize, modelBounds, modifiedAppearances); 426 } 427 } else if (node instanceof Link3D) { 428 this.setColorAndTexture(node.getSharedGroup(), color, 429 texture, shininess, materials, mirrored, modelFlags, waitTextureLoadingEnd, 430 pieceSize, modelBounds, modifiedAppearances); 431 } else if (node instanceof Shape3D) { 432 var shape = node; 433 var shapeName = shape.getName(); 434 var appearance = shape.getAppearance(); 435 if (appearance === null) { 436 appearance = new Appearance3D(); 437 node.setAppearance(appearance); 438 } 439 440 // Check appearance wasn't already changed 441 if (modifiedAppearances.indexOf(appearance) === -1) { 442 var defaultAppearance = null; 443 var colorModified = color !== null; 444 var textureModified = !colorModified 445 && texture !== null; 446 var materialModified = !colorModified 447 && !textureModified 448 && materials !== null && materials.length > 0; 449 var appearanceModified = colorModified 450 || textureModified 451 || materialModified 452 || shininess !== null 453 || mirrored 454 || modelFlags != 0; 455 var windowPane = shapeName !== null 456 && shapeName.indexOf(ModelManager.WINDOW_PANE_SHAPE_PREFIX) === 0; 457 if (!windowPane && appearanceModified 458 || windowPane && materialModified) { 459 // Store shape default appearance 460 // (global color or texture change doesn't have effect on window panes) 461 if (appearance.defaultAppearance === undefined) { 462 appearance.defaultAppearance = appearance.clone(); 463 } 464 defaultAppearance = appearance.defaultAppearance; 465 } 466 var materialShininess = 0.; 467 if (appearanceModified) { 468 materialShininess = shininess !== null 469 ? shininess 470 : (appearance.getSpecularColor() !== undefined 471 && appearance.getShininess() !== undefined 472 ? appearance.getShininess() / 128 473 : 0); 474 } 475 if (colorModified) { 476 // Change color only of shapes that are not window panes 477 if (windowPane) { 478 this.restoreDefaultAppearance(appearance, null); 479 } else { 480 // Change material if no default texture is displayed on the shape 481 // (textures always keep the colors of their image file) 482 this.updateAppearanceMaterial(appearance, color, color, materialShininess); 483 if (defaultAppearance.getTransparency() !== undefined) { 484 appearance.setTransparency(defaultAppearance.getTransparency()); 485 } 486 if (defaultAppearance.getCullFace() !== undefined) { 487 appearance.setCullFace(defaultAppearance.getCullFace()); 488 } 489 appearance.setTextureCoordinatesGeneration(defaultAppearance.getTextureCoordinatesGeneration()); 490 appearance.setTextureImage(null); 491 } 492 } else if (textureModified) { 493 // Change texture only of shapes that are not window panes 494 if (windowPane) { 495 this.restoreDefaultAppearance(appearance, null); 496 } else { 497 appearance.setTextureCoordinatesGeneration(this.getTextureCoordinates(appearance, texture, pieceSize, modelBounds)); 498 this.updateTextureTransform(appearance, texture, true); 499 this.updateAppearanceMaterial(appearance, Object3DBranch.DEFAULT_COLOR, Object3DBranch.DEFAULT_AMBIENT_COLOR, materialShininess); 500 TextureManager.getInstance().loadTexture(texture.getImage(), 0, 501 typeof waitTextureLoadingEnd == "function" ? false : waitTextureLoadingEnd, 502 this.getTextureObserver(appearance, mirrored, modelFlags, waitTextureLoadingEnd)); 503 } 504 } else if (materialModified) { 505 var materialFound = false; 506 // Apply color, texture and shininess of the material named as appearance name 507 for (var i = 0; i < materials.length; i++) { 508 var material = materials [i]; 509 if (material !== null 510 && (material.getKey() != null 511 && material.getKey() == appearance.getName() 512 || material.getKey() == null 513 && material.getName() == appearance.getName())) { 514 if (material.getShininess() !== null) { 515 materialShininess = material.getShininess(); 516 } 517 color = material.getColor(); 518 if (color !== null 519 && (color & 0xFF000000) != 0) { 520 this.updateAppearanceMaterial(appearance, color, color, materialShininess); 521 if (defaultAppearance.getTransparency() !== undefined) { 522 appearance.setTransparency(defaultAppearance.getTransparency()); 523 } 524 if (defaultAppearance.getCullFace() !== undefined) { 525 appearance.setCullFace(defaultAppearance.getCullFace()); 526 } 527 appearance.setTextureImage(null); 528 } else if (color === null && material.getTexture() !== null) { 529 var materialTexture = material.getTexture(); 530 if (this.isTexturesCoordinatesDefined(shape)) { 531 this.restoreDefaultTextureCoordinatesGeneration(appearance); 532 this.updateTextureTransform(appearance, materialTexture); 533 } else { 534 appearance.setTextureCoordinatesGeneration(this.getTextureCoordinates(appearance, material.getTexture(), pieceSize, modelBounds)); 535 this.updateTextureTransform(appearance, materialTexture, true); 536 } 537 this.updateAppearanceMaterial(appearance, Object3DBranch.DEFAULT_COLOR, Object3DBranch.DEFAULT_AMBIENT_COLOR, materialShininess); 538 var materialTexture = material.getTexture(); 539 TextureManager.getInstance().loadTexture(materialTexture.getImage(), 0, 540 typeof waitTextureLoadingEnd == "function" ? false : waitTextureLoadingEnd, 541 this.getTextureObserver(appearance, mirrored, modelFlags, waitTextureLoadingEnd)); 542 } else { 543 this.restoreDefaultAppearance(appearance, material.getShininess()); 544 } 545 materialFound = true; 546 break; 547 } 548 } 549 if (!materialFound) { 550 this.restoreDefaultAppearance(appearance, null); 551 } 552 } else { 553 this.restoreDefaultAppearance(appearance, shininess); 554 } 555 556 this.setCullFace(appearance, mirrored, (modelFlags & PieceOfFurniture.SHOW_BACK_FACE) != 0); 557 558 // Store modified appearances to avoid changing their values more than once 559 modifiedAppearances.push(appearance); 560 } 561 } 562 } 563 564 /** 565 * Returns a texture observer that will update the given <code>appearance</code>. 566 * @private 567 */ 568 HomePieceOfFurniture3D.prototype.getTextureObserver = function(appearance, mirrored, modelFlags, waitTextureLoadingEnd) { 569 var piece3D = this; 570 this.modifiedTexturesCount++; 571 return { 572 textureUpdated : function(textureImage) { 573 if (TextureManager.getInstance().isTextureTransparent(textureImage)) { 574 appearance.setCullFace(Appearance3D.CULL_NONE); 575 } else { 576 var defaultAppearance = appearance.defaultAppearance; 577 if (defaultAppearance !== null 578 && defaultAppearance.getCullFace() !== null) { 579 appearance.setCullFace(defaultAppearance.getCullFace()); 580 } 581 } 582 if (appearance.getTextureImage() !== textureImage) { 583 appearance.setTextureImage(textureImage); 584 } 585 586 piece3D.setCullFace(appearance, mirrored, (modelFlags & PieceOfFurniture.SHOW_BACK_FACE) != 0); 587 588 // If all customized textures are loaded, report loading end to waitTextureLoadingEnd 589 if (--piece3D.modifiedTexturesCount === 0 590 && typeof waitTextureLoadingEnd == "function") { 591 waitTextureLoadingEnd(piece3D); 592 } 593 }, 594 textureError : function(error) { 595 return this.textureUpdated(TextureManager.getInstance().getErrorImage()); 596 } 597 }; 598 } 599 600 /** 601 * Returns a texture coordinates generator that wraps the given texture on front face. 602 * @private 603 */ 604 HomePieceOfFurniture3D.prototype.getTextureCoordinates = function(appearance, texture, pieceSize, modelBounds) { 605 var lower = vec3.create(); 606 modelBounds.getLower(lower); 607 var upper = vec3.create(); 608 modelBounds.getUpper(upper); 609 var minimumSize = ModelManager.getInstance().getMinimumSize(); 610 var sx = pieceSize [0] / Math.max(upper [0] - lower [0], minimumSize); 611 var sw = -lower [0] * sx; 612 var ty = pieceSize [1] / Math.max(upper [1] - lower [1], minimumSize); 613 var tz = pieceSize [2] / Math.max(upper [2] - lower [2], minimumSize); 614 var tw = -lower [1] * ty + upper [2] * tz; 615 return {planeS : vec4.fromValues(sx, 0, 0, sw), 616 planeT : vec4.fromValues(0, ty, -tz, tw)}; 617 } 618 619 /** 620 * Returns <code>true</code> if all the geometries of the given <code>shape</code> define some texture coordinates. 621 * @private 622 */ 623 HomePieceOfFurniture3D.prototype.isTexturesCoordinatesDefined = function(shape) { 624 var geometries = shape.getGeometries(); 625 for (var i = 0, n = geometries.length; i < n; i++) { 626 if (!geometries [i].hasTextureCoordinates()) { 627 return false; 628 } 629 } 630 return true; 631 } 632 633 /** 634 * Sets the cull face of the given <code>appearance</code>. 635 * @private 636 */ 637 HomePieceOfFurniture3D.prototype.setCullFace = function(appearance, mirrored, backFaceShown) { 638 // Change cull face 639 if (appearance.getCullFace() !== Appearance3D.CULL_NONE) { 640 var cullFace = appearance.getCullFace() !== undefined 641 ? appearance.getCullFace() 642 : Appearance3D.CULL_BACK; 643 var defaultCullFace = appearance.defaultCullFace; 644 if (defaultCullFace === undefined) { 645 appearance.defaultCullFace = (defaultCullFace = cullFace); 646 } 647 appearance.setCullFace((mirrored ^ backFaceShown ^ defaultCullFace === Appearance3D.CULL_FRONT) 648 ? Appearance3D.CULL_FRONT 649 : Appearance3D.CULL_BACK); 650 } 651 } 652 653 /** 654 * Restores default material and texture of the given <code>appearance</code>. 655 * @private 656 */ 657 HomePieceOfFurniture3D.prototype.restoreDefaultAppearance = function(appearance, shininess) { 658 if (appearance.defaultAppearance !== undefined) { 659 var defaultAppearance = appearance.defaultAppearance; 660 if (defaultAppearance.getAmbientColor() !== undefined) { 661 appearance.setAmbientColor(defaultAppearance.getAmbientColor()); 662 } 663 if (defaultAppearance.getDiffuseColor() !== undefined) { 664 appearance.setDiffuseColor(defaultAppearance.getDiffuseColor()); 665 if (shininess !== null) { 666 appearance.setSpecularColor(vec3.fromValues(shininess, shininess, shininess)); 667 appearance.setShininess(shininess * 128); 668 } else { 669 appearance.setSpecularColor(defaultAppearance.getSpecularColor()); 670 appearance.setShininess(defaultAppearance.getShininess()); 671 } 672 } 673 if (defaultAppearance.getTransparency() !== undefined) { 674 appearance.setTransparency(defaultAppearance.getTransparency()); 675 } 676 if (appearance.getCullFace() !== undefined) { 677 appearance.setCullFace(defaultAppearance.getCullFace()); 678 } 679 if (defaultAppearance.getTextureCoordinatesGeneration() !== undefined) { 680 appearance.setTextureCoordinatesGeneration(defaultAppearance.getTextureCoordinatesGeneration()); 681 } 682 if (appearance.getTextureImage() !== undefined) { 683 appearance.setTextureImage(defaultAppearance.getTextureImage()); 684 } 685 } 686 } 687 688 /** 689 * Restores default texture coordinates generation of the given <code>appearance</code>. 690 * @private 691 */ 692 HomePieceOfFurniture3D.prototype.restoreDefaultTextureCoordinatesGeneration = function(appearance) { 693 if (appearance.defaultAppearance !== undefined) { 694 var defaultAppearance = appearance.defaultAppearance; 695 if (defaultAppearance.getTextureCoordinatesGeneration() !== undefined) { 696 appearance.setTextureCoordinatesGeneration(defaultAppearance.getTextureCoordinatesGeneration()); 697 } 698 } 699 } 700 701 /** 702 * Sets the visible attribute of the <code>Shape3D</code> children nodes of <code>node</code>. 703 * @private 704 */ 705 HomePieceOfFurniture3D.prototype.setVisible = function(node, visible, modelFlags, materials) { 706 if (node instanceof Group3D) { 707 // Set visibility of all children 708 var children = node.getChildren(); 709 for (var i = 0; i < children.length; i++) { 710 this.setVisible(children [i], visible, modelFlags, materials); 711 } 712 } else if (node instanceof Link3D) { 713 this.setVisible(node.getSharedGroup(), visible, modelFlags, materials); 714 } else if (node instanceof Shape3D) { 715 var shape = node; 716 var appearance = shape.getAppearance(); 717 if (appearance === null) { 718 appearance = new Appearance3D(); 719 node.setAppearance(appearance); 720 } 721 var shapeName = shape.getName(); 722 if (visible 723 && shapeName !== null 724 && shapeName.indexOf(ModelManager.LIGHT_SHAPE_PREFIX) === 0 725 && this.getHome() !== null 726 && !this.isSelected(this.getHome().getSelectedItems()) 727 && (typeof HomeLight === "undefined" 728 || this.getUserData() instanceof HomeLight)) { 729 // Don't display light sources shapes of unselected lights 730 visible = false; 731 } 732 733 if (visible) { 734 var appearanceName = appearance.getName(); 735 if (appearanceName != null) { 736 if ((modelFlags & PieceOfFurniture.HIDE_EDGE_COLOR_MATERIAL) != 0 737 && appearanceName.indexOf(ModelManager.EDGE_COLOR_MATERIAL_PREFIX) === 0) { 738 visible = false; 739 } else if (materials != null) { 740 // Check whether the material color used by this shape isn't invisible 741 for (var i = 0; i < materials.length; i++) { 742 var material = materials [i]; 743 if (material !== null 744 && material.getName() == appearanceName) { 745 var color = material.getColor(); 746 visible = color === null 747 || (color & 0xFF000000) !== 0; 748 break; 749 } 750 } 751 } 752 } 753 } 754 755 // Change visibility 756 appearance.setVisible(visible); 757 } 758 } 759 760 /** 761 * Returns <code>true</code> if this 3D piece is visible. 762 * @private 763 */ 764 HomePieceOfFurniture3D.prototype.isVisible = function() { 765 var piece = this.getUserData(); 766 return piece.isVisible() 767 && (piece.getLevel() === null 768 || piece.getLevel().isViewableAndVisible()); 769 } 770 771 /** 772 * Returns <code>true</code> if this piece of furniture belongs to <code>selectedItems</code>. 773 * @private 774 */ 775 HomePieceOfFurniture3D.prototype.isSelected = function(selectedItems) { 776 for (var i = 0; i < selectedItems.length; i++) { 777 var item = selectedItems [i]; 778 if (item === this.getUserData() 779 || (item instanceof HomeFurnitureGroup 780 && this.isSelected(item.getFurniture()))) { 781 return true; 782 } 783 } 784 return false; 785 } 786 787 /** 788 * Sets whether all <code>Shape3D</code> children nodes of <code>node</code> should have 789 * their normal flipped or not. 790 * Caution !!! Should be executed only once per instance 791 * @param backFaceNormalFlip <code>true</code> if normals should be flipped. 792 * @private 793 */ 794 HomePieceOfFurniture3D.prototype.setBackFaceNormalFlip = function(node, backFaceNormalFlip) { 795 if (node instanceof Group3D) { 796 // Set back face normal flip of all children 797 var children = node.getChildren(); 798 for (var i = 0; i < children.length; i++) { 799 this.setBackFaceNormalFlip(children [i], backFaceNormalFlip); 800 } 801 } else if (node instanceof Link3D) { 802 this.setBackFaceNormalFlip(node.getSharedGroup(), backFaceNormalFlip); 803 } else if (node instanceof Shape3D) { 804 var appearance = node.getAppearance(); 805 if (appearance === null) { 806 appearance = new Appearance3D(); 807 node.setAppearance(appearance); 808 } 809 // Change back face normal flip 810 appearance.setBackFaceNormalFlip( 811 backFaceNormalFlip ^ appearance.getCullFace() === Appearance3D.CULL_FRONT); 812 } 813 } 814 815 /** 816 * Cancels the pickability of the <code>Shape3D</code> children nodes of <code>node</code> 817 * when it uses a transparent appearance. 818 * @private 819 */ 820 HomePieceOfFurniture3D.prototype.setTransparentShapeNotPickable = function(node) { 821 if (node instanceof Group3D) { 822 var children = node.getChildren(); 823 for (var i = 0; i < children.length; i++) { 824 this.setTransparentShapeNotPickable(children [i]); 825 } 826 } else if (node instanceof Link3D) { 827 this.setTransparentShapeNotPickable(node.getSharedGroup()); 828 } else if (node instanceof Shape3D) { 829 var appearance = node.getAppearance(); 830 if (appearance !== null 831 && appearance.getTransparency() > 0) { 832 node.setPickable(false); 833 } 834 } 835 } 836 837