1 /* 2 Copyright 2008-2024 3 Matthias Ehmann, 4 Carsten Miller, 5 Andreas Walter, 6 Alfred Wassermann 7 8 This file is part of JSXGraph. 9 10 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 11 12 You can redistribute it and/or modify it under the terms of the 13 14 * GNU Lesser General Public License as published by 15 the Free Software Foundation, either version 3 of the License, or 16 (at your option) any later version 17 OR 18 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 19 20 JSXGraph is distributed in the hope that it will be useful, 21 but WITHOUT ANY WARRANTY; without even the implied warranty of 22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 GNU Lesser General Public License for more details. 24 25 You should have received a copy of the GNU Lesser General Public License and 26 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 27 and <https://opensource.org/licenses/MIT/>. 28 */ 29 /*global JXG:true, define: true*/ 30 31 /** 32 * Create axes and rear and front walls of the 33 * view3d bounding box bbox3D. 34 */ 35 import JXG from "../jxg.js"; 36 import Const from "../base/constants.js"; 37 import Type from "../utils/type.js"; 38 39 JXG.Polyhedron3D = function (view, polyhedron, faces, attributes) { 40 var e, 41 genericMethods, 42 generateMethod, 43 that = this; 44 45 this.constructor(view.board, attributes, Const.OBJECT_TYPE_POLYHEDRON3D, Const.OBJECT_CLASS_3D); 46 this.constructor3D(view, "polyhedron3d"); 47 48 this.board.finalizeAdding(this); 49 50 this.elType = 'polyhedron3d'; 51 52 /** 53 * List of Face3D objects. 54 * @name Polyhedron3D#faces 55 * @type Array 56 */ 57 this.faces = faces; 58 59 /** 60 * Number of faces 61 * @name Polyhedron3D#numberFaces 62 * @type Number 63 */ 64 this.numberFaces = faces.length; 65 66 /** 67 * Contains the defining data of the polyhedron: 68 * Definitions of vertices and a list of vertices for each face. This is pretty much the input given 69 * in the construction of the polyhedron plus internal data. 70 * @name Polyhedron3D#def 71 * @type Object 72 * @example 73 * polyhedron = { 74 * view: view, 75 * vertices: {}, 76 * coords: {}, 77 * coords2D: {}, 78 * zIndex: {}, 79 * faces: [] 80 * }; 81 */ 82 this.def = polyhedron; 83 84 // Simultaneous methods for all faces 85 genericMethods = [ 86 "setAttribute", 87 "setParents", 88 "prepareUpdate", 89 "updateRenderer", 90 "update", 91 "fullUpdate", 92 "highlight", 93 "noHighlight" 94 ]; 95 96 generateMethod = function (what) { 97 return function () { 98 var i; 99 100 for (i in that.faces) { 101 if (that.faces.hasOwnProperty(i)) { 102 if (Type.exists(that.faces[i][what])) { 103 that.faces[i][what].apply(that.faces[i], arguments); 104 } 105 } 106 } 107 return that; 108 }; 109 }; 110 111 for (e = 0; e < genericMethods.length; e++) { 112 this[genericMethods[e]] = generateMethod(genericMethods[e]); 113 } 114 115 this.methodMap = Type.deepCopy(this.methodMap, { 116 setAttribute: "setAttribute", 117 setParents: "setParents" 118 }); 119 }; 120 JXG.Polyhedron3D.prototype = new JXG.GeometryElement(); 121 Type.copyPrototypeMethods(JXG.Polyhedron3D, JXG.GeometryElement3D, "constructor3D"); 122 123 JXG.extend( 124 JXG.Polyhedron3D.prototype, 125 /** @lends JXG.Polyhedron3D.prototype */ { 126 127 addTransform: function (el, transform) { 128 if (this.faces.length > 0 && el.faces.length > 0) { 129 this.faces[0].addTransform(el.faces[0], transform); 130 } else { 131 throw new Error("Adding transformation failed. At least one of the two polyhedra has no faces."); 132 } 133 return this; 134 } 135 136 } 137 ); 138 139 /** 140 * @class A polyhedron in a 3D view consists of faces. 141 * @pseudo 142 * @description Create a polyhedron in a 3D view consisting of faces. Faces can 143 * be 0-, 1- or 2-dimensional. 144 * 145 * @name Polyhedron3D 146 * @augments JXG.GeometryElement3D 147 * @constructor 148 * @type Object 149 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 150 * @param {} TODO 151 * 152 * @example 153 * var box = [-4, 4]; 154 * var view = board.create( 155 * 'view3d', 156 * [[-5, -3], [8, 8], 157 * [box, box, box]], 158 * { 159 * projection: 'parallel', 160 * trackball: { enabled: false }, 161 * depthOrder: { 162 * enabled: true 163 * }, 164 * xPlaneRear: { visible: false }, 165 * yPlaneRear: { visible: false }, 166 * zPlaneRear: { fillOpacity: 0.2 } 167 * } 168 * ); 169 * var cube = view.create('polyhedron3d', [ 170 * [ 171 * [-3, -3, -3], 172 * [3, -3, -3], 173 * [3, 3, -3], 174 * [-3, 3, -3], 175 * 176 * [-3, -3, 3], 177 * [3, -3, 3], 178 * [3, 3, 3], 179 * [-3, 3, 3] 180 * ], 181 * [ 182 * [0, 1, 2, 3], 183 * [0, 1, 5, 4], 184 * [[1, 2, 6, 5], { fillColor: 'black', fillOpacity: 0.5, strokeWidth: 5 }], 185 * [2, 3, 7, 6], 186 * [3, 0, 4, 7], 187 * [4, 5, 6, 7] 188 * ] 189 * ], { 190 * fillColorArray: ['blue', 'red', 'yellow'] 191 * }); 192 * 193 * </pre><div id="JXG2ab3325b-4171-4a00-9896-a1b886969e18" class="jxgbox" style="width: 300px; height: 300px;"></div> 194 * <script type="text/javascript"> 195 * (function() { 196 * var board = JXG.JSXGraph.initBoard('JXG2ab3325b-4171-4a00-9896-a1b886969e18', 197 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 198 * var box = [-4, 4]; 199 * var view = board.create( 200 * 'view3d', 201 * [[-5, -3], [8, 8], 202 * [box, box, box]], 203 * { 204 * projection: 'parallel', 205 * trackball: { enabled: false }, 206 * depthOrder: { 207 * enabled: true 208 * }, 209 * xPlaneRear: { visible: false }, 210 * yPlaneRear: { visible: false }, 211 * zPlaneRear: { fillOpacity: 0.2 } 212 * } 213 * ); 214 * var cube = view.create('polyhedron3d', [ 215 * [ 216 * [-3, -3, -3], 217 * [3, -3, -3], 218 * [3, 3, -3], 219 * [-3, 3, -3], 220 * 221 * [-3, -3, 3], 222 * [3, -3, 3], 223 * [3, 3, 3], 224 * [-3, 3, 3] 225 * ], 226 * [ 227 * [0, 1, 2, 3], 228 * [0, 1, 5, 4], 229 * [[1, 2, 6, 5], { fillColor: 'black', fillOpacity: 0.5, strokeWidth: 5 }], 230 * [2, 3, 7, 6], 231 * [3, 0, 4, 7], 232 * [4, 5, 6, 7] 233 * ] 234 * ], { 235 * fillColorArray: ['blue', 'red', 'yellow'] 236 * }); 237 * 238 * })(); 239 * 240 * </script><pre> 241 * 242 * @example 243 * var box = [-4, 4]; 244 * var view = board.create( 245 * 'view3d', 246 * [[-5, -3], [8, 8], 247 * [box, box, box]], 248 * { 249 * projection: 'parallel', 250 * trackball: { enabled: false }, 251 * depthOrder: { 252 * enabled: true 253 * }, 254 * xPlaneRear: { visible: false }, 255 * yPlaneRear: { visible: false }, 256 * zPlaneRear: { fillOpacity: 0.2 } 257 * } 258 * ); 259 * var aa = view.create('point3d', [-3, -3, -3], { name: 'A', layer: 12}); 260 * var bb = view.create('point3d', [() => aa.X(), () => aa.Y(), 3], { name: 'B', fixed: true, layer: 12}); 261 * var cube = view.create('polyhedron3d', [ 262 * { 263 * a: 'A', 264 * b: [3, -3, -3], 265 * c: [3, 3, -3], 266 * d: [-3, 3, -3], 267 * 268 * e: bb, 269 * f: [3, -3, 3], 270 * g: [3, 3, 3], 271 * h: [-3, 3, 3] 272 * }, 273 * [ 274 * ['a', 'b', 'c', 'd'], 275 * ['a', 'b', 'f', 'e'], 276 * ['b', 'c', 'g', 'f'], 277 * ['c', 'd', 'h', 'g'], 278 * ['d', 'a', 'e', 'h'], 279 * ['e', 'f', 'g', 'h'], 280 * 281 * ['a', 'g'], // Edge 282 * ['f'] // Vertex 283 * ] 284 * ], { 285 * fillColorArray: ['blue', 'red', 'yellow'], 286 * fillOpacity: 0.4, 287 * layer: 12 288 * }); 289 * cube.faces[6].setAttribute({ strokeWidth: 5 }); 290 * cube.faces[7].setAttribute({ strokeWidth: 10 }); 291 * 292 * </pre><div id="JXG1e862f44-3e38-424b-98d5-f972338a8b7f" class="jxgbox" style="width: 300px; height: 300px;"></div> 293 * <script type="text/javascript"> 294 * (function() { 295 * var board = JXG.JSXGraph.initBoard('JXG1e862f44-3e38-424b-98d5-f972338a8b7f', 296 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 297 * var box = [-4, 4]; 298 * var view = board.create( 299 * 'view3d', 300 * [[-5, -3], [8, 8], 301 * [box, box, box]], 302 * { 303 * projection: 'parallel', 304 * trackball: { enabled: false }, 305 * depthOrder: { 306 * enabled: true 307 * }, 308 * xPlaneRear: { visible: false }, 309 * yPlaneRear: { visible: false }, 310 * zPlaneRear: { fillOpacity: 0.2 } 311 * } 312 * ); 313 * var aa = view.create('point3d', [-3, -3, -3], { name: 'A', layer: 12}); 314 * var bb = view.create('point3d', [() => aa.X(), () => aa.Y(), 3], { name: 'B', fixed: true, layer: 12}); 315 * var cube = view.create('polyhedron3d', [ 316 * { 317 * a: 'A', 318 * b: [3, -3, -3], 319 * c: [3, 3, -3], 320 * d: [-3, 3, -3], 321 * 322 * e: bb, 323 * f: [3, -3, 3], 324 * g: [3, 3, 3], 325 * h: [-3, 3, 3] 326 * }, 327 * [ 328 * ['a', 'b', 'c', 'd'], 329 * ['a', 'b', 'f', 'e'], 330 * ['b', 'c', 'g', 'f'], 331 * ['c', 'd', 'h', 'g'], 332 * ['d', 'a', 'e', 'h'], 333 * ['e', 'f', 'g', 'h'], 334 * 335 * ['a', 'g'], // Edge 336 * ['f'] // Vertex 337 * ] 338 * ], { 339 * fillColorArray: ['blue', 'red', 'yellow'], 340 * fillOpacity: 0.4, 341 * layer: 12 342 * }); 343 * cube.faces[6].setAttribute({ strokeWidth: 5 }); 344 * cube.faces[7].setAttribute({ strokeWidth: 10 }); 345 * 346 * })(); 347 * 348 * </script><pre> 349 * 350 * @example 351 * var box = [-4, 4]; 352 * var view = board.create( 353 * 'view3d', 354 * [[-5, -3], [8, 8], 355 * [box, box, box]], 356 * { 357 * projection: 'parallel', 358 * trackball: { enabled: false }, 359 * depthOrder: { 360 * enabled: true 361 * }, 362 * xPlaneRear: { visible: false }, 363 * yPlaneRear: { visible: false }, 364 * zPlaneRear: { fillOpacity: 0.2 } 365 * } 366 * ); 367 * var s = board.create('slider', [[-4, -6], [4, -6], [0, 2, 4]], { name: 's' }); 368 * var cube = view.create('polyhedron3d', [ 369 * [ 370 * () => { let f = s.Value(); return [-f, -f, -f]; }, 371 * () => { let f = s.Value(); return [f, -f, -f]; }, 372 * () => { let f = s.Value(); return [f, f, -f]; }, 373 * () => { let f = s.Value(); return [-f, f, -f]; }, 374 * 375 * () => { let f = s.Value(); return [-f, -f, f]; }, 376 * () => { let f = s.Value(); return [f, -f, f]; }, 377 * () => { let f = s.Value(); return [f, f, f]; }, 378 * // () => { let f = s.Value(); return [-f, f, f]; } 379 * [ () => -s.Value(), () => s.Value(), () => s.Value() ] 380 * ], 381 * [ 382 * [0, 1, 2, 3], 383 * [0, 1, 5, 4], 384 * [1, 2, 6, 5], 385 * [2, 3, 7, 6], 386 * [3, 0, 4, 7], 387 * [4, 5, 6, 7], 388 * ] 389 * ], { 390 * strokeWidth: 3, 391 * fillOpacity: 0.6, 392 * fillColorArray: ['blue', 'red', 'yellow'], 393 * shader: { 394 * enabled:false 395 * } 396 * }); 397 * 398 * </pre><div id="JXG6f27584b-b648-4743-a864-a6c559ead00e" class="jxgbox" style="width: 300px; height: 300px;"></div> 399 * <script type="text/javascript"> 400 * (function() { 401 * var board = JXG.JSXGraph.initBoard('JXG6f27584b-b648-4743-a864-a6c559ead00e', 402 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 403 * var box = [-4, 4]; 404 * var view = board.create( 405 * 'view3d', 406 * [[-5, -3], [8, 8], 407 * [box, box, box]], 408 * { 409 * projection: 'parallel', 410 * trackball: { enabled: false }, 411 * depthOrder: { 412 * enabled: true 413 * }, 414 * xPlaneRear: { visible: false }, 415 * yPlaneRear: { visible: false }, 416 * zPlaneRear: { fillOpacity: 0.2 } 417 * } 418 * ); 419 * var s = board.create('slider', [[-4, -6], [4, -6], [0, 2, 4]], { name: 's' }); 420 * var cube = view.create('polyhedron3d', [ 421 * [ 422 * () => { let f = s.Value(); return [-f, -f, -f]; }, 423 * () => { let f = s.Value(); return [f, -f, -f]; }, 424 * () => { let f = s.Value(); return [f, f, -f]; }, 425 * () => { let f = s.Value(); return [-f, f, -f]; }, 426 * 427 * () => { let f = s.Value(); return [-f, -f, f]; }, 428 * () => { let f = s.Value(); return [f, -f, f]; }, 429 * () => { let f = s.Value(); return [f, f, f]; }, 430 * // () => { let f = s.Value(); return [-f, f, f]; } 431 * [ () => -s.Value(), () => s.Value(), () => s.Value() ] 432 * ], 433 * [ 434 * [0, 1, 2, 3], 435 * [0, 1, 5, 4], 436 * [1, 2, 6, 5], 437 * [2, 3, 7, 6], 438 * [3, 0, 4, 7], 439 * [4, 5, 6, 7], 440 * ] 441 * ], { 442 * strokeWidth: 3, 443 * fillOpacity: 0.6, 444 * fillColorArray: ['blue', 'red', 'yellow'], 445 * shader: { 446 * enabled:false 447 * } 448 * }); 449 * 450 * })(); 451 * 452 * </script><pre> 453 * 454 * @example 455 * var box = [-4, 4]; 456 * var view = board.create( 457 * 'view3d', 458 * [[-5, -3], [8, 8], 459 * [box, box, box]], 460 * { 461 * projection: 'parallel', 462 * trackball: { enabled: false }, 463 * depthOrder: { 464 * enabled: true 465 * }, 466 * xPlaneRear: { visible: false }, 467 * yPlaneRear: { visible: false }, 468 * zPlaneRear: { fillOpacity: 0.2 } 469 * } 470 * ); 471 * let rho = 1.6180339887; 472 * let vertexList = [ 473 * [0, -1, -rho], [0, +1, -rho], [0, -1, rho], [0, +1, rho], 474 * [1, rho, 0], [-1, rho, 0], [1, -rho, 0], [-1, -rho, 0], 475 * [-rho, 0, 1], [-rho, 0, -1], [rho, 0, 1], [rho, 0, -1] 476 * ]; 477 * let faceArray = [ 478 * [4, 1, 11], 479 * [11, 1, 0], 480 * [6, 11, 0], 481 * [0, 1, 9], 482 * [11, 10, 4], 483 * [9, 1, 5], 484 * [8, 9, 5], 485 * [5, 3, 8], 486 * [6, 10, 11], 487 * [2, 3, 10], 488 * [2, 10, 6], 489 * [8, 3, 2], 490 * [3, 4, 10], 491 * [7, 8, 2], 492 * [9, 8, 7], 493 * [0, 9, 7], 494 * [4, 3, 5], 495 * [5, 1, 4], 496 * [0, 7, 6], 497 * [7, 2, 6] 498 * ]; 499 * var ico = view.create('polyhedron3d', [vertexList, faceArray], { 500 * fillColorArray: [], 501 * fillOpacity: 1, 502 * strokeWidth: 0.1, 503 * layer: 12, 504 * shader: { 505 * enabled: true, 506 * type: 'angle', 507 * hue: 60, 508 * saturation: 90, 509 * minlightness: 60, 510 * maxLightness: 80 511 * } 512 * }); 513 * 514 * </pre><div id="JXGfea93484-96e9-4eb5-9e45-bb53d612aead" class="jxgbox" style="width: 300px; height: 300px;"></div> 515 * <script type="text/javascript"> 516 * (function() { 517 * var board = JXG.JSXGraph.initBoard('JXGfea93484-96e9-4eb5-9e45-bb53d612aead', 518 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 519 * var box = [-4, 4]; 520 * var view = board.create( 521 * 'view3d', 522 * [[-5, -3], [8, 8], 523 * [box, box, box]], 524 * { 525 * projection: 'parallel', 526 * trackball: { enabled: false }, 527 * depthOrder: { 528 * enabled: true 529 * }, 530 * xPlaneRear: { visible: false }, 531 * yPlaneRear: { visible: false }, 532 * zPlaneRear: { fillOpacity: 0.2 } 533 * } 534 * ); 535 * let rho = 1.6180339887; 536 * let vertexList = [ 537 * [0, -1, -rho], [0, +1, -rho], [0, -1, rho], [0, +1, rho], 538 * [1, rho, 0], [-1, rho, 0], [1, -rho, 0], [-1, -rho, 0], 539 * [-rho, 0, 1], [-rho, 0, -1], [rho, 0, 1], [rho, 0, -1] 540 * ]; 541 * let faceArray = [ 542 * [4, 1, 11], 543 * [11, 1, 0], 544 * [6, 11, 0], 545 * [0, 1, 9], 546 * [11, 10, 4], 547 * [9, 1, 5], 548 * [8, 9, 5], 549 * [5, 3, 8], 550 * [6, 10, 11], 551 * [2, 3, 10], 552 * [2, 10, 6], 553 * [8, 3, 2], 554 * [3, 4, 10], 555 * [7, 8, 2], 556 * [9, 8, 7], 557 * [0, 9, 7], 558 * [4, 3, 5], 559 * [5, 1, 4], 560 * [0, 7, 6], 561 * [7, 2, 6] 562 * ]; 563 * var ico = view.create('polyhedron3d', [vertexList, faceArray], { 564 * fillColorArray: [], 565 * fillOpacity: 1, 566 * strokeWidth: 0.1, 567 * layer: 12, 568 * shader: { 569 * enabled: true, 570 * type: 'angle', 571 * hue: 60, 572 * saturation: 90, 573 * minlightness: 60, 574 * maxLightness: 80 575 * } 576 * }); 577 * 578 * })(); 579 * 580 * </script><pre> 581 * 582 */ 583 JXG.createPolyhedron3D = function (board, parents, attributes) { 584 var view = parents[0], 585 i, le, 586 face, f, 587 el, 588 attr, attr_polyhedron, 589 faceList = [], 590 base = null, 591 transform = null, 592 593 polyhedron = { 594 view: view, 595 vertices: {}, 596 coords: {}, 597 coords2D: {}, 598 zIndex: {}, 599 faces: [] 600 }; 601 602 if (Type.exists(parents[1].type) && parents[1].type === Const.OBJECT_TYPE_POLYHEDRON3D) { 603 // Polyhedron from baseElement and transformations 604 base = parents[1]; 605 transform = parents[2]; 606 polyhedron.vertices = base.def.vertices; 607 polyhedron.faces = base.def.faces; 608 } else { 609 // Copy vertices into a dict 610 if (Type.isArray(parents[1])) { 611 le = parents[1].length; 612 for (i = 0; i < le; i++) { 613 polyhedron.vertices[i] = parents[1][i]; 614 } 615 } else if (Type.isObject(parents[1])) { 616 for (i in parents[1]) { 617 if (parents[1].hasOwnProperty(i)) { 618 polyhedron.vertices[i] = parents[1][i]; 619 } 620 } 621 } 622 polyhedron.faces = parents[2]; 623 } 624 625 attr_polyhedron = Type.copyAttributes(attributes, board.options, "polyhedron3d"); 626 627 console.time('polyhedron'); 628 629 view.board.suspendUpdate(); 630 // Create face3d elements 631 le = polyhedron.faces.length; 632 for (i = 0; i < le; i++) { 633 attr = Type.copyAttributes(attributes, board.options, "face3d"); 634 if (attr_polyhedron.fillcolorarray.length > 0) { 635 attr.fillcolor = attr_polyhedron.fillcolorarray[i % attr_polyhedron.fillcolorarray.length]; 636 } 637 f = polyhedron.faces[i]; 638 639 if (Type.isArray(f) && f.length === 2 && Type.isObject(f[1]) && Type.isArray(f[0])) { 640 // Handle case that face is of type [[points], {attr}] 641 Type.mergeAttr(attr, f[1]); 642 // Normalize face array, i.e. don't store attributes of that face in polyhedron 643 polyhedron.faces[i] = f[0]; 644 } 645 646 face = view.create('face3d', [polyhedron, i], attr); 647 faceList.push(face); 648 } 649 el = new JXG.Polyhedron3D(view, polyhedron, faceList, attr_polyhedron); 650 el.setParents(el); // Sets el as parent to all faces. 651 for (i = 0; i < le; i++) { 652 el.inherits.push(el.faces[i]); 653 el.addChild(el.faces[i]); 654 } 655 if (base !== null) { 656 el.addTransform(base, transform); 657 el.addParents(base); 658 } 659 view.board.unsuspendUpdate(); 660 661 console.timeEnd('polyhedron'); 662 663 return el; 664 }; 665 666 JXG.registerElement("polyhedron3d", JXG.createPolyhedron3D); 667