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