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