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