1 /* 2 Copyright 2008-2025 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 addTransform: "addTransform" 119 }); 120 }; 121 JXG.Polyhedron3D.prototype = new JXG.GeometryElement(); 122 Type.copyPrototypeMethods(JXG.Polyhedron3D, JXG.GeometryElement3D, "constructor3D"); 123 124 JXG.extend( 125 JXG.Polyhedron3D.prototype, 126 /** @lends JXG.Polyhedron3D.prototype */ { 127 128 // Already documented in element3d.js 129 addTransform: function (el, transform) { 130 if (this.faces.length > 0 && el.faces.length > 0) { 131 this.faces[0].addTransform(el.faces[0], transform); 132 } else { 133 throw new Error("Adding transformation failed. At least one of the two polyhedra has no faces."); 134 } 135 return this; 136 }, 137 138 /** 139 * Output polyhedron in ASCII STL format. 140 * Normals are ignored and output as 0 0 0. 141 * 142 * @param {String} name Set name of the model, overwrites property name 143 * @returns String 144 */ 145 toSTL: function(name) { 146 var i, j, v, f, c, le, 147 txt = 'model '; 148 149 if (name === undefined) { 150 name = this.name; 151 } 152 153 txt += name + '\n'; 154 155 for (i = 0; i < this.def.faces.length; i++) { 156 txt += ' facet normal 0 0 0\n outer loop\n'; 157 f = this.def.faces[i]; 158 le = f.length; 159 v = this.def.coords; 160 for (j = 0; j < le; j++) { 161 c = v[f[j]]; 162 txt += ' vertex ' + c[1] + ' ' + c[2] + ' ' + c[3] + '\n'; 163 } 164 txt += ' endloop\n endfacet\n'; 165 } 166 txt += 'endsolid ' + name + '\n'; 167 168 return txt; 169 } 170 } 171 ); 172 173 /** 174 * @class A polyhedron in a 3D view consists of faces. 175 * @pseudo 176 * @description Create a polyhedron in a 3D view consisting of faces. Faces can 177 * be 0-, 1- or 2-dimensional. 178 * 179 * @name Polyhedron3D 180 * @augments JXG.GeometryElement3D 181 * @constructor 182 * @type Object 183 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 184 * @param {} TODO 185 * 186 * @example 187 * var box = [-4, 4]; 188 * var view = board.create( 189 * 'view3d', 190 * [[-5, -3], [8, 8], 191 * [box, box, box]], 192 * { 193 * projection: 'parallel', 194 * trackball: { enabled: false }, 195 * depthOrder: { 196 * enabled: true 197 * }, 198 * xPlaneRear: { visible: false }, 199 * yPlaneRear: { visible: false }, 200 * zPlaneRear: { fillOpacity: 0.2 } 201 * } 202 * ); 203 * var cube = view.create('polyhedron3d', [ 204 * [ 205 * [-3, -3, -3], 206 * [3, -3, -3], 207 * [3, 3, -3], 208 * [-3, 3, -3], 209 * 210 * [-3, -3, 3], 211 * [3, -3, 3], 212 * [3, 3, 3], 213 * [-3, 3, 3] 214 * ], 215 * [ 216 * [0, 1, 2, 3], 217 * [0, 1, 5, 4], 218 * [[1, 2, 6, 5], { fillColor: 'black', fillOpacity: 0.5, strokeWidth: 5 }], 219 * [2, 3, 7, 6], 220 * [3, 0, 4, 7], 221 * [4, 5, 6, 7] 222 * ] 223 * ], { 224 * fillColorArray: ['blue', 'red', 'yellow'] 225 * }); 226 * 227 * </pre><div id="JXG2ab3325b-4171-4a00-9896-a1b886969e18" class="jxgbox" style="width: 300px; height: 300px;"></div> 228 * <script type="text/javascript"> 229 * (function() { 230 * var board = JXG.JSXGraph.initBoard('JXG2ab3325b-4171-4a00-9896-a1b886969e18', 231 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 232 * var box = [-4, 4]; 233 * var view = board.create( 234 * 'view3d', 235 * [[-5, -3], [8, 8], 236 * [box, box, box]], 237 * { 238 * projection: 'parallel', 239 * trackball: { enabled: false }, 240 * depthOrder: { 241 * enabled: true 242 * }, 243 * xPlaneRear: { visible: false }, 244 * yPlaneRear: { visible: false }, 245 * zPlaneRear: { fillOpacity: 0.2 } 246 * } 247 * ); 248 * var cube = view.create('polyhedron3d', [ 249 * [ 250 * [-3, -3, -3], 251 * [3, -3, -3], 252 * [3, 3, -3], 253 * [-3, 3, -3], 254 * 255 * [-3, -3, 3], 256 * [3, -3, 3], 257 * [3, 3, 3], 258 * [-3, 3, 3] 259 * ], 260 * [ 261 * [0, 1, 2, 3], 262 * [0, 1, 5, 4], 263 * [[1, 2, 6, 5], { fillColor: 'black', fillOpacity: 0.5, strokeWidth: 5 }], 264 * [2, 3, 7, 6], 265 * [3, 0, 4, 7], 266 * [4, 5, 6, 7] 267 * ] 268 * ], { 269 * fillColorArray: ['blue', 'red', 'yellow'] 270 * }); 271 * 272 * })(); 273 * 274 * </script><pre> 275 * 276 * @example 277 * var box = [-4, 4]; 278 * var view = board.create( 279 * 'view3d', 280 * [[-5, -3], [8, 8], 281 * [box, box, box]], 282 * { 283 * projection: 'parallel', 284 * trackball: { enabled: false }, 285 * depthOrder: { 286 * enabled: true 287 * }, 288 * xPlaneRear: { visible: false }, 289 * yPlaneRear: { visible: false }, 290 * zPlaneRear: { fillOpacity: 0.2 } 291 * } 292 * ); 293 * var aa = view.create('point3d', [-3, -3, -3], { name: 'A', layer: 12}); 294 * var bb = view.create('point3d', [() => aa.X(), () => aa.Y(), 3], { name: 'B', fixed: true, layer: 12}); 295 * var cube = view.create('polyhedron3d', [ 296 * { 297 * a: 'A', 298 * b: [3, -3, -3], 299 * c: [3, 3, -3], 300 * d: [-3, 3, -3], 301 * 302 * e: bb, 303 * f: [3, -3, 3], 304 * g: [3, 3, 3], 305 * h: [-3, 3, 3] 306 * }, 307 * [ 308 * ['a', 'b', 'c', 'd'], 309 * ['a', 'b', 'f', 'e'], 310 * ['b', 'c', 'g', 'f'], 311 * ['c', 'd', 'h', 'g'], 312 * ['d', 'a', 'e', 'h'], 313 * ['e', 'f', 'g', 'h'], 314 * 315 * ['a', 'g'], // Edge 316 * ['f'] // Vertex 317 * ] 318 * ], { 319 * fillColorArray: ['blue', 'red', 'yellow'], 320 * fillOpacity: 0.4, 321 * layer: 12 322 * }); 323 * cube.faces[6].setAttribute({ strokeWidth: 5 }); 324 * cube.faces[7].setAttribute({ strokeWidth: 10 }); 325 * 326 * </pre><div id="JXG1e862f44-3e38-424b-98d5-f972338a8b7f" class="jxgbox" style="width: 300px; height: 300px;"></div> 327 * <script type="text/javascript"> 328 * (function() { 329 * var board = JXG.JSXGraph.initBoard('JXG1e862f44-3e38-424b-98d5-f972338a8b7f', 330 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 331 * var box = [-4, 4]; 332 * var view = board.create( 333 * 'view3d', 334 * [[-5, -3], [8, 8], 335 * [box, box, box]], 336 * { 337 * projection: 'parallel', 338 * trackball: { enabled: false }, 339 * depthOrder: { 340 * enabled: true 341 * }, 342 * xPlaneRear: { visible: false }, 343 * yPlaneRear: { visible: false }, 344 * zPlaneRear: { fillOpacity: 0.2 } 345 * } 346 * ); 347 * var aa = view.create('point3d', [-3, -3, -3], { name: 'A', layer: 12}); 348 * var bb = view.create('point3d', [() => aa.X(), () => aa.Y(), 3], { name: 'B', fixed: true, layer: 12}); 349 * var cube = view.create('polyhedron3d', [ 350 * { 351 * a: 'A', 352 * b: [3, -3, -3], 353 * c: [3, 3, -3], 354 * d: [-3, 3, -3], 355 * 356 * e: bb, 357 * f: [3, -3, 3], 358 * g: [3, 3, 3], 359 * h: [-3, 3, 3] 360 * }, 361 * [ 362 * ['a', 'b', 'c', 'd'], 363 * ['a', 'b', 'f', 'e'], 364 * ['b', 'c', 'g', 'f'], 365 * ['c', 'd', 'h', 'g'], 366 * ['d', 'a', 'e', 'h'], 367 * ['e', 'f', 'g', 'h'], 368 * 369 * ['a', 'g'], // Edge 370 * ['f'] // Vertex 371 * ] 372 * ], { 373 * fillColorArray: ['blue', 'red', 'yellow'], 374 * fillOpacity: 0.4, 375 * layer: 12 376 * }); 377 * cube.faces[6].setAttribute({ strokeWidth: 5 }); 378 * cube.faces[7].setAttribute({ strokeWidth: 10 }); 379 * 380 * })(); 381 * 382 * </script><pre> 383 * 384 * @example 385 * var box = [-4, 4]; 386 * var view = board.create( 387 * 'view3d', 388 * [[-5, -3], [8, 8], 389 * [box, box, box]], 390 * { 391 * projection: 'parallel', 392 * trackball: { enabled: false }, 393 * depthOrder: { 394 * enabled: true 395 * }, 396 * xPlaneRear: { visible: false }, 397 * yPlaneRear: { visible: false }, 398 * zPlaneRear: { fillOpacity: 0.2 } 399 * } 400 * ); 401 * var s = board.create('slider', [[-4, -6], [4, -6], [0, 2, 4]], { name: 's' }); 402 * var cube = view.create('polyhedron3d', [ 403 * [ 404 * () => { let f = s.Value(); return [-f, -f, -f]; }, 405 * () => { let f = s.Value(); return [f, -f, -f]; }, 406 * () => { let f = s.Value(); return [f, f, -f]; }, 407 * () => { let f = s.Value(); return [-f, f, -f]; }, 408 * 409 * () => { let f = s.Value(); return [-f, -f, f]; }, 410 * () => { let f = s.Value(); return [f, -f, f]; }, 411 * () => { let f = s.Value(); return [f, f, f]; }, 412 * // () => { let f = s.Value(); return [-f, f, f]; } 413 * [ () => -s.Value(), () => s.Value(), () => s.Value() ] 414 * ], 415 * [ 416 * [0, 1, 2, 3], 417 * [0, 1, 5, 4], 418 * [1, 2, 6, 5], 419 * [2, 3, 7, 6], 420 * [3, 0, 4, 7], 421 * [4, 5, 6, 7], 422 * ] 423 * ], { 424 * strokeWidth: 3, 425 * fillOpacity: 0.6, 426 * fillColorArray: ['blue', 'red', 'yellow'], 427 * shader: { 428 * enabled:false 429 * } 430 * }); 431 * 432 * </pre><div id="JXG6f27584b-b648-4743-a864-a6c559ead00e" class="jxgbox" style="width: 300px; height: 300px;"></div> 433 * <script type="text/javascript"> 434 * (function() { 435 * var board = JXG.JSXGraph.initBoard('JXG6f27584b-b648-4743-a864-a6c559ead00e', 436 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 437 * var box = [-4, 4]; 438 * var view = board.create( 439 * 'view3d', 440 * [[-5, -3], [8, 8], 441 * [box, box, box]], 442 * { 443 * projection: 'parallel', 444 * trackball: { enabled: false }, 445 * depthOrder: { 446 * enabled: true 447 * }, 448 * xPlaneRear: { visible: false }, 449 * yPlaneRear: { visible: false }, 450 * zPlaneRear: { fillOpacity: 0.2 } 451 * } 452 * ); 453 * var s = board.create('slider', [[-4, -6], [4, -6], [0, 2, 4]], { name: 's' }); 454 * var cube = view.create('polyhedron3d', [ 455 * [ 456 * () => { let f = s.Value(); return [-f, -f, -f]; }, 457 * () => { let f = s.Value(); return [f, -f, -f]; }, 458 * () => { let f = s.Value(); return [f, f, -f]; }, 459 * () => { let f = s.Value(); return [-f, f, -f]; }, 460 * 461 * () => { let f = s.Value(); return [-f, -f, f]; }, 462 * () => { let f = s.Value(); return [f, -f, f]; }, 463 * () => { let f = s.Value(); return [f, f, f]; }, 464 * // () => { let f = s.Value(); return [-f, f, f]; } 465 * [ () => -s.Value(), () => s.Value(), () => s.Value() ] 466 * ], 467 * [ 468 * [0, 1, 2, 3], 469 * [0, 1, 5, 4], 470 * [1, 2, 6, 5], 471 * [2, 3, 7, 6], 472 * [3, 0, 4, 7], 473 * [4, 5, 6, 7], 474 * ] 475 * ], { 476 * strokeWidth: 3, 477 * fillOpacity: 0.6, 478 * fillColorArray: ['blue', 'red', 'yellow'], 479 * shader: { 480 * enabled:false 481 * } 482 * }); 483 * 484 * })(); 485 * 486 * </script><pre> 487 * 488 * @example 489 * var box = [-4, 4]; 490 * var view = board.create( 491 * 'view3d', 492 * [[-5, -3], [8, 8], 493 * [box, box, box]], 494 * { 495 * projection: 'parallel', 496 * trackball: { enabled: false }, 497 * depthOrder: { 498 * enabled: true 499 * }, 500 * xPlaneRear: { visible: false }, 501 * yPlaneRear: { visible: false }, 502 * zPlaneRear: { fillOpacity: 0.2 } 503 * } 504 * ); 505 * let rho = 1.6180339887; 506 * let vertexList = [ 507 * [0, -1, -rho], [0, +1, -rho], [0, -1, rho], [0, +1, rho], 508 * [1, rho, 0], [-1, rho, 0], [1, -rho, 0], [-1, -rho, 0], 509 * [-rho, 0, 1], [-rho, 0, -1], [rho, 0, 1], [rho, 0, -1] 510 * ]; 511 * let faceArray = [ 512 * [4, 1, 11], 513 * [11, 1, 0], 514 * [6, 11, 0], 515 * [0, 1, 9], 516 * [11, 10, 4], 517 * [9, 1, 5], 518 * [8, 9, 5], 519 * [5, 3, 8], 520 * [6, 10, 11], 521 * [2, 3, 10], 522 * [2, 10, 6], 523 * [8, 3, 2], 524 * [3, 4, 10], 525 * [7, 8, 2], 526 * [9, 8, 7], 527 * [0, 9, 7], 528 * [4, 3, 5], 529 * [5, 1, 4], 530 * [0, 7, 6], 531 * [7, 2, 6] 532 * ]; 533 * var ico = view.create('polyhedron3d', [vertexList, faceArray], { 534 * fillColorArray: [], 535 * fillOpacity: 1, 536 * strokeWidth: 0.1, 537 * layer: 12, 538 * shader: { 539 * enabled: true, 540 * type: 'angle', 541 * hue: 60, 542 * saturation: 90, 543 * minlightness: 60, 544 * maxLightness: 80 545 * } 546 * }); 547 * 548 * </pre><div id="JXGfea93484-96e9-4eb5-9e45-bb53d612aead" class="jxgbox" style="width: 300px; height: 300px;"></div> 549 * <script type="text/javascript"> 550 * (function() { 551 * var board = JXG.JSXGraph.initBoard('JXGfea93484-96e9-4eb5-9e45-bb53d612aead', 552 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 553 * var box = [-4, 4]; 554 * var view = board.create( 555 * 'view3d', 556 * [[-5, -3], [8, 8], 557 * [box, box, box]], 558 * { 559 * projection: 'parallel', 560 * trackball: { enabled: false }, 561 * depthOrder: { 562 * enabled: true 563 * }, 564 * xPlaneRear: { visible: false }, 565 * yPlaneRear: { visible: false }, 566 * zPlaneRear: { fillOpacity: 0.2 } 567 * } 568 * ); 569 * let rho = 1.6180339887; 570 * let vertexList = [ 571 * [0, -1, -rho], [0, +1, -rho], [0, -1, rho], [0, +1, rho], 572 * [1, rho, 0], [-1, rho, 0], [1, -rho, 0], [-1, -rho, 0], 573 * [-rho, 0, 1], [-rho, 0, -1], [rho, 0, 1], [rho, 0, -1] 574 * ]; 575 * let faceArray = [ 576 * [4, 1, 11], 577 * [11, 1, 0], 578 * [6, 11, 0], 579 * [0, 1, 9], 580 * [11, 10, 4], 581 * [9, 1, 5], 582 * [8, 9, 5], 583 * [5, 3, 8], 584 * [6, 10, 11], 585 * [2, 3, 10], 586 * [2, 10, 6], 587 * [8, 3, 2], 588 * [3, 4, 10], 589 * [7, 8, 2], 590 * [9, 8, 7], 591 * [0, 9, 7], 592 * [4, 3, 5], 593 * [5, 1, 4], 594 * [0, 7, 6], 595 * [7, 2, 6] 596 * ]; 597 * var ico = view.create('polyhedron3d', [vertexList, faceArray], { 598 * fillColorArray: [], 599 * fillOpacity: 1, 600 * strokeWidth: 0.1, 601 * layer: 12, 602 * shader: { 603 * enabled: true, 604 * type: 'angle', 605 * hue: 60, 606 * saturation: 90, 607 * minlightness: 60, 608 * maxLightness: 80 609 * } 610 * }); 611 * 612 * })(); 613 * 614 * </script><pre> 615 * 616 */ 617 JXG.createPolyhedron3D = function (board, parents, attributes) { 618 var view = parents[0], 619 i, le, 620 face, f, 621 el, 622 attr, attr_polyhedron, 623 faceList = [], 624 base = null, 625 transform = null, 626 627 polyhedron = { 628 view: view, 629 vertices: {}, 630 coords: {}, 631 coords2D: {}, 632 zIndex: {}, 633 faces: [] 634 }; 635 636 if (Type.exists(parents[1].type) && parents[1].type === Const.OBJECT_TYPE_POLYHEDRON3D) { 637 // Polyhedron from baseElement and transformations 638 base = parents[1]; 639 transform = parents[2]; 640 polyhedron.vertices = base.def.vertices; 641 polyhedron.faces = base.def.faces; 642 } else { 643 // Copy vertices into a dict 644 if (Type.isArray(parents[1])) { 645 le = parents[1].length; 646 for (i = 0; i < le; i++) { 647 polyhedron.vertices[i] = parents[1][i]; 648 } 649 } else if (Type.isObject(parents[1])) { 650 for (i in parents[1]) { 651 if (parents[1].hasOwnProperty(i)) { 652 polyhedron.vertices[i] = parents[1][i]; 653 } 654 } 655 } 656 polyhedron.faces = parents[2]; 657 } 658 659 attr_polyhedron = Type.copyAttributes(attributes, board.options, "polyhedron3d"); 660 661 console.time('polyhedron'); 662 663 view.board.suspendUpdate(); 664 // Create face3d elements 665 le = polyhedron.faces.length; 666 for (i = 0; i < le; i++) { 667 attr = Type.copyAttributes(attributes, board.options, "face3d"); 668 if (attr_polyhedron.fillcolorarray.length > 0) { 669 attr.fillcolor = attr_polyhedron.fillcolorarray[i % attr_polyhedron.fillcolorarray.length]; 670 } 671 f = polyhedron.faces[i]; 672 673 if (Type.isArray(f) && f.length === 2 && Type.isObject(f[1]) && Type.isArray(f[0])) { 674 // Handle case that face is of type [[points], {attr}] 675 Type.mergeAttr(attr, f[1]); 676 // Normalize face array, i.e. don't store attributes of that face in polyhedron 677 polyhedron.faces[i] = f[0]; 678 } 679 680 face = view.create('face3d', [polyhedron, i], attr); 681 faceList.push(face); 682 } 683 el = new JXG.Polyhedron3D(view, polyhedron, faceList, attr_polyhedron); 684 el.setParents(el); // Sets el as parent to all faces. 685 for (i = 0; i < le; i++) { 686 el.inherits.push(el.faces[i]); 687 el.addChild(el.faces[i]); 688 } 689 if (base !== null) { 690 el.addTransform(base, transform); 691 el.addParents(base); 692 } 693 view.board.unsuspendUpdate(); 694 695 console.timeEnd('polyhedron'); 696 697 return el; 698 }; 699 700 JXG.registerElement("polyhedron3d", JXG.createPolyhedron3D); 701