1 /* 2 Copyright 2008-2024 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(1); 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 this.grid = null; 864 865 /** 866 * Normal vector of the plane. Left hand side of the Hesse normal form. 867 * @name Plane3D#normal 868 * @type Array 869 * @private 870 * 871 * @see Plane3D.updateNormal 872 * 873 */ 874 this.normal = [0, 0, 0, 0]; 875 876 /** 877 * Right hand side of the Hesse normal form. 878 * @name Plane3D#d 879 * @type Array 880 * @private 881 * 882 * @see Plane3D.updateNormal 883 * 884 */ 885 this.d = 0; 886 887 this.updateCoords(); 888 this.updateNormal(); 889 890 this.methodMap = Type.deepCopy(this.methodMap, { 891 // TODO 892 }); 893 }; 894 JXG.Plane3D.prototype = new JXG.GeometryElement(); 895 Type.copyPrototypeMethods(JXG.Plane3D, JXG.GeometryElement3D, 'constructor3D'); 896 897 JXG.extend( 898 JXG.Plane3D.prototype, 899 /** @lends JXG.Plane3D.prototype */ { 900 901 /** 902 * Get coordinate array [x, y, z] of a point on the plane for parameters (u, v). 903 * 904 * @name Plane3D#F 905 * @function 906 * @param {Number} u 907 * @param {Number} v 908 * @returns Array of length 3. 909 */ 910 F: function (u, v) { 911 var i, v1, v2, l1, l2; 912 913 v1 = this.vec1.slice(); 914 v2 = this.vec2.slice(); 915 l1 = Mat.norm(v1, 3); 916 l2 = Mat.norm(v2, 3); 917 for (i = 0; i < 3; i++) { 918 v1[i] /= l1; 919 v2[i] /= l2; 920 } 921 922 return [ 923 this.point.X() + u * v1[0] + v * v2[0], 924 this.point.Y() + u * v1[1] + v * v2[1], 925 this.point.Z() + u * v1[2] + v * v2[2] 926 ]; 927 }, 928 929 /** 930 * Get x-coordinate of a point on the plane for parameters (u, v). 931 * 932 * @name Plane3D#X 933 * @function 934 * @param {Number} u 935 * @param {Number} v 936 * @returns Number 937 */ 938 X: function(u, v) { 939 return this.F(u, v)[0]; 940 }, 941 942 /** 943 * Get y-coordinate of a point on the plane for parameters (u, v). 944 * 945 * @name Plane3D#Y 946 * @function 947 * @param {Number} u 948 * @param {Number} v 949 * @returns Number 950 */ 951 Y: function(u, v) { 952 return this.F(u, v)[1]; 953 }, 954 955 /** 956 * Get z-coordinate of a point on the plane for parameters (u, v). 957 * 958 * @name Plane3D#Z 959 * @function 960 * @param {Number} u 961 * @param {Number} v 962 * @returns Number 963 */ 964 Z: function(u, v) { 965 return this.F(u, v)[2]; 966 }, 967 968 /** 969 * Update the arrays {@link JXG.Plane3D#vec1} and {@link JXG.Plane3D#vec1} containing the homogeneous coords of the spanning vectors. 970 * 971 * @name Plane3D#updateCoords 972 * @function 973 * @returns {Object} Reference to Plane3D object 974 * @private 975 */ 976 updateCoords: function() { 977 var i, s; 978 979 if (Type.exists(this.direction1.view) && this.direction1.type === Const.OBJECT_TYPE_LINE3D) { 980 this.vec1 = this.direction1.vec.slice(); 981 } else if (Type.isFunction(this.direction1)) { 982 this.vec1 = Type.evaluate(this.direction1); 983 if (this.vec1.length === 3) { 984 this.vec1.unshift(1); 985 } 986 } else { 987 s = 0; 988 if (this.direction1.length === 3) { 989 this.vec1[0] = 0; 990 s = 1; 991 } 992 for (i = 0; i < this.direction1.length; i++) { 993 this.vec1[s + i] = Type.evaluate(this.direction1[i]); 994 } 995 } 996 997 if (Type.exists(this.direction2.view) && this.direction2.type === Const.OBJECT_TYPE_LINE3D) { 998 this.vec2 = this.direction2.vec.slice(); 999 } else if (Type.isFunction(this.direction2)) { 1000 this.vec2 = Type.evaluate(this.direction2); 1001 if (this.vec2.length === 3) { 1002 this.vec2.unshift(1); 1003 } 1004 } else { 1005 s = 0; 1006 if (this.direction2.length === 3) { 1007 this.vec2[0] = 0; 1008 s = 1; 1009 } 1010 for (i = 0; i < this.direction2.length; i++) { 1011 this.vec2[s + i] = Type.evaluate(this.direction2[i]); 1012 } 1013 } 1014 1015 return this; 1016 }, 1017 1018 /** 1019 * Update the Hesse normal form of the plane, i.e. update normal vector and right hand side. 1020 * Updates also {@link vec1} and {@link vec2}. 1021 * 1022 * @name Plane3D#updateNormal 1023 * @function 1024 * @returns {Object} Reference to the Plane3D object 1025 * @private 1026 * @example 1027 * plane.updateNormal(); 1028 * 1029 */ 1030 updateNormal: function () { 1031 var i, len; 1032 1033 if (!this.needsUpdate) { 1034 // Extraordinary update, conflicts with rotating of box and plane transformations 1035 // this.updateCoords(); 1036 } 1037 1038 this.normal = Mat.crossProduct(this.vec1.slice(1), this.vec2.slice(1)); 1039 1040 len = Mat.norm(this.normal); 1041 if (Math.abs(len) > Mat.eps * Mat.eps) { 1042 for (i = 0; i < 3; i++) { 1043 this.normal[i] /= len; 1044 } 1045 } 1046 this.normal.unshift(0); 1047 this.d = Mat.innerProduct(this.point.coords, this.normal, 4); 1048 1049 return this; 1050 }, 1051 1052 // Already documented in element3d.js 1053 updateDataArray: function () { 1054 var s1, e1, s2, e2, c2d, l1, l2, 1055 planes = ['xPlaneRear', 'yPlaneRear', 'zPlaneRear'], // Must be ordered x, y, z 1056 points = [], 1057 v1 = [0, 0, 0], 1058 v2 = [0, 0, 0], 1059 q = [0, 0, 0], 1060 p = [0, 0, 0], 1061 eps = 1.e-12, 1062 d, i, j, a, b, first, pos, pos_akt, 1063 view = this.view; 1064 1065 this.dataX = []; 1066 this.dataY = []; 1067 1068 this.updateNormal(); 1069 1070 // Infinite plane 1071 if ( 1072 this.elType !== 'axisplane3d' && 1073 view.defaultAxes && 1074 Type.evaluate(this.range_u[0]) === -Infinity && 1075 Type.evaluate(this.range_u[1]) === Infinity && 1076 Type.evaluate(this.range_v[0]) === -Infinity && 1077 Type.evaluate(this.range_v[1]) === Infinity 1078 ) { 1079 // Determine the intersections of the new plane with 1080 // the view bbox3d. 1081 // 1082 // Start with the rear plane. 1083 // For each face of the bbox3d we determine two points 1084 // which are the end points of the intersecting line 1085 // between the plane and a face of bbox3d. 1086 // We start with the three rear planes (set in planes[] above) 1087 for (j = 0; j < planes.length; j++) { 1088 p = view.intersectionPlanePlane(this, view.defaultAxes[planes[j]]); 1089 if (p[0] !== false && p[1] !== false) { 1090 // This test is necessary to filter out intersection lines which are 1091 // identical to intersections of axis planes (they would occur twice), 1092 // i.e. edges of bbox3d. 1093 for (i = 0; i < points.length; i++) { 1094 if ( 1095 (Geometry.distance(p[0], points[i][0], 4) < eps && 1096 Geometry.distance(p[1], points[i][1], 4) < eps) || 1097 (Geometry.distance(p[0], points[i][1], 4) < eps && 1098 Geometry.distance(p[1], points[i][0], 4) < eps) 1099 ) { 1100 break; 1101 } 1102 } 1103 if (i === points.length) { 1104 points.push(p.slice()); 1105 } 1106 } 1107 1108 // Take a point on the corresponding front plane of bbox3d. 1109 p = [1, 0, 0, 0]; 1110 p[j + 1] = view.bbox3D[j][1]; 1111 1112 // Use the Hesse normal form of front plane to intersect it with the plane 1113 // d is the rhs of the Hesse normal form of the front plane. 1114 d = Mat.innerProduct(p, view.defaultAxes[planes[j]].normal, 4); 1115 p = view.intersectionPlanePlane(this, view.defaultAxes[planes[j]], d); 1116 1117 if (p[0] !== false && p[1] !== false) { 1118 // Do the same test as above 1119 for (i = 0; i < points.length; i++) { 1120 // Same test for edges of bbox3d as above 1121 if ( 1122 (Geometry.distance(p[0], points[i][0], 4) < eps && 1123 Geometry.distance(p[1], points[i][1], 4) < eps) || 1124 (Geometry.distance(p[0], points[i][1], 4) < eps && 1125 Geometry.distance(p[1], points[i][0], 4) < eps) 1126 ) { 1127 break; 1128 } 1129 } 1130 if (i === points.length) { 1131 points.push(p.slice()); 1132 } 1133 } 1134 } 1135 1136 // Handle the case that the plane does not intersect bbox3d at all. 1137 if (points.length === 0) { 1138 return { X: this.dataX, Y: this.dataY }; 1139 } 1140 1141 // Concatenate the intersection points to a polygon. 1142 // If all went well, each intersection should appear 1143 // twice in the list. 1144 first = 0; 1145 pos = first; 1146 i = 0; 1147 do { 1148 p = points[pos][i]; 1149 if (p.length === 4) { 1150 c2d = view.project3DTo2D(p); 1151 this.dataX.push(c2d[1]); 1152 this.dataY.push(c2d[2]); 1153 } 1154 i = (i + 1) % 2; 1155 p = points[pos][i]; 1156 1157 pos_akt = pos; 1158 for (j = 0; j < points.length; j++) { 1159 if (j !== pos && Geometry.distance(p, points[j][0]) < eps) { 1160 pos = j; 1161 i = 0; 1162 break; 1163 } 1164 if (j !== pos && Geometry.distance(p, points[j][1]) < eps) { 1165 pos = j; 1166 i = 1; 1167 break; 1168 } 1169 } 1170 if (pos === pos_akt) { 1171 console.log('Error plane3d update: did not find next', pos); 1172 break; 1173 } 1174 } while (pos !== first); 1175 1176 c2d = view.project3DTo2D(points[first][0]); 1177 this.dataX.push(c2d[1]); 1178 this.dataY.push(c2d[2]); 1179 } else { 1180 // 3D bounded flat 1181 s1 = Type.evaluate(this.range_u[0]); 1182 e1 = Type.evaluate(this.range_u[1]); 1183 s2 = Type.evaluate(this.range_v[0]); 1184 e2 = Type.evaluate(this.range_v[1]); 1185 1186 q = this.point.coords; 1187 v1 = this.vec1.slice(); 1188 v2 = this.vec2.slice(); 1189 l1 = Mat.norm(v1, 4); 1190 l2 = Mat.norm(v2, 4); 1191 for (i = 1; i < 4; i++) { 1192 v1[i] /= l1; 1193 v2[i] /= l2; 1194 } 1195 1196 for (j = 0; j < 4; j++) { 1197 switch (j) { 1198 case 0: 1199 a = s1; 1200 b = s2; 1201 break; 1202 case 1: 1203 a = e1; 1204 b = s2; 1205 break; 1206 case 2: 1207 a = e1; 1208 b = e2; 1209 break; 1210 case 3: 1211 a = s1; 1212 b = e2; 1213 } 1214 for (i = 0; i < 4; i++) { 1215 p[i] = q[i] + a * v1[i] + b * v2[i]; 1216 } 1217 c2d = view.project3DTo2D(p); 1218 this.dataX.push(c2d[1]); 1219 this.dataY.push(c2d[2]); 1220 } 1221 // Close the curve 1222 this.dataX.push(this.dataX[0]); 1223 this.dataY.push(this.dataY[0]); 1224 } 1225 return { X: this.dataX, Y: this.dataY }; 1226 }, 1227 1228 // Already documented in element3d.js 1229 addTransform: function (el, transform) { 1230 this.addTransformGeneric(el, transform); 1231 this.point.addTransform(el.point, transform); 1232 return this; 1233 }, 1234 1235 // Already documented in element3d.js 1236 updateTransform: function () { 1237 var c1, c2, i; 1238 1239 if (this.transformations.length === 0 || this.baseElement === null) { 1240 return this; 1241 } 1242 1243 if (this === this.baseElement) { 1244 c1 = this.vec1; 1245 c2 = this.vec2; 1246 } else { 1247 c1 = this.baseElement.vec1; 1248 c2 = this.baseElement.vec2; 1249 } 1250 1251 for (i = 0; i < this.transformations.length; i++) { 1252 this.transformations[i].update(); 1253 c1 = Mat.matVecMult(this.transformations[i].matrix, c1); 1254 c2 = Mat.matVecMult(this.transformations[i].matrix, c2); 1255 } 1256 this.vec1 = c1; 1257 this.vec2 = c2; 1258 1259 return this; 1260 }, 1261 1262 // Already documented in element3d.js 1263 update: function () { 1264 if (this.needsUpdate) { 1265 this.updateCoords() 1266 .updateTransform(); 1267 } 1268 return this; 1269 }, 1270 1271 // Already documented in element3d.js 1272 updateRenderer: function () { 1273 this.needsUpdate = false; 1274 return this; 1275 }, 1276 1277 // Already documented in element3d.js 1278 projectCoords: function (p, params) { 1279 return Geometry.projectCoordsToParametric(p, this, 2, params); 1280 } 1281 } 1282 ); 1283 1284 /** 1285 * @class A 3D plane is defined either by a point and two linearly independent vectors, or by three points. 1286 * 1287 * @description 1288 * A 3D plane is defined either by a point and two linearly independent vectors, or by three points. 1289 * In the first case, the parameters are a 3D point (or a coordinate array) and two vectors (arrays). 1290 * In the second case, the parameters consist of three 3D points (given as points or coordinate arrays). 1291 * In order to distinguish the two cases, in the latter case (three points), the additional attribute {@link Plane3D#threePoints} 1292 * has to be supplied if both, the second point and the third point, are given as arrays or functions. Otherwise, it would not be 1293 * clear if the input arrays have to be interpreted as points or directions. 1294 * <p> 1295 * All coordinate arrays can be supplied as functions returning a coordinate array. 1296 * Planes defined by three points are always infinite. 1297 * 1298 * @pseudo 1299 * @name Plane3D 1300 * @augments JXG.GeometryElement3D 1301 * @constructor 1302 * @throws {Exception} If the element cannot be constructed with the given parent 1303 * objects an exception is thrown. 1304 * 1305 * @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. 1306 * <ul> 1307 * <li> point: Point3D or array of length 3 1308 * <li> direction1: line3d element or array of length 3 or function returning an array of numbers or function returning an array 1309 * <li> direction2: line3d element or array of length 3 or function returning an array of numbers or function returning an array 1310 * <li> range1: array of length 2, elements can also be functions. Use [-Infinity, Infinity] for infinite lines. 1311 * <li> range2: array of length 2, elements can also be functions. Use [-Infinity, Infinity] for infinite lines. 1312 * </ul> 1313 * @param {JXG.Point3D,array,function_JXG.Point3D,array,function_JXG.Point3D,array,function} point1,point2,point3 The plane is defined by three points. 1314 * @type JXG.Plane3D 1315 * 1316 * @example 1317 * var view = board.create( 1318 * 'view3d', 1319 * [[-6, -3], [8, 8], 1320 * [[-3, 3], [-3, 3], [-3, 3]]], 1321 * { 1322 * depthOrder: { 1323 * enabled: true 1324 * }, 1325 * projection: 'central', 1326 * xPlaneRear: {fillOpacity: 0.2}, 1327 * yPlaneRear: {fillOpacity: 0.2}, 1328 * zPlaneRear: {fillOpacity: 0.2} 1329 * } 1330 * ); 1331 * 1332 * var A = view.create('point3d', [-2, 0, 1], {size: 2}); 1333 * 1334 * // Infinite Plane by point and two directions 1335 * var plane = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0], [-Infinity, Infinity], [-Infinity, Infinity]]); 1336 * 1337 * </pre><div id="JXG69f491ef-d7c7-4105-a962-86a588fbd23b" class="jxgbox" style="width: 300px; height: 300px;"></div> 1338 * <script type="text/javascript"> 1339 * (function() { 1340 * var board = JXG.JSXGraph.initBoard('JXG69f491ef-d7c7-4105-a962-86a588fbd23b', 1341 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 1342 * var view = board.create( 1343 * 'view3d', 1344 * [[-6, -3], [8, 8], 1345 * [[-3, 3], [-3, 3], [-3, 3]]], 1346 * { 1347 * depthOrder: { 1348 * enabled: true 1349 * }, 1350 * projection: 'central', 1351 * xPlaneRear: {fillOpacity: 0.2}, 1352 * yPlaneRear: {fillOpacity: 0.2}, 1353 * zPlaneRear: {fillOpacity: 0.2} 1354 * } 1355 * ); 1356 * 1357 * var A = view.create('point3d', [-2, 0, 1], {size: 2}); 1358 * 1359 * // Infinite Plane by point and two directions 1360 * var plane = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0], [-Infinity, Infinity], [-Infinity, Infinity]]); 1361 * 1362 * })(); 1363 * 1364 * </script><pre> 1365 * 1366 * @example 1367 * var view = board.create( 1368 * 'view3d', 1369 * [[-6, -3], [8, 8], 1370 * [[-3, 3], [-3, 3], [-3, 3]]], 1371 * { 1372 * depthOrder: { 1373 * enabled: true 1374 * }, 1375 * projection: 'central', 1376 * xPlaneRear: {fillOpacity: 0.2}, 1377 * yPlaneRear: {fillOpacity: 0.2}, 1378 * zPlaneRear: {fillOpacity: 0.2} 1379 * } 1380 * ); 1381 * 1382 * var A = view.create('point3d', [-2, 0, 1], {size: 2}); 1383 * 1384 * // Finite Plane by point and two directions 1385 * var plane1 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0], [-2, 2], [-2, 2]]); 1386 * var plane2 = view.create('plane3d', [[0, 0, -1], [1, 0, 0], [0, 1, 0], [-2, 2], [-2, 2]], { 1387 * mesh3d: { visible: true }, 1388 * point: {visible: true, name: "B", fixed: false} 1389 * }); 1390 * 1391 * </pre><div id="JXGea9dda1b-748b-4ed3-b4b3-57e310bd8141" class="jxgbox" style="width: 300px; height: 300px;"></div> 1392 * <script type="text/javascript"> 1393 * (function() { 1394 * var board = JXG.JSXGraph.initBoard('JXGea9dda1b-748b-4ed3-b4b3-57e310bd8141', 1395 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 1396 * var view = board.create( 1397 * 'view3d', 1398 * [[-6, -3], [8, 8], 1399 * [[-3, 3], [-3, 3], [-3, 3]]], 1400 * { 1401 * depthOrder: { 1402 * enabled: true 1403 * }, 1404 * projection: 'central', 1405 * xPlaneRear: {fillOpacity: 0.2}, 1406 * yPlaneRear: {fillOpacity: 0.2}, 1407 * zPlaneRear: {fillOpacity: 0.2} 1408 * } 1409 * ); 1410 * 1411 * var A = view.create('point3d', [-2, 0, 1], {size: 2}); 1412 * 1413 * // Finite Plane by point and two directions 1414 * var plane1 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0], [-2, 2], [-2, 2]]); 1415 * var plane2 = view.create('plane3d', [[0, 0, -1], [1, 0, 0], [0, 1, 0], [-2, 2], [-2, 2]], { 1416 * mesh3d: { visible: true }, 1417 * point: {visible: true, name: "B", fixed: false} 1418 * }); 1419 * 1420 * })(); 1421 * 1422 * </script><pre> 1423 * @example 1424 * var view = board.create( 1425 * 'view3d', 1426 * [[-6, -3], [8, 8], 1427 * [[-3, 3], [-3, 3], [-3, 3]]], 1428 * { 1429 * depthOrder: { 1430 * enabled: true 1431 * }, 1432 * projection: 'central', 1433 * xPlaneRear: { visible: false, fillOpacity: 0.2 }, 1434 * yPlaneRear: { visible: false, fillOpacity: 0.2 }, 1435 * zPlaneRear: { fillOpacity: 0.2 } 1436 * } 1437 * ); 1438 * 1439 * var A = view.create('point3d', [-2, 0, 1], { size: 2 }); 1440 * 1441 * var line1 = view.create('line3d', [A, [0, 0, 1], [-Infinity, Infinity]], { strokeColor: 'blue' }); 1442 * var line2 = view.create('line3d', [A, [1, 1, 0], [-Infinity, Infinity]], { strokeColor: 'blue' }); 1443 * 1444 * // Plane by point and two lines 1445 * var plane2 = view.create('plane3d', [A, line1, line2], { 1446 * fillColor: 'blue' 1447 * }); 1448 * 1449 * </pre><div id="JXG8bc6e266-e27c-4ffa-86a2-8076f4069573" class="jxgbox" style="width: 300px; height: 300px;"></div> 1450 * <script type="text/javascript"> 1451 * (function() { 1452 * var board = JXG.JSXGraph.initBoard('JXG8bc6e266-e27c-4ffa-86a2-8076f4069573', 1453 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 1454 * var view = board.create( 1455 * 'view3d', 1456 * [[-6, -3], [8, 8], 1457 * [[-3, 3], [-3, 3], [-3, 3]]], 1458 * { 1459 * depthOrder: { 1460 * enabled: true 1461 * }, 1462 * projection: 'central', 1463 * xPlaneRear: { visible: false, fillOpacity: 0.2 }, 1464 * yPlaneRear: { visible: false, fillOpacity: 0.2 }, 1465 * zPlaneRear: { fillOpacity: 0.2 } 1466 * } 1467 * ); 1468 * 1469 * var A = view.create('point3d', [-2, 0, 1], { size: 2 }); 1470 * 1471 * var line1 = view.create('line3d', [A, [0, 0, 1], [-Infinity, Infinity]], { strokeColor: 'blue' }); 1472 * var line2 = view.create('line3d', [A, [1, 1, 0], [-Infinity, Infinity]], { strokeColor: 'blue' }); 1473 * 1474 * // Plane by point and two lines 1475 * var plane2 = view.create('plane3d', [A, line1, line2], { 1476 * fillColor: 'blue' 1477 * }); 1478 * 1479 * })(); 1480 * 1481 * </script><pre> 1482 * 1483 * @example 1484 * var view = board.create( 1485 * 'view3d', 1486 * [[-6, -3], [8, 8], 1487 * [[-3, 3], [-3, 3], [-3, 3]]], 1488 * { 1489 * depthOrder: { 1490 * enabled: true 1491 * }, 1492 * projection: 'central', 1493 * xPlaneRear: {fillOpacity: 0.2}, 1494 * yPlaneRear: {fillOpacity: 0.2}, 1495 * zPlaneRear: {fillOpacity: 0.2} 1496 * } 1497 * ); 1498 * 1499 * var A = view.create('point3d', [0, 0, 1], {size: 2}); 1500 * var B = view.create('point3d', [2, 2, 1], {size: 2}); 1501 * var C = view.create('point3d', [-2, 0, 1], {size: 2}); 1502 * 1503 * // Plane by three points 1504 * var plane = view.create('plane3d', [A, B, C], { 1505 * fillColor: 'blue' 1506 * }); 1507 * 1508 * </pre><div id="JXG139100df-3ece-4cd1-b34f-28b5b3105106" class="jxgbox" style="width: 300px; height: 300px;"></div> 1509 * <script type="text/javascript"> 1510 * (function() { 1511 * var board = JXG.JSXGraph.initBoard('JXG139100df-3ece-4cd1-b34f-28b5b3105106', 1512 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 1513 * var view = board.create( 1514 * 'view3d', 1515 * [[-6, -3], [8, 8], 1516 * [[-3, 3], [-3, 3], [-3, 3]]], 1517 * { 1518 * depthOrder: { 1519 * enabled: true 1520 * }, 1521 * projection: 'central', 1522 * xPlaneRear: {fillOpacity: 0.2}, 1523 * yPlaneRear: {fillOpacity: 0.2}, 1524 * zPlaneRear: {fillOpacity: 0.2} 1525 * } 1526 * ); 1527 * 1528 * var A = view.create('point3d', [0, 0, 1], {size: 2}); 1529 * var B = view.create('point3d', [2, 2, 1], {size: 2}); 1530 * var C = view.create('point3d', [-2, 0, 1], {size: 2}); 1531 * 1532 * // Plane by three points 1533 * var plane = view.create('plane3d', [A, B, C], { 1534 * fillColor: 'blue' 1535 * }); 1536 * 1537 * })(); 1538 * 1539 * </script><pre> 1540 * 1541 * @example 1542 * var view = board.create( 1543 * 'view3d', 1544 * [[-6, -3], [8, 8], 1545 * [[-3, 3], [-3, 3], [-3, 3]]], 1546 * { 1547 * depthOrder: { 1548 * enabled: true 1549 * }, 1550 * projection: 'central', 1551 * xPlaneRear: {fillOpacity: 0.2}, 1552 * yPlaneRear: {fillOpacity: 0.2}, 1553 * zPlaneRear: {fillOpacity: 0.2} 1554 * } 1555 * ); 1556 * 1557 * var A = view.create('point3d', [-2, 0, 1], {size: 2}); 1558 * 1559 * // Infinite Plane by two directions, 1560 * // range1 = range2 = [-Infinity, Infinity] 1561 * var plane1 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0]], { 1562 * fillColor: 'blue', 1563 * }); 1564 * 1565 * // Infinite Plane by three points, 1566 * var plane2 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0]], { 1567 * threePoints: true, 1568 * fillColor: 'red', 1569 * point2: {visible: true}, 1570 * point3: {visible: true} 1571 * }); 1572 * 1573 * </pre><div id="JXGf31b9666-0c2e-45e7-a186-ae2c07b6bdb8" class="jxgbox" style="width: 300px; height: 300px;"></div> 1574 * <script type="text/javascript"> 1575 * (function() { 1576 * var board = JXG.JSXGraph.initBoard('JXGf31b9666-0c2e-45e7-a186-ae2c07b6bdb8', 1577 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 1578 * var view = board.create( 1579 * 'view3d', 1580 * [[-6, -3], [8, 8], 1581 * [[-3, 3], [-3, 3], [-3, 3]]], 1582 * { 1583 * depthOrder: { 1584 * enabled: true 1585 * }, 1586 * projection: 'central', 1587 * xPlaneRear: {fillOpacity: 0.2}, 1588 * yPlaneRear: {fillOpacity: 0.2}, 1589 * zPlaneRear: {fillOpacity: 0.2} 1590 * } 1591 * ); 1592 * 1593 * var A = view.create('point3d', [-2, 0, 1], {size: 2}); 1594 * 1595 * // Infinite Plane by two directions, 1596 * // range1 = range2 = [-Infinity, Infinity] 1597 * var plane1 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0]], { 1598 * fillColor: 'blue', 1599 * }); 1600 * 1601 * // Infinite Plane by three points, 1602 * var plane2 = view.create('plane3d', [A, [1, 0, 0], [0, 1, 0]], { 1603 * threePoints: true, 1604 * fillColor: 'red', 1605 * point2: {visible: true}, 1606 * point3: {visible: true} 1607 * }); 1608 * 1609 * })(); 1610 * 1611 * </script><pre> 1612 * 1613 */ 1614 JXG.createPlane3D = function (board, parents, attributes) { 1615 var view = parents[0], 1616 attr, 1617 point, point2, point3, 1618 dir1, dir2, range_u, range_v, 1619 el, grid, 1620 base = null, 1621 transform = null; 1622 1623 attr = Type.copyAttributes(attributes, board.options, 'plane3d'); 1624 if (parents.length === 4 && 1625 (attr.threepoints || Type.isPoint3D(parents[2]) || Type.isPoint3D(parents[3])) 1626 ) { 1627 // Three points 1628 point = Type.providePoints3D(view, [parents[1]], attributes, 'plane3d', ['point1'])[0]; 1629 point2 = Type.providePoints3D(view, [parents[2]], attributes, 'plane3d', ['point2'])[0]; 1630 point3 = Type.providePoints3D(view, [parents[3]], attributes, 'plane3d', ['point3'])[0]; 1631 dir1 = function() { 1632 return [point2.X() - point.X(), point2.Y() - point.Y(), point2.Z() - point.Z()]; 1633 }; 1634 dir2 = function() { 1635 return [point3.X() - point.X(), point3.Y() - point.Y(), point3.Z() - point.Z()]; 1636 }; 1637 range_u = [-Infinity, Infinity]; 1638 range_v = [-Infinity, Infinity]; 1639 } else { 1640 if (parents[1].type === Const.OBJECT_TYPE_PLANE3D && 1641 Type.isTransformationOrArray(parents[2]) 1642 ) { 1643 base = parents[1]; 1644 transform = parents[2]; 1645 1646 point = Type.providePoints3D(view, [[0, 0, 0, 0]], attributes, 'plane3d', ['point'])[0]; 1647 dir1 = [0, 0.0001, 0, 0]; 1648 dir2 = [0, 0, 0.0001, 0]; 1649 range_u = parents[3] || [-Infinity, Infinity]; 1650 range_v = parents[4] || [-Infinity, Infinity]; 1651 } else { 1652 point = Type.providePoints3D(view, [parents[1]], attributes, 'plane3d', ['point'])[0]; 1653 dir1 = parents[2]; 1654 dir2 = parents[3]; 1655 range_u = parents[4] || [-Infinity, Infinity]; 1656 range_v = parents[5] || [-Infinity, Infinity]; 1657 } 1658 if (point === false) { 1659 throw new Error( 1660 "JSXGraph: Can't create plane3d with first parent of type '" + typeof parents[1] + 1661 "'." + 1662 "\nPossible first parent types are: point3d, array of length 3, function returning an array of length 3." 1663 ); 1664 } 1665 if ((base !== null && parents < 3) || (base === null && parents.length < 4)) { 1666 throw new Error( 1667 "JSXGraph: Can't create plane3d with parents of type '" + 1668 typeof parents[1] + ", " + 1669 typeof parents[2] + ", " + 1670 typeof parents[3] + ", " + 1671 typeof parents[4] + ", " + 1672 typeof parents[5] + "'." 1673 ); 1674 } 1675 } 1676 1677 el = new JXG.Plane3D(view, point, dir1, range_u, dir2, range_v, attr); 1678 point.addChild(el); 1679 1680 attr = el.setAttr2D(attr); 1681 el.element2D = view.create('curve', [[], []], attr); 1682 el.element2D.view = view; 1683 1684 if (base !== null && transform !== null) { 1685 el.addTransform(base, transform); 1686 el.addParents(base); 1687 } 1688 1689 /** 1690 * @class 1691 * @ignore 1692 */ 1693 el.element2D.updateDataArray = function () { 1694 var ret = el.updateDataArray(); 1695 this.dataX = ret.X; 1696 this.dataY = ret.Y; 1697 }; 1698 el.addChild(el.element2D); 1699 el.inherits.push(el.element2D); 1700 el.element2D.setParents(el); 1701 1702 attr = Type.copyAttributes(attributes.mesh3d, board.options, 'mesh3d'); 1703 if ( 1704 Math.abs(el.range_u[0]) !== Infinity && 1705 Math.abs(el.range_u[1]) !== Infinity && 1706 Math.abs(el.range_v[0]) !== Infinity && 1707 Math.abs(el.range_v[1]) !== Infinity 1708 ) { 1709 grid = view.create('mesh3d', [ 1710 function () { 1711 return point.coords; 1712 }, 1713 dir1, range_u, dir2, range_v 1714 ], attr 1715 ); 1716 el.grid = grid; 1717 el.addChild(grid); 1718 el.inherits.push(grid); 1719 grid.setParents(el); 1720 el.grid.view = view; 1721 } 1722 1723 el.element2D.prepareUpdate().update(); 1724 if (!board.isSuspendedUpdate) { 1725 el.element2D.updateVisibility().updateRenderer(); 1726 } 1727 1728 return el; 1729 }; 1730 1731 JXG.registerElement('plane3d', JXG.createPlane3D); 1732 1733 /** 1734 * @class The line that is the intersection of two (infinite) plane elements in 3D. 1735 * 1736 * @pseudo 1737 * @name IntersectionLine3D 1738 * @augments JXG.Line3D 1739 * @constructor 1740 * @type JXG.Line3D 1741 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1742 * @param {JXG.Plane3D_JXG.Plane3D} el1,el2 The result will be the intersection of el1 and el2. 1743 * @example 1744 * // Create the intersection line of two planes 1745 * var view = board.create( 1746 * 'view3d', 1747 * [[-6, -3], [8, 8], 1748 * [[-1, 3], [-1, 3], [-1, 3]]], 1749 * { 1750 * xPlaneRear: {visible:false}, 1751 * yPlaneRear: {visible:false}, 1752 * zPlaneRear: {fillOpacity: 0.2, gradient: null} 1753 * } 1754 * ); 1755 * var a = view.create('point3d', [2, 2, 0]); 1756 * 1757 * var p1 = view.create( 1758 * 'plane3d', 1759 * [a, [1, 0, 0], [0, 1, 0]], 1760 * {fillColor: '#00ff80'} 1761 * ); 1762 * var p2 = view.create( 1763 * 'plane3d', 1764 * [a, [-2, 1, 1], [1, -2, 1]], 1765 * {fillColor: '#ff0000'} 1766 * ); 1767 * 1768 * var i = view.create('intersectionline3d', [p1, p2]); 1769 * 1770 * </pre><div id="JXGdb931076-b29a-4eff-b97e-4251aaf24943" class="jxgbox" style="width: 300px; height: 300px;"></div> 1771 * <script type="text/javascript"> 1772 * (function() { 1773 * var board = JXG.JSXGraph.initBoard('JXGdb931076-b29a-4eff-b97e-4251aaf24943', 1774 * {boundingbox: [-8, 8, 8,-8], axis: false, pan: {enabled: false}, showcopyright: false, shownavigation: false}); 1775 * var view = board.create( 1776 * 'view3d', 1777 * [[-6, -3], [8, 8], 1778 * [[-1, 3], [-1, 3], [-1, 3]]], 1779 * { 1780 * xPlaneRear: {visible:false}, 1781 * yPlaneRear: {visible:false}, 1782 * zPlaneRear: {fillOpacity: 0.2, gradient: null} 1783 * } 1784 * ); 1785 * var a = view.create('point3d', [2, 2, 0]); 1786 * 1787 * var p1 = view.create( 1788 * 'plane3d', 1789 * [a, [1, 0, 0], [0, 1, 0]], 1790 * {fillColor: '#00ff80'} 1791 * ); 1792 * var p2 = view.create( 1793 * 'plane3d', 1794 * [a, [-2, 1, 1], [1, -2, 1]], 1795 * {fillColor: '#ff0000'} 1796 * ); 1797 * 1798 * var i = view.create('intersectionline3d', [p1, p2]); 1799 * 1800 * })(); 1801 * 1802 * </script><pre> 1803 * 1804 */ 1805 JXG.createIntersectionLine3D = function (board, parents, attributes) { 1806 var view = parents[0], 1807 el1 = parents[1], 1808 el2 = parents[2], 1809 ixnLine, i, func, 1810 attr = Type.copyAttributes(attributes, board.options, "intersectionline3d"), 1811 pts = []; 1812 1813 func = Geometry.intersectionFunction3D(view, el1, el2); 1814 for (i = 0; i < 2; i++) { 1815 pts[i] = view.create('point3d', func[i], attr['point' + (i + 1)]); 1816 } 1817 ixnLine = view.create('line3d', pts, attr); 1818 1819 try { 1820 el1.addChild(ixnLine); 1821 el2.addChild(ixnLine); 1822 } catch (_e) { 1823 throw new Error( 1824 "JSXGraph: Can't create 'intersection' with parent types '" + 1825 typeof parents[1] + 1826 "' and '" + 1827 typeof parents[2] + 1828 "'." 1829 ); 1830 } 1831 1832 ixnLine.type = Const.OBJECT_TYPE_INTERSECTION_LINE3D; 1833 ixnLine.elType = 'intersectionline3d'; 1834 ixnLine.setParents([el1.id, el2.id]); 1835 1836 return ixnLine; 1837 }; 1838 1839 JXG.registerElement('intersectionline3d', JXG.createIntersectionLine3D); 1840