1 /* 2 Copyright 2008-2026 3 Matthias Ehmann, 4 Aaron Fenyes, 5 Carsten Miller, 6 Andreas Walter, 7 Alfred Wassermann 8 9 This file is part of JSXGraph. 10 11 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 12 13 You can redistribute it and/or modify it under the terms of the 14 15 * GNU Lesser General Public License as published by 16 the Free Software Foundation, either version 3 of the License, or 17 (at your option) any later version 18 OR 19 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 20 21 JSXGraph is distributed in the hope that it will be useful, 22 but WITHOUT ANY WARRANTY; without even the implied warranty of 23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 GNU Lesser General Public License for more details. 25 26 You should have received a copy of the GNU Lesser General Public License and 27 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 28 and <https://opensource.org/licenses/MIT/>. 29 */ 30 /*global JXG:true, define: true*/ 31 32 /** 33 * Create linear spaces of dimension at least one, 34 * i.e. lines and planes. 35 */ 36 import JXG from '../jxg.js'; 37 import Const from '../base/constants.js'; 38 import Type from '../utils/type.js'; 39 import Mat from '../math/math.js'; 40 import Geometry from '../math/geometry.js'; 41 import Tiling from '../math/tiling.js'; 42 43 // ----------------------- 44 // Lines 45 // ----------------------- 46 47 /** 48 * Constructor for 3D lines. 49 * @class Creates a new 3D line object. Do not use this constructor to create a 3D line. Use {@link JXG.View3D#create} with type {@link Line3D} instead. 50 * 51 * @augments JXG.GeometryElement3D 52 * @augments JXG.GeometryElement 53 * @param {View3D} view 54 * @param {Point3D|Array} point 55 * @param {Array} direction 56 * @param {Array} range 57 * @param {Object} attributes 58 * @see JXG.Board#generateName 59 */ 60 JXG.Line3D = function (view, point, direction, range, attributes) { 61 this.constructor(view.board, attributes, Const.OBJECT_TYPE_LINE3D, Const.OBJECT_CLASS_3D); 62 this.constructor3D(view, 'line3d'); 63 64 /** 65 * 3D point which - together with a direction - defines the line. 66 * @name point 67 * @memberOf Line3D 68 * @type Point3D 69 * 70 * @see Line3D#direction 71 */ 72 this.point = point; 73 74 /** 75 * Direction which - together with a point - defines the line. Array of numbers or functions (of length 3) or function 76 * returning array of length 3. 77 * 78 * @name Line3D#direction 79 * @type Array|Function 80 * @see Line3D.point 81 */ 82 this.direction = direction; 83 84 /** 85 * Spanning vector of the 3D line. Contains the evaluated coordinates from {@link direction} 86 * and {@link range}. 87 * The array has length 4, the first entry being 0. 88 * 89 * @name Line3D#vec 90 * @type {Array} 91 */ 92 this.vec = [0, 0, 0, 0]; 93 94 /** 95 * Range [r1, r2] of the line. r1, r2 can be numbers or functions. 96 * The 3D line goes from (point + r1 * direction) to (point + r2 * direction) 97 * @name Line3D#range 98 * @type Array 99 * @default [-Infinity, Infinity] 100 */ 101 this.range = range || [-Infinity, Infinity]; 102 103 /** 104 * Starting point of the 3D line 105 * @name Line3D#point1 106 * @type JXG.Point3D 107 * @private 108 */ 109 this.point1 = null; 110 111 /** 112 * End point of the 3D line 113 * @name Line3D#point2 114 * @type JXG.Point3D 115 * @private 116 */ 117 this.point2 = null; 118 119 this.board.finalizeAdding(this); 120 }; 121 JXG.Line3D.prototype = new JXG.GeometryElement(); 122 123 Type.copyPrototypeMethods(JXG.Line3D, JXG.GeometryElement3D, 'constructor3D'); 124 Type.copyMethodMap(JXG.Line3D, { 125 // TODO 126 }); 127 128 JXG.extend( 129 JXG.Line3D.prototype, 130 /** @lends JXG.Line3D.prototype */ { 131 132 /** 133 * Update the array {@link Line3D#vec} containing the homogeneous coords of the spanning vector. 134 * 135 * @name Line3D#updateCoords 136 * @function 137 * @returns {Object} Reference to Line3D object 138 * @private 139 */ 140 updateCoords: function() { 141 var i, 142 s = 0; 143 144 if ((Type.exists(this.direction.view) && this.direction.type === Const.OBJECT_TYPE_LINE3D)) { 145 // direction is another line3D object 146 this.vec = this.direction.vec.slice(); 147 } else if (Type.isFunction(this.direction)) { 148 this.vec = Type.evaluate(this.direction); 149 if (this.vec.length === 3) { 150 this.vec.unshift(0); 151 } 152 } else { 153 if (this.direction.length === 3) { 154 this.vec[0] = 0; 155 s = 1; 156 } 157 for (i = 0; i < this.direction.length; i++) { 158 this.vec[s + i] = Type.evaluate(this.direction[i]); 159 } 160 } 161 162 return this; 163 }, 164 165 /** 166 * Determine one end point of a 3D line from point, direction and range). 167 * 168 * @name Line3D#getPointCoords 169 * @param {Number|function} r Usually, one of the range borders. 170 * @private 171 * @returns {Array} Coordinates of length 4. 172 */ 173 getPointCoords: function (r) { 174 var p = [], 175 d = [], 176 r0; 177 178 p = this.point.coords; 179 d = this.vec; 180 181 // Intersect the ray - if necessary - with the cube, 182 // i.e. clamp the line. 183 r0 = Type.evaluate(r); 184 r = this.view.intersectionLineCube(p, d, r0); 185 186 // Check if r is infinite. This happens 187 // if this.vec is the zero vector. 188 if (Math.abs(r) === Infinity) { 189 r = 0; 190 } 191 return [ 192 p[0] + d[0] * r, 193 p[1] + d[1] * r, 194 p[2] + d[2] * r, 195 p[3] + d[3] * r 196 ]; 197 }, 198 199 addTransform: function (el, transform) { 200 this.point.addTransform(el.point, transform); 201 this.addTransformGeneric(el, transform); 202 203 return this; 204 }, 205 206 removeTransform: function (transform) { 207 this.point.removeTransform(transform); 208 this.removeTransformGeneric(transform); 209 210 return this; 211 }, 212 213 clearTransforms: function () { 214 this.point.clearTransformsGeneric(); 215 this.clearTransformsGeneric(); 216 217 return this; 218 }, 219 220 updateTransform: function () { 221 var c, i; 222 223 if (this.transformations.length === 0 || this.baseElement === null) { 224 return this; 225 } 226 227 if (this === this.baseElement) { 228 c = this.vec; 229 } else { 230 c = this.baseElement.vec; 231 } 232 for (i = 0; i < this.transformations.length; i++) { 233 this.transformations[i].update(); 234 c = Mat.matVecMult(this.transformations[i].matrix, c); 235 } 236 this.vec = c; 237 238 return this; 239 }, 240 241 // Already documented in JXG.GeometryElement 242 update: function () { 243 if (this.needsUpdate) { 244 this.updateCoords() 245 .updateTransform(); 246 } 247 return this; 248 }, 249 250 /** 251 * Set the 2D position of the defining points. 252 * 253 * @name Line3D#setPosition2D 254 * @function 255 * @param {JXG.Transformation} t projective 2D transformation 256 * @private 257 */ 258 setPosition2D: function (t) { 259 var j, el; 260 261 for (j = 0; j < this.parents.length; j++) { 262 // Run through defining 3D points 263 el = this.view.select(this.parents[j]); 264 if (el.elType === 'point3d' && el.element2D.draggable()) { 265 t.applyOnce(el.element2D); 266 } 267 } 268 this.endpoints[0].update(); 269 this.endpoints[1].update(); 270 }, 271 272 // Already documented in JXG.GeometryElement 273 updateRenderer: function () { 274 this.needsUpdate = false; 275 return this; 276 }, 277 278 // Already documented in element3d.js 279 projectCoords: function (p, params) { 280 var p0_coords = this.getPointCoords(0), 281 p1_coords = this.getPointCoords(1), 282 dir = [ 283 p1_coords[0] - p0_coords[0], 284 p1_coords[1] - p0_coords[1], 285 p1_coords[2] - p0_coords[2] 286 ], 287 diff = [ 288 p[0] - p0_coords[0], 289 p[1] - p0_coords[1], 290 p[2] - p0_coords[2] 291 ], 292 t = Mat.innerProduct(diff, dir) / Mat.innerProduct(dir, dir), 293 t_clamped = Math.min(Math.max(t, Type.evaluate(this.range[0])), Type.evaluate(this.range[1])), 294 c3d; 295 296 c3d = this.getPointCoords(t_clamped).slice(); 297 params[0] = t_clamped; 298 299 return c3d; 300 }, 301 302 // Already documented in element3d.js 303 projectScreenCoords: function (pScr) { 304 var r = this.range, 305 f = 100000, 306 end0, end1; 307 308 r[0] = (r[0] === Infinity) ? f : r[0]; 309 r[0] = (r[0] === -Infinity) ? -f : r[0]; 310 r[1] = (r[1] === Infinity) ? f : r[1]; 311 r[1] = (r[1] === -Infinity) ? -f : r[1]; 312 313 end0 = this.getPointCoords(r[0]); 314 end1 = this.getPointCoords(r[1]); 315 316 return this.view.projectScreenToSegment(pScr, end0, end1); 317 }, 318 319 /** 320 * Update the z-index of the line, i.e. the z-index of its midpoint. 321 * @name Line3D#updateZIndex 322 * @function 323 * @returns {Object} Reference to Line3D object 324 */ 325 updateZIndex: function() { 326 var p1 = this.endpoints[0], 327 p2 = this.endpoints[1], 328 c3d = [1, p1.X() + p2.X(), p1.Y() + p2.Y(), p1.Z() + p2.Z()]; 329 330 c3d[1] *= 0.5; 331 c3d[2] *= 0.5; 332 c3d[3] *= 0.5; 333 // this.zIndex = Mat.matVecMult(this.view.matrix3DRotShift, c3d)[3]; 334 this.zIndex = Mat.innerProduct(this.view.matrix3DRotShift[3], c3d); 335 336 return this; 337 } 338 } 339 ); 340 341 /** 342 * @class A line in 3D is given by two points, or one point and a direction vector. 343 * 344 * @description 345 * A line in 3D is given by two points, or one point and a direction vector. 346 * That is, there are the following two possibilities to create a Line3D object: 347 * <ol> 348 * <li> The 3D line is defined by two 3D points (Point3D): 349 * The points can be either existing points or coordinate arrays of 350 * the form [x, y, z]. 351 * <p> The 3D line is defined by a point (or coordinate array [x, y, z]) 352 * a direction given as array [x, y, z] and an optional range 353 * given as array [s, e]. The default value for the range is [-Infinity, Infinity]. 354 * </ol> 355 * All numbers can also be provided as functions returning a number. 356 * The case [point, array] is ambiguous, it is not clear if 'array' contains the coordinates of a point 357 * or of a direction. In that case, 'array' is interpreted as the coordinate array of a point, 358 * i.e. the line is defined by two points. 359 * 360 * @pseudo 361 * @name Line3D 362 * @augments JXG.GeometryElement3D 363 * @constructor 364 * @type JXG.Line3D 365 * @throws {Exception} If the element cannot be constructed with the given parent 366 * objects an exception is thrown. 367 * @param {JXG.Point3D,array,function_JXG.Point3D,array,function} point1,point2 First and second defining point of the line. 368 * The attributes {@link Line3D#straightFirst} and {@link Line3D#straightLast} control if the line is displayed as 369 * segment, ray or infinite line. 370 * @param {JXG.Point3D,array,function_JXG.Line3D,array,function_array,function} point,direction,range The line is defined by point, direction and range. 371 * <ul> 372 * <li> point: Point3D or array of length 3 373 * <li> direction: array of length 3 or function returning an array of numbers or function returning an array 374 * <li> range: array of length 2, elements can also be functions. Use [-Infinity, Infinity] for infinite lines. 375 * </ul> 376 * 377 * @example 378 * var bound = [-5, 5]; 379 * var view = board.create('view3d', 380 * [[-6, -3], [8, 8], 381 * [bound, bound, bound]], 382 * {}); 383 * var p = view.create('point3d', [1, 2, 2], { name:'A', size: 5 }); 384 * // Lines through 2 points 385 * var l1 = view.create('line3d', [[1, 3, 3], [-3, -3, -3]], {point1: {visible: true}, point2: {visible: true} }); 386 * var l2 = view.create('line3d', [p, l1.point1]); 387 * 388 * // Line by point, direction, range 389 * var l3 = view.create('line3d', [p, [0, 0, 1], [-2, 4]]); 390 * 391 * </pre><div id='JXG05f9baa4-6059-4502-8911-6a934f823b3d' class='jxgbox' style='width: 300px; height: 300px;'></div> 392 * <script type='text/javascript'> 393 * (function() { 394 * var board = JXG.JSXGraph.initBoard('JXG05f9baa4-6059-4502-8911-6a934f823b3d', 395 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 396 * var bound = [-5, 5]; 397 * var view = board.create('view3d', 398 * [[-6, -3], [8, 8], 399 * [bound, bound, bound]], 400 * {}); 401 * var p = view.create('point3d', [1, 2, 2], { name:'A', size: 5 }); 402 * // Lines through 2 points 403 * var l1 = view.create('line3d', [[1, 3, 3], [-3, -3, -3]], {name: 'll1', point1: {visible: true}, point2: {visible: true} }); 404 * var l2 = view.create('line3d', [p, l1.point1]); 405 * // Line by point, direction, range 406 * var l3 = view.create('line3d', [p, [0, 0, 1], [-2, 4]]); 407 * })(); 408 * 409 * </script><pre> 410 * 411 * @example 412 * var view = board.create( 413 * 'view3d', 414 * [[-6, -3], [8, 8], 415 * [[-3, 3], [-3, 3], [-3, 3]]], 416 * { 417 * depthOrder: { 418 * enabled: true 419 * }, 420 * projection: 'central', 421 * xPlaneRear: {fillOpacity: 0.2}, 422 * yPlaneRear: {fillOpacity: 0.2}, 423 * zPlaneRear: {fillOpacity: 0.2} 424 * } 425 * ); 426 * 427 * var A = view.create('point3d', [0, 0, 0], {size: 2}); 428 * var B = view.create('point3d', [2, 1, 1], {size: 2}); 429 * var C = view.create('point3d', [-2.5, 2.5, 1.5], {size: 2}); 430 * 431 * // Draggable line by two points 432 * var line1 = view.create('line3d', [A, B], { 433 * fixed: false, 434 * straightFirst: true, 435 * straightLast: true, 436 * dash: 2 437 * }); 438 * 439 * // Line by point, direction, and range 440 * var line2 = view.create('line3d', [C, [1, 0, 0], [-1, Infinity]], { 441 * strokeColor: 'blue' 442 * }); 443 * 444 * // Line by point and array 445 * var line3 = view.create('line3d', [C, [-2.5, -1, 1.5]], { 446 * point2: { visible: true}, 447 * strokeColor: 'red' 448 * }); 449 * 450 * </pre><div id="JXGc42dda18-0a72-45f2-8add-3b2ad7e10853" class="jxgbox" style="width: 300px; height: 300px;"></div> 451 * <script type="text/javascript"> 452 * (function() { 453 * var board = JXG.JSXGraph.initBoard('JXGc42dda18-0a72-45f2-8add-3b2ad7e10853', 454 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 455 * var view = board.create( 456 * 'view3d', 457 * [[-6, -3], [8, 8], 458 * [[-3, 3], [-3, 3], [-3, 3]]], 459 * { 460 * depthOrder: { 461 * enabled: true 462 * }, 463 * projection: 'central', 464 * xPlaneRear: {fillOpacity: 0.2}, 465 * yPlaneRear: {fillOpacity: 0.2}, 466 * zPlaneRear: {fillOpacity: 0.2} 467 * } 468 * ); 469 * 470 * var A = view.create('point3d', [0, 0, 0], {size: 2}); 471 * var B = view.create('point3d', [2, 1, 1], {size: 2}); 472 * var C = view.create('point3d', [-2.5, 2.5, 1.5], {size: 2}); 473 * 474 * // Draggable line by two points 475 * var line1 = view.create('line3d', [A, B], { 476 * fixed: false, 477 * straightFirst: true, 478 * straightLast: true, 479 * dash: 2 480 * }); 481 * 482 * // Line by point, direction, and range 483 * var line2 = view.create('line3d', [C, [1, 0, 0], [-1, Infinity]], { 484 * strokeColor: 'blue' 485 * }); 486 * 487 * // Line by point and array 488 * var line3 = view.create('line3d', [C, [-2.5, -1, 1.5]], { 489 * point2: { visible: true}, 490 * strokeColor: 'red' 491 * }); 492 * 493 * })(); 494 * 495 * </script><pre> 496 * 497 * @example 498 * var view = board.create( 499 * 'view3d', 500 * [[-6, -3], [8, 8], 501 * [[-3, 3], [-3, 3], [-3, 3]]], 502 * { 503 * depthOrder: { 504 * enabled: true 505 * }, 506 * projection: 'parallel', 507 * xPlaneRear: { fillOpacity: 0.2 }, 508 * yPlaneRear: { fillOpacity: 0.2 }, 509 * zPlaneRear: { fillOpacity: 0.2 } 510 * } 511 * ); 512 * 513 * 514 * var A = view.create('point3d', [-2, 0, 1], { size: 2 }); 515 * var B = view.create('point3d', [-2, 0, 2], { size: 2 }); 516 * var line1 = view.create('line3d', [A, B], { 517 * fixed: false, 518 * strokeColor: 'blue', 519 * straightFirst: true, 520 * straightLast: true 521 * }); 522 * 523 * var C = view.create('point3d', [2, 0, 1], { size: 2 }); 524 * var line2 = view.create('line3d', [C, line1, [-Infinity, Infinity]], { strokeColor: 'red' }); 525 * 526 * </pre><div id="JXGc9234445-de9b-4543-aae7-0ef2d0b540e6" class="jxgbox" style="width: 300px; height: 300px;"></div> 527 * <script type="text/javascript"> 528 * (function() { 529 * var board = JXG.JSXGraph.initBoard('JXGc9234445-de9b-4543-aae7-0ef2d0b540e6', 530 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 531 * var view = board.create( 532 * 'view3d', 533 * [[-6, -3], [8, 8], 534 * [[-3, 3], [-3, 3], [-3, 3]]], 535 * { 536 * depthOrder: { 537 * enabled: true 538 * }, 539 * projection: 'parallel', 540 * xPlaneRear: { fillOpacity: 0.2 }, 541 * yPlaneRear: { fillOpacity: 0.2 }, 542 * zPlaneRear: { fillOpacity: 0.2 } 543 * } 544 * ); 545 * 546 * 547 * var A = view.create('point3d', [-2, 0, 1], { size: 2 }); 548 * var B = view.create('point3d', [-2, 0, 2], { size: 2 }); 549 * var line1 = view.create('line3d', [A, B], { 550 * fixed: false, 551 * strokeColor: 'blue', 552 * straightFirst: true, 553 * straightLast: true 554 * }); 555 * 556 * var C = view.create('point3d', [2, 0, 1], { size: 2 }); 557 * var line2 = view.create('line3d', [C, line1, [-Infinity, Infinity]], { strokeColor: 'red' }); 558 * 559 * })(); 560 * 561 * </script><pre> 562 * 563 */ 564 JXG.createLine3D = function (board, parents, attributes) { 565 var view = parents[0], 566 attr, points, 567 point, direction, range, 568 point1, point2, endpoints, 569 el, 570 base = null, 571 transform = null; 572 573 attr = Type.copyAttributes(attributes, board.options, 'line3d'); 574 575 // In any case, parents[1] contains a point or point coordinates 576 577 if (parents[1].type === Const.OBJECT_TYPE_LINE3D && 578 Type.isTransformationOrArray(parents[2]) 579 ) { 580 base = parents[1]; 581 transform = parents[2]; 582 583 points = Type.providePoints3D( 584 view, 585 [ 586 [0, 0, 0, 0], 587 [0, 0, 0, 0] 588 ], 589 attributes, 590 'line3d', 591 ['point1', 'point2'] 592 ); 593 } 594 595 if (base === null && // No transformation 596 (Type.isPoint3D(parents[2]) || 597 ( parents.length === 3 && (Type.isArray(parents[2]) || Type.isFunction(parents[2])) ) 598 ) 599 ) { 600 // Line defined by two points; [view, point1, point2] 601 point1 = Type.providePoints3D(view, [parents[1]], attributes, 'line3d', ['point1'])[0]; 602 point2 = Type.providePoints3D(view, [parents[2]], attributes, 'line3d', ['point2'])[0]; 603 direction = function () { 604 return [0, point2.X() - point1.X(), point2.Y() - point1.Y(), point2.Z() - point1.Z()]; 605 }; 606 range = [0, 1]; // Segment by default 607 el = new JXG.Line3D(view, point1, direction, range, attr); 608 el.prepareUpdate().update(); 609 610 // Create two shadow points that are the end points of the visible line. 611 // This is of relevance if the line has straightFirst or straightLast set to true, then 612 // endpoints differ from point1, point2. 613 // In such a case, the endpoints are the intersection of the line with the cube. 614 endpoints = Type.providePoints3D( 615 view, 616 [ 617 [1, 0, 0, 0], 618 [1, 0, 0, 0] 619 ], 620 { visible: true }, 621 'line3d', 622 ['point1', 'point2'] 623 ); 624 625 /** 626 * @ignore 627 */ 628 endpoints[0].F = function () { 629 var r = 0; 630 if (el.evalVisProp('straightfirst')) { 631 r = -Infinity; 632 } 633 return el.getPointCoords(r); 634 }; 635 636 /** 637 * @ignore 638 */ 639 endpoints[1].F = function () { 640 var r = 1; 641 if (el.evalVisProp('straightlast')) { 642 r = Infinity; 643 } 644 return el.getPointCoords(r); 645 }; 646 endpoints[0].prepareUpdate().update(); 647 endpoints[1].prepareUpdate().update(); 648 649 // The 2D line is always a segment. 650 attr = el.setAttr2D(attr); 651 attr.straightfirst = false; 652 attr.straightlast = false; 653 el.element2D = view.create('segment', [endpoints[0].element2D, endpoints[1].element2D], attr); 654 el.element2D.view = view; 655 el.element2D.dump = false; 656 657 /** 658 * Shadow points that determine the visible line. 659 * This is of relevance if the line is defined by two points and has straightFirst or straightLast set to true. 660 * In such a case, the shadow points are the intersection of the line with the cube. 661 * 662 * @name JXG.Point3D.endpoints 663 * @type Array 664 * @private 665 */ 666 el.endpoints = endpoints; 667 el.addChild(endpoints[0]); 668 el.addChild(endpoints[1]); 669 // el.setParents(endpoints); 670 el.addParents([point1, point2]); 671 672 } else { 673 // Line defined by point, direction and range 674 675 // Directions are handled as arrays of length 4, i.e. with homogeneous coordinates. 676 if (base !== null) { 677 point = Type.providePoints3D(view, [[0, 0, 0]], attributes, 'line3d', ['point'])[0]; 678 direction = [0, 0, 0, 0.0001]; 679 range = parents[3] || [-Infinity, Infinity]; 680 } else if ( 681 (Type.exists(parents[2].view) && parents[2].type === Const.OBJECT_TYPE_LINE3D) || // direction given by another line 682 Type.isFunction(parents[2]) || (parents[2].length === 3) || (parents[2].length === 4) // direction given as function or array 683 ) { 684 point = Type.providePoints3D(view, [parents[1]], attributes, 'line3d', ['point'])[0]; 685 direction = parents[2]; 686 range = parents[3]; 687 } else { 688 throw new Error( 689 "JSXGraph: Can't create line3d with parents of type '" + 690 typeof parents[1] + ", " + 691 typeof parents[2] + ", " + 692 typeof parents[3] + "'." 693 ); 694 } 695 696 points = Type.providePoints3D( 697 view, 698 [ 699 [1, 0, 0, 0], 700 [1, 0, 0, 0] 701 ], 702 attributes, 703 'line3d', 704 ['point1', 'point2'] 705 ); 706 707 // Create a line3d with two dummy points 708 el = new JXG.Line3D(view, point, direction, range, attr); 709 el.prepareUpdate().update(); 710 711 // Now set the real points which define the line 712 /** 713 * @ignore 714 */ 715 points[0].F = function () { 716 return el.getPointCoords(Type.evaluate(el.range[0])); 717 }; 718 points[0].prepareUpdate().update(); 719 point1 = points[0]; 720 721 /** 722 * @ignore 723 */ 724 points[1].F = function () { 725 return el.getPointCoords(Type.evaluate(el.range[1])); 726 }; 727 points[1].prepareUpdate().update(); 728 point2 = points[1]; 729 730 attr = el.setAttr2D(attr); 731 attr.straightfirst = false; 732 attr.straightlast = false; 733 el.element2D = view.create('segment', [point1.element2D, point2.element2D], attr); 734 el.element2D.view = view; 735 el.element2D.dump = false; 736 737 /** 738 * Array of length 2 containing the endings of the Line3D element. These are the defining points, 739 * the intersections of the line with the bounding box, or the endings defined by the range. 740 * @name Line3D#endpoints 741 * @type {Array} 742 */ 743 el.endpoints = points; 744 745 el.addParents(point); 746 747 if (base !== null && transform !== null) { 748 el.addTransform(base, transform); 749 el.addParents(base); 750 } 751 } 752 753 el.addChild(el.element2D); 754 el.inherits.push(el.element2D); 755 el.element2D.addParents(el); 756 757 el.point1 = point1; 758 el.point2 = point2; 759 if (el.point1._is_new) { 760 el.addChild(el.point1); 761 delete el.point1._is_new; 762 } else { 763 el.point1.addChild(el); 764 } 765 if (el.point2._is_new) { 766 el.addChild(el.point2); 767 delete el.point2._is_new; 768 } else { 769 el.point2.addChild(el); 770 } 771 if (Type.exists(point)) { 772 if (point._is_new) { 773 el.addChild(point); 774 delete point._is_new; 775 } else { 776 point.addChild(el); 777 } 778 } 779 780 el.update(); 781 el.element2D.prepareUpdate().update().updateRenderer(); 782 return el; 783 }; 784 785 JXG.registerElement('line3d', JXG.createLine3D); 786 787 // ----------------------- 788 // Planes 789 // ----------------------- 790 791 /** 792 * Constructor for 3D planes. 793 * @class Creates a new 3D plane object. Do not use this constructor to create a 3D plane. Use {@link JXG.Board#create} with type {@link Plane3D} instead. 794 * 795 * @augments JXG.GeometryElement3D 796 * @augments JXG.GeometryElement 797 * @param {View3D} view 798 * @param {Point3D|Array} point 799 * @param {Array} direction1 800 * @param {Array} range_u 801 * @param {Array} direction2 802 * @param {Array} range_v 803 * @param {Object} attributes 804 * @see JXG.Board#generateName 805 */ 806 JXG.Plane3D = function (view, point, dir1, range_u, dir2, range_v, attributes) { 807 this.constructor(view.board, attributes, Const.OBJECT_TYPE_PLANE3D, Const.OBJECT_CLASS_3D); 808 this.constructor3D(view, 'plane3d'); 809 810 this.board.finalizeAdding(this); 811 812 /** 813 * 3D point which - together with two direction vectors - defines the plane. 814 * 815 * @name point 816 * @memberOf Plane3D 817 * @type JXG.Point3D 818 * 819 * @see Plane3D#direction1 820 * @see Plane3D#direction2 821 */ 822 this.point = point; 823 824 /** 825 * Two linearly independent vectors - together with a point - define the plane. Each of these direction vectors is an 826 * array of numbers or functions (either of length 3 or 4) or function returning array of length 3 or 4. 827 * Homogeneous coordinates of directions have the form [0, x, y, z]. 828 * 829 * @name Plane3D#direction1 830 * @type Array|Function 831 * 832 * @see Plane3D.point 833 * @see Plane3D#direction2 834 */ 835 this.direction1 = dir1; 836 837 /** 838 * Two linearly independent vectors - together with a point - define the plane. Each of these direction vectors is an 839 * array of numbers or functions (either of length 3 or 4) or function returning array of length 3 or 4. 840 * Homogeneous coordinates of directions have the form [0, x, y, z]. 841 * 842 * @type Array|Function 843 * @name Plane3D#direction2 844 * @see Plane3D.point 845 * @see Plane3D#direction1 846 */ 847 this.direction2 = dir2; 848 849 /** 850 * Range [r1, r2] of {@link direction1}. The 3D line goes from (point + r1 * direction1) to (point + r2 * direction1) 851 * @name Plane3D#range_u 852 * @type {Array} 853 * @default [-Infinity, Infinity] 854 * @default 855 */ 856 this.range_u = range_u || [-Infinity, Infinity]; 857 858 /** 859 * Range [r1, r2] of {@link direction2}. The 3D line goes from (point + r1 * direction2) to (point + r2 * direction2) 860 * @name Plane3D#range_v 861 * @type {Array} 862 * @type {Array} 863 * @default [-Infinity, Infinity] 864 */ 865 this.range_v = range_v || [-Infinity, Infinity]; 866 867 /** 868 * Spanning vector 1 of the 3D plane. Contains the evaluated coordinates from {@link direction1} and {@link range1}. 869 * and is of length 4, the first entry being 0, i.e. homogenous coordinates. 870 * 871 * @name Plane3D#vec1 872 * @type Array 873 * @private 874 * 875 * @see Plane3D#updateCoords 876 */ 877 this.vec1 = [0, 0, 0, 0]; 878 879 /** 880 * Spanning vector 2 of the 3D plane. Contains the evaluated coordinates from {@link Plane3D#direction2} and {@link Plane3D#range2} 881 * and is of length 4, the first entry being 0, i.e. homogenous coordinates. 882 * 883 * @name Plane3D#vec2 884 * @type Array 885 * @private 886 * 887 * @see Plane3D#updateCoords 888 */ 889 this.vec2 = [0, 0, 0, 0]; 890 891 /** 892 * Mesh (grid) element of the plane. 893 * 894 * @name Plane3D#mesh3d 895 * @type Mesh3D 896 * @private 897 */ 898 this.mesh3d = null; 899 900 /** 901 * Normal vector of the plane. Left hand side of the Hesse normal form. 902 * @name Plane3D#normal 903 * @type Array 904 * @private 905 * 906 * @see Plane3D.updateNormal 907 * 908 */ 909 this.normal = [0, 0, 0, 0]; 910 911 /** 912 * Right hand side of the Hesse normal form. 913 * @name Plane3D#d 914 * @type Array 915 * @private 916 * 917 * @see Plane3D.updateNormal 918 * 919 */ 920 this.d = 0; 921 922 this.updateCoords(); 923 this.updateNormal(); 924 }; 925 926 JXG.Plane3D.prototype = new JXG.GeometryElement(); 927 928 Type.copyPrototypeMethods(JXG.Plane3D, JXG.GeometryElement3D, 'constructor3D'); 929 Type.copyMethodMap(JXG.Plane3D, { 930 // TODO 931 }); 932 933 JXG.extend( 934 JXG.Plane3D.prototype, 935 /** @lends JXG.Plane3D.prototype */ { 936 937 /** 938 * Get coordinate array [1, x, y, z] of a point on the plane for parameters (u, v). 939 * 940 * @name Plane3D#F 941 * @function 942 * @param {Number} u 943 * @param {Number} v 944 * @returns Array of length 3. 945 */ 946 F: function (u, v) { 947 var i, v1, v2, l1, l2; 948 949 v1 = this.vec1.slice(); 950 v2 = this.vec2.slice(); 951 l1 = Mat.norm(v1, 4); 952 l2 = Mat.norm(v2, 4); 953 for (i = 1; i < 4; i++) { 954 v1[i] /= l1; 955 v2[i] /= l2; 956 } 957 958 return [ 959 1, 960 this.point.X() + u * v1[0] + v * v2[0], 961 this.point.Y() + u * v1[1] + v * v2[1], 962 this.point.Z() + u * v1[2] + v * v2[2] 963 ]; 964 }, 965 966 /** 967 * Get x-coordinate of a point on the plane for parameters (u, v). 968 * 969 * @name Plane3D#X 970 * @function 971 * @param {Number} u 972 * @param {Number} v 973 * @returns Number 974 */ 975 X: function(u, v) { 976 return this.F(u, v)[1]; 977 }, 978 979 /** 980 * Get y-coordinate of a point on the plane for parameters (u, v). 981 * 982 * @name Plane3D#Y 983 * @function 984 * @param {Number} u 985 * @param {Number} v 986 * @returns Number 987 */ 988 Y: function(u, v) { 989 return this.F(u, v)[2]; 990 }, 991 992 /** 993 * Get z-coordinate of a point on the plane for parameters (u, v). 994 * 995 * @name Plane3D#Z 996 * @function 997 * @param {Number} u 998 * @param {Number} v 999 * @returns Number 1000 */ 1001 Z: function(u, v) { 1002 return this.F(u, v)[3]; 1003 }, 1004 1005 /** 1006 * Update the arrays {@link JXG.Plane3D#vec1} and {@link JXG.Plane3D#vec1} containing the homogeneous coords of the spanning vectors. 1007 * 1008 * @name Plane3D#updateCoords 1009 * @function 1010 * @returns {Object} Reference to Plane3D object 1011 * @private 1012 */ 1013 updateCoords: function() { 1014 var i, s; 1015 1016 if (Type.exists(this.direction1.view) && this.direction1.type === Const.OBJECT_TYPE_LINE3D) { 1017 this.vec1 = this.direction1.vec.slice(); 1018 } else if (Type.isFunction(this.direction1)) { 1019 this.vec1 = Type.evaluate(this.direction1); 1020 if (this.vec1.length === 3) { 1021 this.vec1.unshift(0); 1022 } 1023 } else { 1024 s = 0; 1025 if (this.direction1.length === 3) { 1026 this.vec1[0] = 0; 1027 s = 1; 1028 } 1029 for (i = 0; i < this.direction1.length; i++) { 1030 this.vec1[s + i] = Type.evaluate(this.direction1[i]); 1031 } 1032 } 1033 1034 if (Type.exists(this.direction2.view) && this.direction2.type === Const.OBJECT_TYPE_LINE3D) { 1035 this.vec2 = this.direction2.vec.slice(); 1036 } else if (Type.isFunction(this.direction2)) { 1037 this.vec2 = Type.evaluate(this.direction2); 1038 if (this.vec2.length === 3) { 1039 this.vec2.unshift(0); 1040 } 1041 } else { 1042 s = 0; 1043 if (this.direction2.length === 3) { 1044 this.vec2[0] = 0; 1045 s = 1; 1046 } 1047 for (i = 0; i < this.direction2.length; i++) { 1048 this.vec2[s + i] = Type.evaluate(this.direction2[i]); 1049 } 1050 } 1051 1052 return this; 1053 }, 1054 1055 /** 1056 * Update the Hesse normal form of the plane, i.e. update normal vector and right hand side. 1057 * Updates also {@link vec1} and {@link vec2}. 1058 * 1059 * @name Plane3D#updateNormal 1060 * @function 1061 * @returns {Object} Reference to the Plane3D object 1062 * @private 1063 * @example 1064 * plane.updateNormal(); 1065 * 1066 */ 1067 updateNormal: function () { 1068 var i, len; 1069 1070 if (!this.needsUpdate) { 1071 // Extraordinary update, conflicts with rotating of box and plane transformations 1072 // this.updateCoords(); 1073 } 1074 1075 this.normal = Mat.crossProduct(this.vec1.slice(1), this.vec2.slice(1)); 1076 1077 len = Mat.norm(this.normal); 1078 if (Math.abs(len) > Mat.eps * Mat.eps) { 1079 for (i = 0; i < 3; i++) { 1080 this.normal[i] /= len; 1081 } 1082 } 1083 this.normal.unshift(0); 1084 this.d = Mat.innerProduct(this.point.coords, this.normal, 4); 1085 1086 return this; 1087 }, 1088 1089 // Already documented in element3d.js 1090 updateDataArray: function () { 1091 var s1, e1, s2, e2, c2d, l1, l2, 1092 planes = ['xPlaneRear', 'yPlaneRear', 'zPlaneRear'], // Must be ordered x, y, z 1093 points = [], 1094 v1 = [0, 0, 0], 1095 v2 = [0, 0, 0], 1096 q = [0, 0, 0], 1097 p = [0, 0, 0], 1098 eps = 1.e-12, 1099 d, i, j, a, b, first, pos, pos_akt, 1100 view = this.view; 1101 1102 this.dataX = []; 1103 this.dataY = []; 1104 1105 this.updateNormal(); 1106 1107 // Infinite plane 1108 if ( 1109 this.elType !== 'axisplane3d' && 1110 view.defaultAxes && 1111 Type.evaluate(this.range_u[0]) === -Infinity && 1112 Type.evaluate(this.range_u[1]) === Infinity && 1113 Type.evaluate(this.range_v[0]) === -Infinity && 1114 Type.evaluate(this.range_v[1]) === Infinity 1115 ) { 1116 // Determine the intersections of the plane with 1117 // the view bbox3d. 1118 // 1119 // Start with the rear plane. 1120 // For each face of the bbox3d we determine two points 1121 // which are the end points of the intersecting line 1122 // between the plane and a face of bbox3d. 1123 // We start with the three rear planes (set in planes[] above) 1124 for (j = 0; j < planes.length; j++) { 1125 p = view.intersectionPlanePlane(this, view.defaultAxes[planes[j]]); 1126 if (p[0] !== false && p[1] !== false) { 1127 // This test is necessary to filter out intersection lines which are 1128 // identical to intersections of axis planes (they would occur twice), 1129 // i.e. edges of bbox3d. 1130 for (i = 0; i < points.length; i++) { 1131 if ( 1132 (Geometry.distance(p[0], points[i][0], 4) < eps && 1133 Geometry.distance(p[1], points[i][1], 4) < eps) || 1134 (Geometry.distance(p[0], points[i][1], 4) < eps && 1135 Geometry.distance(p[1], points[i][0], 4) < eps) 1136 ) { 1137 break; 1138 } 1139 } 1140 if (i === points.length) { 1141 points.push(p.slice()); 1142 } 1143 } 1144 1145 // Take a point on the corresponding front plane of bbox3d. 1146 p = [1, 0, 0, 0]; 1147 p[j + 1] = view.bbox3D[j][1]; 1148 1149 // Use the Hesse normal form of front plane to intersect it with the plane 1150 // d is the rhs of the Hesse normal form of the front plane. 1151 d = Mat.innerProduct(p, view.defaultAxes[planes[j]].normal, 4); 1152 p = view.intersectionPlanePlane(this, view.defaultAxes[planes[j]], d); 1153 1154 if (p[0] !== false && p[1] !== false) { 1155 // Do the same test as above 1156 for (i = 0; i < points.length; i++) { 1157 // Same test for edges of bbox3d as above 1158 if ( 1159 (Geometry.distance(p[0], points[i][0], 4) < eps && 1160 Geometry.distance(p[1], points[i][1], 4) < eps) || 1161 (Geometry.distance(p[0], points[i][1], 4) < eps && 1162 Geometry.distance(p[1], points[i][0], 4) < eps) 1163 ) { 1164 break; 1165 } 1166 } 1167 if (i === points.length) { 1168 points.push(p.slice()); 1169 } 1170 } 1171 } 1172 1173 // Handle the case that the plane does not intersect bbox3d at all. 1174 if (points.length === 0) { 1175 return { X: this.dataX, Y: this.dataY }; 1176 } 1177 1178 // Concatenate the intersection points to a polygon. 1179 // If all went well, each intersection should appear 1180 // twice in the list. 1181 first = 0; 1182 pos = first; 1183 i = 0; 1184 do { 1185 p = points[pos][i]; 1186 if (p.length === 4) { 1187 c2d = view.project3DTo2D(p); 1188 this.dataX.push(c2d[1]); 1189 this.dataY.push(c2d[2]); 1190 } 1191 i = (i + 1) % 2; 1192 p = points[pos][i]; 1193 1194 pos_akt = pos; 1195 for (j = 0; j < points.length; j++) { 1196 if (j !== pos && Geometry.distance(p, points[j][0]) < eps) { 1197 pos = j; 1198 i = 0; 1199 break; 1200 } 1201 if (j !== pos && Geometry.distance(p, points[j][1]) < eps) { 1202 pos = j; 1203 i = 1; 1204 break; 1205 } 1206 } 1207 if (pos === pos_akt) { 1208 console.log('Error plane3d update: did not find next', pos); 1209 break; 1210 } 1211 } while (pos !== first); 1212 1213 c2d = view.project3DTo2D(points[first][0]); 1214 this.dataX.push(c2d[1]); 1215 this.dataY.push(c2d[2]); 1216 } else { 1217 // 3D bounded flat 1218 s1 = Type.evaluate(this.range_u[0]); 1219 e1 = Type.evaluate(this.range_u[1]); 1220 s2 = Type.evaluate(this.range_v[0]); 1221 e2 = Type.evaluate(this.range_v[1]); 1222 1223 q = this.point.coords; 1224 v1 = this.vec1.slice(); 1225 v2 = this.vec2.slice(); 1226 l1 = Mat.norm(v1, 4); 1227 l2 = Mat.norm(v2, 4); 1228 for (i = 1; i < 4; i++) { 1229 v1[i] /= l1; 1230 v2[i] /= l2; 1231 } 1232 1233 for (j = 0; j < 4; j++) { 1234 switch (j) { 1235 case 0: 1236 a = s1; 1237 b = s2; 1238 break; 1239 case 1: 1240 a = e1; 1241 b = s2; 1242 break; 1243 case 2: 1244 a = e1; 1245 b = e2; 1246 break; 1247 case 3: 1248 a = s1; 1249 b = e2; 1250 } 1251 for (i = 0; i < 4; i++) { 1252 p[i] = q[i] + a * v1[i] + b * v2[i]; 1253 } 1254 c2d = view.project3DTo2D(p); 1255 this.dataX.push(c2d[1]); 1256 this.dataY.push(c2d[2]); 1257 } 1258 // Close the curve 1259 this.dataX.push(this.dataX[0]); 1260 this.dataY.push(this.dataY[0]); 1261 } 1262 return { X: this.dataX, Y: this.dataY }; 1263 }, 1264 1265 // Already documented in element3d.js 1266 addTransform: function (el, transform) { 1267 this.addTransformGeneric(el, transform); 1268 this.point.addTransform(el.point, transform); 1269 return this; 1270 }, 1271 1272 removeTransform: function (transform) { 1273 this.removeTransformGeneric(transform); 1274 this.point.removeTransform(transform); 1275 1276 return this; 1277 }, 1278 1279 clearTransforms: function () { 1280 this.clearTransformsGeneric(); 1281 this.point.clearTransformsGeneric(); 1282 1283 return this; 1284 }, 1285 1286 // Already documented in element3d.js 1287 updateTransform: function () { 1288 var c1, c2, i; 1289 1290 if (this.transformations.length === 0 || this.baseElement === null) { 1291 return this; 1292 } 1293 1294 if (this === this.baseElement) { 1295 c1 = this.vec1; 1296 c2 = this.vec2; 1297 } else { 1298 c1 = this.baseElement.vec1; 1299 c2 = this.baseElement.vec2; 1300 } 1301 1302 for (i = 0; i < this.transformations.length; i++) { 1303 this.transformations[i].update(); 1304 c1 = Mat.matVecMult(this.transformations[i].matrix, c1); 1305 c2 = Mat.matVecMult(this.transformations[i].matrix, c2); 1306 } 1307 this.vec1 = c1; 1308 this.vec2 = c2; 1309 1310 return this; 1311 }, 1312 1313 // Already documented in element3d.js 1314 update: function () { 1315 if (this.needsUpdate) { 1316 this.updateCoords() 1317 .updateTransform(); 1318 } 1319 return this; 1320 }, 1321 1322 // Already documented in element3d.js 1323 updateRenderer: function () { 1324 this.needsUpdate = false; 1325 1326 return this; 1327 }, 1328 1329 // Already documented in element3d.js 1330 // projectScreenCoords: function (pScr, params, cyclic) { 1331 // if (params.length === 0) { 1332 // params.unshift( 1333 // 0.5 * (this.range_u[0] + this.range_u[1]), 1334 // 0.5 * (this.range_v[0] + this.range_v[1]) 1335 // ); 1336 // } 1337 // return Geometry.projectScreenCoordsToParametric(pScr, this, params, cyclic); 1338 // }, 1339 1340 // Already documented in element3d.js 1341 projectCoords: function (p, params) { 1342 return Geometry.projectCoordsToParametric(p, this, 2, params); 1343 } 1344 } 1345 ); 1346 1347 /** 1348 * @class A 3D plane is defined either by a point and two linearly independent vectors, or by three points. 1349 * 1350 * @description 1351 * A 3D plane is defined either by a point and two linearly independent vectors, or by three points. 1352 * In the first case, the parameters are a 3D point (or a coordinate array) and two vectors (arrays). 1353 * In the second case, the parameters consist of three 3D points (given as points or coordinate arrays). 1354 * In order to distinguish the two cases, in the latter case (three points), the additional attribute {@link Plane3D#threePoints} 1355 * has to be supplied if both, the second point and the third point, are given as arrays or functions. Otherwise, it would not be 1356 * clear if the input arrays have to be interpreted as points or directions. 1357 * <p> 1358 * All coordinate arrays can be supplied as functions returning a coordinate array. 1359 * 1360 * @pseudo 1361 * @name Plane3D 1362 * @augments JXG.GeometryElement3D 1363 * @constructor 1364 * @throws {Exception} If the element cannot be constructed with the given parent 1365 * objects an exception is thrown. 1366 * 1367 * @param {JXG.Point3D,array,function_JXG.Line3D,array,function_JXG.Line3D,array,function_array,function_array,function} point,direction1,direction2,[range1],[range2] The plane is defined by point, direction1, direction2, range1, and range2. 1368 * <ul> 1369 * <li> point: Point3D or array of length 3 1370 * <li> direction1: line3d element or array of length 3 or function returning an array of numbers or function returning an array 1371 * <li> direction2: line3d element or array of length 3 or function returning an array of numbers or function returning an array 1372 * <li> range1: array of length 2, elements can also be functions. Use [-Infinity, Infinity] for infinite lines. 1373 * <li> range2: array of length 2, elements can also be functions. Use [-Infinity, Infinity] for infinite lines. 1374 * </ul> 1375 * @param {JXG.Point3D,array,function_JXG.Point3D,array,function_JXG.Point3D,array,function} point1,point2,point3 The plane is defined by three points. 1376 * @type JXG.Plane3D 1377 * 1378 * @example 1379 * var view = board.create( 1380 * 'view3d', 1381 * [[-6, -3], [8, 8], 1382 * [[-3, 3], [-3, 3], [-3, 3]]], 1383 * { 1384 * depthOrder: { 1385 * enabled: true 1386 * }, 1387 * projection: 'central', 1388 * xPlaneRear: {fillOpacity: 0.2}, 1389 * yPlaneRear: {fillOpacity: 0.2}, 1390 * zPlaneRear: {fillOpacity: 0.2} 1391 * } 1392 * ); 1393 * 1394 * var A = view.create('point3d', [-2, 0, 1], {size: 2}); 1395 * 1396 * // Infinite Plane by point and two directions 1397 * var plane = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0], [-Infinity, Infinity], [-Infinity, Infinity]]); 1398 * 1399 * </pre><div id="JXG69f491ef-d7c7-4105-a962-86a588fbd23b" class="jxgbox" style="width: 300px; height: 300px;"></div> 1400 * <script type="text/javascript"> 1401 * (function() { 1402 * var board = JXG.JSXGraph.initBoard('JXG69f491ef-d7c7-4105-a962-86a588fbd23b', 1403 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 1404 * var view = board.create( 1405 * 'view3d', 1406 * [[-6, -3], [8, 8], 1407 * [[-3, 3], [-3, 3], [-3, 3]]], 1408 * { 1409 * depthOrder: { 1410 * enabled: true 1411 * }, 1412 * projection: 'central', 1413 * xPlaneRear: {fillOpacity: 0.2}, 1414 * yPlaneRear: {fillOpacity: 0.2}, 1415 * zPlaneRear: {fillOpacity: 0.2} 1416 * } 1417 * ); 1418 * 1419 * var A = view.create('point3d', [-2, 0, 1], {size: 2}); 1420 * 1421 * // Infinite Plane by point and two directions 1422 * var plane = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0], [-Infinity, Infinity], [-Infinity, Infinity]]); 1423 * 1424 * })(); 1425 * 1426 * </script><pre> 1427 * 1428 * @example 1429 * var view = board.create( 1430 * 'view3d', 1431 * [[-6, -3], [8, 8], 1432 * [[-3, 3], [-3, 3], [-3, 3]]], 1433 * { 1434 * depthOrder: { 1435 * enabled: true 1436 * }, 1437 * projection: 'central', 1438 * xPlaneRear: {fillOpacity: 0.2}, 1439 * yPlaneRear: {fillOpacity: 0.2}, 1440 * zPlaneRear: {fillOpacity: 0.2} 1441 * } 1442 * ); 1443 * 1444 * var A = view.create('point3d', [-2, 0, 1], {size: 2}); 1445 * 1446 * // Finite Plane by point and two directions 1447 * var plane1 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0], [-2, 2], [-2, 2]]); 1448 * var plane2 = view.create('plane3d', [[0, 0, -1], [1, 0, 0], [0, 1, 0], [-2, 2], [-2, 2]], { 1449 * mesh3d: { visible: true }, 1450 * point: {visible: true, name: "B", fixed: false} 1451 * }); 1452 * 1453 * </pre><div id="JXGea9dda1b-748b-4ed3-b4b3-57e310bd8141" class="jxgbox" style="width: 300px; height: 300px;"></div> 1454 * <script type="text/javascript"> 1455 * (function() { 1456 * var board = JXG.JSXGraph.initBoard('JXGea9dda1b-748b-4ed3-b4b3-57e310bd8141', 1457 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 1458 * var view = board.create( 1459 * 'view3d', 1460 * [[-6, -3], [8, 8], 1461 * [[-3, 3], [-3, 3], [-3, 3]]], 1462 * { 1463 * depthOrder: { 1464 * enabled: true 1465 * }, 1466 * projection: 'central', 1467 * xPlaneRear: {fillOpacity: 0.2}, 1468 * yPlaneRear: {fillOpacity: 0.2}, 1469 * zPlaneRear: {fillOpacity: 0.2} 1470 * } 1471 * ); 1472 * 1473 * var A = view.create('point3d', [-2, 0, 1], {size: 2}); 1474 * 1475 * // Finite Plane by point and two directions 1476 * var plane1 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0], [-2, 2], [-2, 2]]); 1477 * var plane2 = view.create('plane3d', [[0, 0, -1], [1, 0, 0], [0, 1, 0], [-2, 2], [-2, 2]], { 1478 * mesh3d: { visible: true }, 1479 * point: {visible: true, name: "B", fixed: false} 1480 * }); 1481 * 1482 * })(); 1483 * 1484 * </script><pre> 1485 * @example 1486 * var view = board.create( 1487 * 'view3d', 1488 * [[-6, -3], [8, 8], 1489 * [[-3, 3], [-3, 3], [-3, 3]]], 1490 * { 1491 * depthOrder: { 1492 * enabled: true 1493 * }, 1494 * projection: 'central', 1495 * xPlaneRear: { visible: false, fillOpacity: 0.2 }, 1496 * yPlaneRear: { visible: false, fillOpacity: 0.2 }, 1497 * zPlaneRear: { fillOpacity: 0.2 } 1498 * } 1499 * ); 1500 * 1501 * var A = view.create('point3d', [-2, 0, 1], { size: 2 }); 1502 * 1503 * var line1 = view.create('line3d', [A, [0, 0, 1], [-Infinity, Infinity]], { strokeColor: 'blue' }); 1504 * var line2 = view.create('line3d', [A, [1, 1, 0], [-Infinity, Infinity]], { strokeColor: 'blue' }); 1505 * 1506 * // Plane by point and two lines 1507 * var plane2 = view.create('plane3d', [A, line1, line2], { 1508 * fillColor: 'blue' 1509 * }); 1510 * 1511 * </pre><div id="JXG8bc6e266-e27c-4ffa-86a2-8076f4069573" class="jxgbox" style="width: 300px; height: 300px;"></div> 1512 * <script type="text/javascript"> 1513 * (function() { 1514 * var board = JXG.JSXGraph.initBoard('JXG8bc6e266-e27c-4ffa-86a2-8076f4069573', 1515 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 1516 * var view = board.create( 1517 * 'view3d', 1518 * [[-6, -3], [8, 8], 1519 * [[-3, 3], [-3, 3], [-3, 3]]], 1520 * { 1521 * depthOrder: { 1522 * enabled: true 1523 * }, 1524 * projection: 'central', 1525 * xPlaneRear: { visible: false, fillOpacity: 0.2 }, 1526 * yPlaneRear: { visible: false, fillOpacity: 0.2 }, 1527 * zPlaneRear: { fillOpacity: 0.2 } 1528 * } 1529 * ); 1530 * 1531 * var A = view.create('point3d', [-2, 0, 1], { size: 2 }); 1532 * 1533 * var line1 = view.create('line3d', [A, [0, 0, 1], [-Infinity, Infinity]], { strokeColor: 'blue' }); 1534 * var line2 = view.create('line3d', [A, [1, 1, 0], [-Infinity, Infinity]], { strokeColor: 'blue' }); 1535 * 1536 * // Plane by point and two lines 1537 * var plane2 = view.create('plane3d', [A, line1, line2], { 1538 * fillColor: 'blue' 1539 * }); 1540 * 1541 * })(); 1542 * 1543 * </script><pre> 1544 * 1545 * @example 1546 * var view = board.create( 1547 * 'view3d', 1548 * [[-6, -3], [8, 8], 1549 * [[-3, 3], [-3, 3], [-3, 3]]], 1550 * { 1551 * depthOrder: { 1552 * enabled: true 1553 * }, 1554 * projection: 'central', 1555 * xPlaneRear: {fillOpacity: 0.2}, 1556 * yPlaneRear: {fillOpacity: 0.2}, 1557 * zPlaneRear: {fillOpacity: 0.2} 1558 * } 1559 * ); 1560 * 1561 * var A = view.create('point3d', [0, 0, 1], {size: 2}); 1562 * var B = view.create('point3d', [2, 2, 1], {size: 2}); 1563 * var C = view.create('point3d', [-2, 0, 1], {size: 2}); 1564 * 1565 * // Plane by three points 1566 * var plane = view.create('plane3d', [A, B, C], { 1567 * fillColor: 'blue' 1568 * }); 1569 * 1570 * </pre><div id="JXG139100df-3ece-4cd1-b34f-28b5b3105106" class="jxgbox" style="width: 300px; height: 300px;"></div> 1571 * <script type="text/javascript"> 1572 * (function() { 1573 * var board = JXG.JSXGraph.initBoard('JXG139100df-3ece-4cd1-b34f-28b5b3105106', 1574 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 1575 * var view = board.create( 1576 * 'view3d', 1577 * [[-6, -3], [8, 8], 1578 * [[-3, 3], [-3, 3], [-3, 3]]], 1579 * { 1580 * depthOrder: { 1581 * enabled: true 1582 * }, 1583 * projection: 'central', 1584 * xPlaneRear: {fillOpacity: 0.2}, 1585 * yPlaneRear: {fillOpacity: 0.2}, 1586 * zPlaneRear: {fillOpacity: 0.2} 1587 * } 1588 * ); 1589 * 1590 * var A = view.create('point3d', [0, 0, 1], {size: 2}); 1591 * var B = view.create('point3d', [2, 2, 1], {size: 2}); 1592 * var C = view.create('point3d', [-2, 0, 1], {size: 2}); 1593 * 1594 * // Plane by three points 1595 * var plane = view.create('plane3d', [A, B, C], { 1596 * fillColor: 'blue' 1597 * }); 1598 * 1599 * })(); 1600 * 1601 * </script><pre> 1602 * 1603 * @example 1604 * var view = board.create( 1605 * 'view3d', 1606 * [[-6, -3], [8, 8], 1607 * [[-3, 3], [-3, 3], [-3, 3]]], 1608 * { 1609 * depthOrder: { 1610 * enabled: true 1611 * }, 1612 * projection: 'central', 1613 * xPlaneRear: {fillOpacity: 0.2}, 1614 * yPlaneRear: {fillOpacity: 0.2}, 1615 * zPlaneRear: {fillOpacity: 0.2} 1616 * } 1617 * ); 1618 * 1619 * var A = view.create('point3d', [-2, 0, 1], {size: 2}); 1620 * 1621 * // Infinite Plane by two directions, 1622 * // range1 = range2 = [-Infinity, Infinity] 1623 * var plane1 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0]], { 1624 * fillColor: 'blue', 1625 * }); 1626 * 1627 * // Infinite Plane by three points, 1628 * var plane2 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0]], { 1629 * threePoints: true, 1630 * fillColor: 'red', 1631 * point2: {visible: true}, 1632 * point3: {visible: true} 1633 * }); 1634 * 1635 * </pre><div id="JXGf31b9666-0c2e-45e7-a186-ae2c07b6bdb8" class="jxgbox" style="width: 300px; height: 300px;"></div> 1636 * <script type="text/javascript"> 1637 * (function() { 1638 * var board = JXG.JSXGraph.initBoard('JXGf31b9666-0c2e-45e7-a186-ae2c07b6bdb8', 1639 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 1640 * var view = board.create( 1641 * 'view3d', 1642 * [[-6, -3], [8, 8], 1643 * [[-3, 3], [-3, 3], [-3, 3]]], 1644 * { 1645 * depthOrder: { 1646 * enabled: true 1647 * }, 1648 * projection: 'central', 1649 * xPlaneRear: {fillOpacity: 0.2}, 1650 * yPlaneRear: {fillOpacity: 0.2}, 1651 * zPlaneRear: {fillOpacity: 0.2} 1652 * } 1653 * ); 1654 * 1655 * var A = view.create('point3d', [-2, 0, 1], {size: 2}); 1656 * 1657 * // Infinite Plane by two directions, 1658 * // range1 = range2 = [-Infinity, Infinity] 1659 * var plane1 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0]], { 1660 * fillColor: 'blue', 1661 * }); 1662 * 1663 * // Infinite Plane by three points, 1664 * var plane2 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0]], { 1665 * threePoints: true, 1666 * fillColor: 'red', 1667 * point2: {visible: true}, 1668 * point3: {visible: true} 1669 * }); 1670 * 1671 * })(); 1672 * 1673 * </script><pre> 1674 * 1675 */ 1676 JXG.createPlane3D = function (board, parents, attributes) { 1677 var view = parents[0], 1678 attr, 1679 point, point2, point3, 1680 dir1, dir2, range_u, range_v, 1681 el, mesh3d, 1682 surface, coords, 1683 su, sv, type, tiling, 1684 m, ma, mi, ma_a, mi_a, s, v, 1685 ru0, ru1, rv0, rv1, 1686 // bb, size, 1687 base = null, 1688 transform = null; 1689 1690 attr = Type.copyAttributes(attributes, board.options, 'plane3d'); 1691 if (//parents.length === 4 && 1692 // () 1693 attr.threepoints || Type.isPoint3D(parents[2]) || Type.isPoint3D(parents[3]) 1694 ) { 1695 // Three points 1696 point = Type.providePoints3D(view, [parents[1]], attributes, 'plane3d', ['point1'])[0]; 1697 point2 = Type.providePoints3D(view, [parents[2]], attributes, 'plane3d', ['point2'])[0]; 1698 point3 = Type.providePoints3D(view, [parents[3]], attributes, 'plane3d', ['point3'])[0]; 1699 dir1 = function() { 1700 return [point2.X() - point.X(), point2.Y() - point.Y(), point2.Z() - point.Z()]; 1701 }; 1702 dir2 = function() { 1703 return [point3.X() - point.X(), point3.Y() - point.Y(), point3.Z() - point.Z()]; 1704 }; 1705 range_u = parents[4] || [-Infinity, Infinity]; 1706 range_v = parents[5] || [-Infinity, Infinity]; 1707 } else { 1708 if (parents[1].type === Const.OBJECT_TYPE_PLANE3D && 1709 Type.isTransformationOrArray(parents[2]) 1710 ) { 1711 // Plane + transformation 1712 base = parents[1]; 1713 transform = parents[2]; 1714 1715 point = Type.providePoints3D(view, [[0, 0, 0, 0]], attributes, 'plane3d', ['point'])[0]; 1716 dir1 = [0, 0.0001, 0, 0]; 1717 dir2 = [0, 0, 0.0001, 0]; 1718 range_u = parents[3] || [-Infinity, Infinity]; 1719 range_v = parents[4] || [-Infinity, Infinity]; 1720 } else { 1721 // Point, direction and ranges 1722 point = Type.providePoints3D(view, [parents[1]], attributes, 'plane3d', ['point'])[0]; 1723 dir1 = parents[2]; 1724 dir2 = parents[3]; 1725 range_u = parents[4] || [-Infinity, Infinity]; 1726 range_v = parents[5] || [-Infinity, Infinity]; 1727 } 1728 if (point === false) { 1729 throw new Error( 1730 "JSXGraph: Can't create plane3d with first parent of type '" + typeof parents[1] + 1731 "'." + 1732 "\nPossible first parent types are: point3d, array of length 3, function returning an array of length 3." 1733 ); 1734 } 1735 if ((base !== null && parents < 3) || (base === null && parents.length < 4)) { 1736 throw new Error( 1737 "JSXGraph: Can't create plane3d with parents of type '" + 1738 typeof parents[1] + ", " + 1739 typeof parents[2] + ", " + 1740 typeof parents[3] + ", " + 1741 typeof parents[4] + ", " + 1742 typeof parents[5] + "'." 1743 ); 1744 } 1745 } 1746 1747 el = new JXG.Plane3D(view, point, dir1, range_u, dir2, range_v, attr); 1748 point.addChild(el); 1749 1750 type = el.evalVisProp('type'); 1751 tiling = el.evalVisProp('tiling'); 1752 1753 // Path filled with gradient color 1754 attr = el.setAttr2D(attr); 1755 el.element2D = view.create('curve', [[], []], attr); 1756 el.element2D.view = view; 1757 el.element2D.dump = false; 1758 1759 if (base !== null && transform !== null) { 1760 el.addTransform(base, transform); 1761 el.addParents(base); 1762 } 1763 1764 /** 1765 * @class 1766 * @ignore 1767 */ 1768 el.element2D.updateDataArray = function () { 1769 var ret = el.updateDataArray(); 1770 this.dataX = ret.X; 1771 this.dataY = ret.Y; 1772 }; 1773 el.addChild(el.element2D); 1774 el.inherits.push(el.element2D); 1775 el.element2D.setParents(el); 1776 1777 if ( 1778 Math.abs(el.range_u[0]) !== Infinity && 1779 Math.abs(el.range_u[1]) !== Infinity && 1780 Math.abs(el.range_v[0]) !== Infinity && 1781 Math.abs(el.range_v[1]) !== Infinity 1782 ) { 1783 1784 if (type === 'wireframe') { 1785 // Uses mesh3d 1786 attr = Type.copyAttributes(attr.mesh3d, board.options, 'mesh3d'); 1787 mesh3d = view.create('mesh3d', [ 1788 function () { 1789 return point.coords; 1790 }, 1791 // dir1, dir2, range_u, range_v 1792 function() { return el.vec1; }, 1793 function() { return el.vec2; }, 1794 el.range_u, 1795 el.range_v 1796 ], attr); 1797 el.mesh3d = mesh3d; 1798 el.addChild(mesh3d); 1799 // el.inherits.push(mesh3d); // TODO Does not work 1800 el.element2D.inherits.push(mesh3d); // Does work - instead 1801 mesh3d.setParents(el); 1802 el.mesh3d.view = view; 1803 } else { 1804 su = el.evalVisProp('stepsu'); 1805 sv = el.evalVisProp('stepsv'); 1806 1807 // Eliminate the call to the expensive el.updateDataArray(); 1808 el.element2D.updateDataArray = function() {}; 1809 1810 ru0 = el.range_u[0]; 1811 ru1 = el.range_u[1]; 1812 rv0 = el.range_v[0]; 1813 rv1 = el.range_v[1]; 1814 1815 // // Attempt to handle infinite planes 1816 // bb = el.view.bbox3D; 1817 // size = 0.5 * Math.sqrt((bb[0][0] - bb[0][1])**2 + (bb[1][0] - bb[1][1])**2 + (bb[2][0] - bb[2][1])**2); 1818 // ru0 = (Math.abs(ru0) === Infinity) ? -size : ru0; 1819 // ru1 = (Math.abs(ru1) === Infinity) ? size : ru1; 1820 // rv0 = (Math.abs(rv0) === Infinity) ? -size : rv0; 1821 // rv1 = (Math.abs(rv1) === Infinity) ? size : rv1; 1822 1823 if (tiling === 'triangle') { 1824 surface = Tiling.triangulation( 1825 [ru0, rv0], 1826 [ru0, rv1], 1827 [ru1, rv1], 1828 [ru1, rv0], 1829 su, sv 1830 ); 1831 } else { 1832 surface = Tiling.rectangulation( 1833 [ru0, rv0], 1834 [ru0, rv1], 1835 [ru1, rv1], 1836 [ru1, rv0], 1837 su, sv 1838 ); 1839 } 1840 1841 el.F = function (u, v) { 1842 return [ 1843 el.point.coords[0] + u * el.vec1[0] + v * el.vec2[0], 1844 el.point.coords[1] + u * el.vec1[1] + v * el.vec2[1], 1845 el.point.coords[2] + u * el.vec1[2] + v * el.vec2[2], 1846 el.point.coords[3] + u * el.vec1[3] + v * el.vec2[3] 1847 ]; 1848 }; 1849 coords = Tiling.mapMeshTo3D(surface, el); 1850 1851 surface = [coords, surface[1]]; 1852 1853 if (type === 'colormap') { 1854 attr.polyhedron.shader.enabled = false; 1855 1856 // Static 1857 m = el.evalVisProp('colormap.max'); 1858 ma = m[0]; 1859 ma_a = m[1]; 1860 m = el.evalVisProp('colormap.min'); 1861 mi = m[0]; 1862 mi_a = m[1]; 1863 s = el.evalVisProp('colormap.s'); 1864 v = el.evalVisProp('colormap.v'); 1865 1866 attr.polyhedron.fillcolorarray = []; 1867 attr.polyhedron.fillcolor = (self) => { 1868 var j, hsl, 1869 z = 0, 1870 p = self.polyhedron, 1871 face = p.faces[self.faceNumber], 1872 le = face.length; 1873 1874 // Dynamic version 1875 // m = self.evalVisProp('max'); 1876 // ma = m[0]; 1877 // ma_a = m[1]; 1878 // m = self.evalVisProp('min'); 1879 // mi = m[0]; 1880 // mi_a = m[1]; 1881 if (le !== 0) { 1882 for (j = 0; j < le; j++) { 1883 z += p.coords[face[j]][3]; 1884 } 1885 z /= le; 1886 } 1887 z = mi_a + (z - mi) * (ma_a - mi_a) / (ma - mi); 1888 1889 // hsl = JXG.hsv2hsl(z, el.evalVisProp('colormap.s'), el.evalVisProp('colormap.v')); // Dynamic version - slower 1890 hsl = JXG.hsv2hsl(z, s, v); 1891 return `hsl(${z} ${hsl[1] * 100}% ${hsl[2] * 100}%)`; 1892 }; 1893 } else if (type === 'shader') { 1894 attr.polyhedron.shader.enabled = true; 1895 } else { 1896 // colorarray 1897 attr.polyhedron.shader.enabled = false; 1898 } 1899 1900 el.polyhedron = view.create('polyhedron3d', surface, attr.polyhedron); 1901 el.addChild(el.polyhedron); 1902 el.inherits.push(el.polyhedron); 1903 el.polyhedron.setParents(el); 1904 } 1905 } 1906 1907 // Wireframe 1908 el.element2D.prepareUpdate().update(); 1909 if (!board.isSuspendedUpdate) { 1910 el.element2D.updateVisibility().updateRenderer(); 1911 } 1912 1913 return el; 1914 }; 1915 1916 JXG.registerElement('plane3d', JXG.createPlane3D); 1917 1918 /** 1919 * @class The line that is the intersection of two (infinite) plane elements in 3D. 1920 * 1921 * @pseudo 1922 * @name IntersectionLine3D 1923 * @augments JXG.Line3D 1924 * @constructor 1925 * @type JXG.Line3D 1926 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1927 * @param {JXG.Plane3D_JXG.Plane3D} el1,el2 The result will be the intersection of el1 and el2. 1928 * @example 1929 * // Create the intersection line of two planes 1930 * var view = board.create( 1931 * 'view3d', 1932 * [[-6, -3], [8, 8], 1933 * [[-1, 3], [-1, 3], [-1, 3]]], 1934 * { 1935 * xPlaneRear: {visible:false}, 1936 * yPlaneRear: {visible:false}, 1937 * zPlaneRear: {fillOpacity: 0.2, gradient: null} 1938 * } 1939 * ); 1940 * var a = view.create('point3d', [2, 2, 0]); 1941 * 1942 * var p1 = view.create( 1943 * 'plane3d', 1944 * [a, [1, 0, 0], [0, 1, 0]], 1945 * {fillColor: '#00ff80'} 1946 * ); 1947 * var p2 = view.create( 1948 * 'plane3d', 1949 * [a, [-2, 1, 1], [1, -2, 1]], 1950 * {fillColor: '#ff0000'} 1951 * ); 1952 * 1953 * var i = view.create('intersectionline3d', [p1, p2]); 1954 * 1955 * </pre><div id="JXGdb931076-b29a-4eff-b97e-4251aaf24943" class="jxgbox" style="width: 300px; height: 300px;"></div> 1956 * <script type="text/javascript"> 1957 * (function() { 1958 * var board = JXG.JSXGraph.initBoard('JXGdb931076-b29a-4eff-b97e-4251aaf24943', 1959 * {boundingbox: [-8, 8, 8,-8], axis: false, pan: {enabled: false}, showcopyright: false, shownavigation: false}); 1960 * var view = board.create( 1961 * 'view3d', 1962 * [[-6, -3], [8, 8], 1963 * [[-1, 3], [-1, 3], [-1, 3]]], 1964 * { 1965 * xPlaneRear: {visible:false}, 1966 * yPlaneRear: {visible:false}, 1967 * zPlaneRear: {fillOpacity: 0.2, gradient: null} 1968 * } 1969 * ); 1970 * var a = view.create('point3d', [2, 2, 0]); 1971 * 1972 * var p1 = view.create( 1973 * 'plane3d', 1974 * [a, [1, 0, 0], [0, 1, 0]], 1975 * {fillColor: '#00ff80'} 1976 * ); 1977 * var p2 = view.create( 1978 * 'plane3d', 1979 * [a, [-2, 1, 1], [1, -2, 1]], 1980 * {fillColor: '#ff0000'} 1981 * ); 1982 * 1983 * var i = view.create('intersectionline3d', [p1, p2]); 1984 * 1985 * })(); 1986 * 1987 * </script><pre> 1988 * 1989 */ 1990 JXG.createIntersectionLine3D = function (board, parents, attributes) { 1991 var view = parents[0], 1992 el1 = parents[1], 1993 el2 = parents[2], 1994 ixnLine, i, func, 1995 attr = Type.copyAttributes(attributes, board.options, 'intersectionline3d'), 1996 pts = []; 1997 1998 func = Geometry.intersectionFunction3D(view, el1, el2); 1999 for (i = 0; i < 2; i++) { 2000 pts[i] = view.create('point3d', func[i], attr['point' + (i + 1)]); 2001 } 2002 ixnLine = view.create('line3d', pts, attr); 2003 2004 try { 2005 el1.addChild(ixnLine); 2006 el2.addChild(ixnLine); 2007 } catch (_e) { 2008 throw new Error( 2009 "JSXGraph: Can't create 'intersection' with parent types '" + 2010 typeof parents[1] + 2011 "' and '" + 2012 typeof parents[2] + 2013 "'." 2014 ); 2015 } 2016 2017 ixnLine.type = Const.OBJECT_TYPE_INTERSECTION_LINE3D; 2018 ixnLine.elType = 'intersectionline3d'; 2019 ixnLine.setParents([el1.id, el2.id]); 2020 2021 return ixnLine; 2022 }; 2023 2024 JXG.registerElement('intersectionline3d', JXG.createIntersectionLine3D); 2025