1 /* 2 Copyright 2008-2025 3 Matthias Ehmann, 4 Carsten Miller, 5 Andreas Walter, 6 Alfred Wassermann 7 8 This file is part of JSXGraph. 9 10 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 11 12 You can redistribute it and/or modify it under the terms of the 13 14 * GNU Lesser General Public License as published by 15 the Free Software Foundation, either version 3 of the License, or 16 (at your option) any later version 17 OR 18 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 19 20 JSXGraph is distributed in the hope that it will be useful, 21 but WITHOUT ANY WARRANTY; without even the implied warranty of 22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 GNU Lesser General Public License for more details. 24 25 You should have received a copy of the GNU Lesser General Public License and 26 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 27 and <https://opensource.org/licenses/MIT/>. 28 */ 29 /*global JXG:true, define: true*/ 30 31 import JXG from "../jxg.js"; 32 import Const from "../base/constants.js"; 33 import Type from "../utils/type.js"; 34 import Mat from "../math/math.js"; 35 import Geometry from "../math/geometry.js"; 36 37 /** 38 * A 3D point is the basic geometric element. 39 * @class Creates a new 3D point object. Do not use this constructor to create a 3D point. Use {@link JXG.View3D#create} with 40 * type {@link Point3D} instead. 41 * @augments JXG.GeometryElement3D 42 * @augments JXG.GeometryElement 43 * @param {JXG.View3D} view The 3D view the point is drawn on. 44 * @param {Function|Array} F Array of numbers, array of functions or function returning an array with defines the user coordinates of the point. 45 * @param {JXG.GeometryElement3D} slide Object the 3D point should be bound to. If null, the point is a free point. 46 * @param {Object} attributes An object containing visual properties like in {@link JXG.Options#point3d} and 47 * {@link JXG.Options#elements}, and optional a name and an id. 48 * @see JXG.Board#generateName 49 */ 50 JXG.Point3D = function (view, F, slide, attributes) { 51 this.constructor(view.board, attributes, Const.OBJECT_TYPE_POINT3D, Const.OBJECT_CLASS_3D); 52 this.constructor3D(view, "point3d"); 53 54 this.board.finalizeAdding(this); 55 56 // add the new point to its view's point list 57 // if (view.visProp.depthorderpoints) { 58 // view.points.push(this); 59 // } 60 61 /** 62 * Homogeneous coordinates of a Point3D, i.e. array of length 4 containing numbers: [w, x, y, z]. 63 * Usually, w=1 for finite points and w=0 for points which are infinitely far. 64 * If coordinates of the point are supplied as functions, they are resolved in {@link Point3D#updateCoords} into numbers. 65 * 66 * @example 67 * p.coords; 68 * 69 * @name Point3D#coords 70 * @type Array 71 * @private 72 */ 73 this.coords = [0, 0, 0, 0]; 74 this.initialCoords = [0, 0, 0, 0]; 75 76 /** 77 * Function or array of functions or array of numbers defining the coordinates of the point, used in {@link updateCoords}. 78 * 79 * @name Point3D#F 80 * @function 81 * @private 82 * 83 * @see updateCoords 84 */ 85 this.F = F; 86 87 /** 88 * Optional slide element, i.e. element the Point3D lives on. 89 * 90 * @example 91 * p.slide; 92 * 93 * @name Point3D#slide 94 * @type JXG.GeometryElement3D 95 * @default null 96 * @private 97 * 98 */ 99 this.slide = slide; 100 101 /** 102 * In case, the point is a glider, store the preimage of the coordinates in terms of the parametric definition of the host element. 103 * That is, if the host element `slide` is a curve, and the coordinates of the point are equal to `p` and `u = this.position[0]`, then 104 * `p = [slide.X(u), slide.Y(u), slide.Z(u)]`. 105 * 106 * @type Array 107 * @private 108 */ 109 this.position = []; 110 111 /** 112 * An array of coordinates for moveTo(). An in-progress move can be updated or cancelled by updating or clearing this array. Use moveTo() instead of 113 * accessing this array directly. 114 * @type Array 115 * @private 116 */ 117 this.movePath = []; 118 this.moveCallback = null; 119 this.moveInterval = null; 120 121 122 this._c2d = null; 123 124 this.methodMap = Type.deepCopy(this.methodMap, { 125 // TODO 126 }); 127 }; 128 129 JXG.Point3D.prototype = new JXG.GeometryElement(); 130 Type.copyPrototypeMethods(JXG.Point3D, JXG.GeometryElement3D, "constructor3D"); 131 132 JXG.extend( 133 JXG.Point3D.prototype, 134 /** @lends JXG.Point3D.prototype */ { 135 136 /** 137 * Get x-coordinate of a 3D point. 138 * 139 * @name X 140 * @memberOf Point3D 141 * @function 142 * @returns {Number} 143 * 144 * @example 145 * p.X(); 146 */ 147 X: function () { 148 return this.coords[1]; 149 }, 150 151 /** 152 * Get y-coordinate of a 3D point. 153 * 154 * @name Y 155 * @memberOf Point3D 156 * @function 157 * @returns Number 158 * 159 * @example 160 * p.Y(); 161 */ 162 Y: function () { 163 return this.coords[2]; 164 }, 165 166 /** 167 * Get z-coordinate of a 3D point. 168 * 169 * @name Z 170 * @memberOf Point3D 171 * @function 172 * @returns Number 173 * 174 * @example 175 * p.Z(); 176 */ 177 Z: function () { 178 return this.coords[3]; 179 }, 180 181 /** 182 * Get w-coordinate of a 3D point. 183 * 184 * @name W 185 * @memberOf Point3D 186 * @function 187 * @returns Number 188 * 189 * @example 190 * p.W(); 191 */ 192 W: function () { 193 return this.coords[0]; 194 }, 195 196 /** 197 * Update the array {@link JXG.Point3D#coords} containing the homogeneous coords. 198 * 199 * @name updateCoords 200 * @memberOf Point3D 201 * @function 202 * @returns {Object} Reference to the Point3D object 203 * @private 204 * @see GeometryElement3D#update() 205 * @example 206 * p.updateCoords(); 207 */ 208 updateCoords: function () { 209 var i, 210 s = 0; 211 212 if (Type.isFunction(this.F)) { 213 this.coords = Type.evaluate(this.F); 214 if (this.coords.length === 3) { 215 this.coords.unshift(1); 216 } 217 } else { 218 if (this.F.length === 3) { 219 this.coords[0] = 1; 220 s = 1; 221 } 222 for (i = 0; i < this.F.length; i++) { 223 // Attention: if F is array of numbers, coords may not be updated. 224 // Otherwise, dragging will not work anymore. 225 if (Type.isFunction(this.F[i])) { 226 this.coords[s + i] = Type.evaluate(this.F[i]); 227 } 228 } 229 } 230 231 return this; 232 }, 233 234 /** 235 * Initialize the coords array. 236 * 237 * @private 238 * @returns {Object} Reference to the Point3D object 239 */ 240 initCoords: function () { 241 var i, 242 s = 0; 243 244 245 if (Type.isFunction(this.F)) { 246 this.coords = Type.evaluate(this.F); 247 if (this.coords.length === 3) { 248 this.coords.unshift(1); 249 } 250 } else { 251 if (this.F.length === 3) { 252 this.coords[0] = 1; 253 s = 1; 254 } 255 for (i = 0; i < this.F.length; i++) { 256 this.coords[s + i] = Type.evaluate(this.F[i]); 257 } 258 } 259 this.initialCoords = this.coords.slice(); 260 261 return this; 262 }, 263 264 /** 265 * Normalize homogeneous coordinates such the the first coordinate (the w-coordinate is equal to 1 or 0)- 266 * 267 * @name normalizeCoords 268 * @memberOf Point3D 269 * @function 270 * @returns {Object} Reference to the Point3D object 271 * @private 272 * @example 273 * p.normalizeCoords(); 274 */ 275 normalizeCoords: function () { 276 if (Math.abs(this.coords[0]) > 1.e-14) { 277 this.coords[1] /= this.coords[0]; 278 this.coords[2] /= this.coords[0]; 279 this.coords[3] /= this.coords[0]; 280 this.coords[0] = 1.0; 281 } 282 return this; 283 }, 284 285 /** 286 * Set the position of a 3D point. 287 * 288 * @name setPosition 289 * @memberOf Point3D 290 * @function 291 * @param {Array} coords 3D coordinates. Either of the form [x,y,z] (Euclidean) or [w,x,y,z] (homogeneous). 292 * @param {Boolean} [noevent] If true, no events are triggered (TODO) 293 * @returns {Object} Reference to the Point3D object 294 * 295 * @example 296 * p.setPosition([1, 3, 4]); 297 */ 298 setPosition: function (coords, noevent) { 299 var c = this.coords; 300 // oc = this.coords.slice(); // Copy of original values 301 302 if (coords.length === 3) { 303 // Euclidean coordinates 304 c[0] = 1.0; 305 c[1] = coords[0]; 306 c[2] = coords[1]; 307 c[3] = coords[2]; 308 } else { 309 // Homogeneous coordinates (normalized) 310 c[0] = coords[0]; 311 c[1] = coords[1]; 312 c[2] = coords[2]; 313 c[3] = coords[2]; 314 this.normalizeCoords(); 315 } 316 317 // console.log(el.emitter, !noevent, oc[0] !== c[0] || oc[1] !== c[1] || oc[2] !== c[2] || oc[3] !== c[3]); 318 // Not yet working TODO 319 // if (el.emitter && !noevent && 320 // (oc[0] !== c[0] || oc[1] !== c[1] || oc[2] !== c[2] || oc[3] !== c[3])) { 321 // this.triggerEventHandlers(['update3D'], [oc]); 322 // } 323 return this; 324 }, 325 326 // /** 327 // * Add transformations to this element. 328 // * @param {JXG.GeometryElement} el 329 // * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} 330 // * or an array of {@link JXG.Transformation}s. 331 // * @returns {JXG.CoordsElement} Reference to itself. 332 // */ 333 addTransform: function (el, transform) { 334 this.addTransformGeneric(el, transform); 335 return this; 336 }, 337 338 updateTransform: function () { 339 var c, i; 340 341 if (this.transformations.length === 0 || this.baseElement === null) { 342 return this; 343 } 344 345 if (this === this.baseElement) { 346 c = this.initialCoords; 347 } else { 348 c = this.baseElement.coords; 349 } 350 for (i = 0; i < this.transformations.length; i++) { 351 this.transformations[i].update(); 352 c = Mat.matVecMult(this.transformations[i].matrix, c); 353 } 354 this.coords = c; 355 356 return this; 357 }, 358 359 // Already documented in JXG.GeometryElement 360 update: function (drag) { 361 var c3d, // Homogeneous 3D coordinates 362 foot, res; 363 364 if ( 365 this.element2D.draggable() && 366 Geometry.distance(this._c2d, this.element2D.coords.usrCoords) !== 0 367 ) { 368 // Update is called from board.updateElements, e.g. after manipulating a 369 // a slider or dragging a point. 370 // Usually this followed by an update call using the other branch below. 371 if (this.view.isVerticalDrag()) { 372 // Drag the point in its vertical to the xy plane 373 // If the point is outside of bbox3d, 374 // c3d is already corrected. 375 c3d = this.view.project2DTo3DVertical(this.element2D, this.coords); 376 } else { 377 // Drag the point in its xy plane 378 foot = [1, 0, 0, this.coords[3]]; 379 c3d = this.view.project2DTo3DPlane(this.element2D, [1, 0, 0, 1], foot); 380 } 381 382 if (c3d[0] !== 0) { 383 // Check if c3d is inside of view.bbox3d 384 // Otherwise, the coords are now corrected. 385 res = this.view.project3DToCube(c3d); 386 this.coords = res[0]; 387 388 if (res[1]) { 389 // The 3D coordinates have been corrected, now 390 // also correct the 2D element. 391 this.element2D.coords.setCoordinates( 392 Const.COORDS_BY_USER, 393 this.view.project3DTo2D(this.coords) 394 ); 395 } 396 if (this.slide) { 397 this.coords = this.slide.projectCoords([1, this.X(), this.Y(), this.Z()], this.position); 398 this.element2D.coords.setCoordinates( 399 Const.COORDS_BY_USER, 400 this.view.project3DTo2D(this.coords) 401 ); 402 } 403 } 404 405 } else { 406 // Update 2D point from its 3D view, e.g. when rotating the view 407 this.updateCoords() 408 .updateTransform(); 409 410 if (this.slide) { 411 this.coords = this.slide.projectCoords([1, this.X(), this.Y(), this.Z()], this.position); 412 } 413 c3d = this.coords; 414 this.element2D.coords.setCoordinates( 415 Const.COORDS_BY_USER, 416 this.view.project3DTo2D(c3d) 417 ); 418 // this.zIndex = Mat.matVecMult(this.view.matrix3DRotShift, c3d)[3]; 419 this.zIndex = Mat.innerProduct(this.view.matrix3DRotShift[3], c3d); 420 } 421 this._c2d = this.element2D.coords.usrCoords.slice(); 422 423 return this; 424 }, 425 426 // Already documented in JXG.GeometryElement 427 updateRenderer: function () { 428 this.needsUpdate = false; 429 return this; 430 }, 431 432 /** 433 * Check whether a point's position is finite, i.e. the first entry is not zero. 434 * @returns {Boolean} True if the first entry of the coordinate vector is not zero; false otherwise. 435 */ 436 testIfFinite: function () { 437 return Math.abs(this.coords[0]) > 1.e-12 ? true : false; 438 // return Type.cmpArrays(this.coords, [0, 0, 0, 0]); 439 }, 440 441 /** 442 * Calculate the distance from one point to another. If one of the points is on the plane at infinity, return positive infinity. 443 * @param {JXG.Point3D} pt The point to which the distance is calculated. 444 * @returns {Number} The distance 445 */ 446 distance: function (pt) { 447 var eps_sq = 1e-12, 448 c_this = this.coords, 449 c_pt = pt.coords; 450 451 if (c_this[0] * c_this[0] > eps_sq && c_pt[0] * c_pt[0] > eps_sq) { 452 return Mat.hypot( 453 c_pt[1] - c_this[1], 454 c_pt[2] - c_this[2], 455 c_pt[3] - c_this[3] 456 ); 457 } else { 458 return Number.POSITIVE_INFINITY; 459 } 460 }, 461 462 463 464 /** 465 * Starts an animated point movement towards the given coordinates <tt>where</tt>. 466 * The animation is done after <tt>time</tt> milliseconds. 467 * If the second parameter is not given or is equal to 0, coordinates are changed without animation. 468 * @param {Array} where Array containing the target coordinate in cartesian or homogenous form. 469 * @param {Number} [time] Number of milliseconds the animation should last. 470 * @param {Object} [options] Optional settings for the animation 471 * @param {function} [options.callback] A function that is called as soon as the animation is finished. 472 * @param {String} [options.effect='<>'|'>'|'<'] animation effects like speed fade in and out. possible values are 473 * '<>' for speed increase on start and slow down at the end (default), '<' for speed up, '>' for slow down, and '--' for constant speed during 474 * the whole animation. 475 * @see JXG.Point3D#moveAlong 476 * @see JXG.Point#moveTo 477 * @example 478 * // visit a coordinate, then use callback to visit a second coordinate. 479 * const board = JXG.JSXGraph.initBoard('jxgbox') 480 * var view = board.create( 481 * 'view3d', 482 * [[-6, -3], [8, 8], 483 * [[-3, 3], [-3, 3], [-3, 3]]]); 484 * 485 * let A = view.create('point3d', [0, 0, 0]); 486 * 487 * // move A with callbacks 488 * board.create('button', [-4, 4.3, 'callbacks', () => { 489 * A.moveTo([3, 3, 3], 3000, 490 * { 491 * callback: () => A.moveTo([-3, -3, -3], 3000, { 492 * callback: () => A.moveTo([0, 0, 0],1000), effect: '<' 493 * }), 494 * effect: '>' 495 * }) 496 * }]) 497 * 498 * // move A with async/await 499 * board.create('button', [-3, 4.3, 'async/await', async () => { 500 * await A.moveTo([3, 3, 3], 3000, { effect: '>' }); 501 * await A.moveTo([-3, -3, -3], 3000, { effect: '<' }); 502 * A.moveTo([0, 0, 0],1000) 503 * }]) 504 * </pre><div id="JXG0f35a50e-e99d-11e8-a1ca-cba3b0c2aad4" class="jxgbox" style="width: 300px; height: 300px;"></div> 505 * <script type="text/javascript"> 506 * { 507 * const board = JXG.JSXGraph.initBoard('JXG0f35a50e-e99d-11e8-a1ca-cba3b0c2aad4') 508 * var view = board.create( 509 * 'view3d', 510 * [[-6, -3], [8, 8], 511 * [[-3, 3], [-3, 3], [-3, 3]]]); 512 * 513 * let A = view.create('point3d', [0, 0, 0]); 514 * // move A with callbacks 515 * board.create('button', [-4, 4.3, 'callbacks', () => { 516 * A.moveTo([3, 3, 3], 3000, 517 * { 518 * callback: () => A.moveTo([-3, -3, -3], 3000, { 519 * callback: () => A.moveTo([0, 0, 0],1000), effect: '<' 520 * }), 521 * effect: '>' 522 * }) 523 * }]) 524 * 525 * // move A with async/await 526 * board.create('button', [-1, 4.3, 'async/await', async () => { 527 * await A.moveTo([3, 3, 3], 3000, { effect: '>' }); 528 * await A.moveTo([-3, -3, -3], 3000, { effect: '<' }); 529 * A.moveTo([0, 0, 0],1000) 530 * }]) 531 * } 532 * </script><pre> 533 */ 534 moveTo: function (where, time, options) { 535 options = options || {}; 536 537 var i, 538 steps = Math.ceil(time / this.board.attr.animationdelay), 539 X = where[0], 540 Y = where[1], 541 Z = where[2], 542 dX = this.coords[1] - X, 543 dY = this.coords[2] - Y, 544 dZ = this.coords[3] - Z, 545 doneCallback = () => { }, 546 stepFun; 547 548 if (options.callback) 549 doneCallback = options.callback; // unload 550 551 552 /** @ignore */ 553 stepFun = function (i) { 554 var x = i / steps; // absolute progress of the animatin 555 556 if (options.effect) { 557 if (options.effect === "<>") { 558 return Math.pow(Math.sin((x * Math.PI) / 2), 2); 559 } 560 if (options.effect === "<") { // cubic ease in 561 return x * x * x; 562 } 563 if (options.effect === ">") { // cubic ease out 564 return 1 - Math.pow(1 - x, 3); 565 } 566 if (options.effect === "==") { 567 return i / steps; // linear 568 } 569 throw new Error("valid effects are '==', '<>', '>', and '<'."); 570 } 571 return i / steps; // default 572 }; 573 574 // immediate move, no time 575 if ( 576 !Type.exists(time) || 577 time === 0 578 // check for tiny move, is this necessary? 579 // Math.abs(where.usrCoords[0] - this.coords.usrCoords[0]) > Mat.eps 580 ) { 581 this.setPosition([X, Y, Z], true); // no event here 582 return this.board.update(this); 583 } 584 585 // In case there is no callback and we are already at the endpoint we can stop here 586 if ( 587 !Type.exists(options.callback) && 588 Math.abs(dX) < Mat.eps && 589 Math.abs(dY) < Mat.eps && 590 Math.abs(dZ) < Mat.eps 591 ) { 592 return this; 593 } 594 595 this.animationPath = []; 596 for (i = steps; i >= 0; i--) { 597 this.animationPath[steps - i] = [ 598 X + dX * stepFun(i), 599 Y + dY * stepFun(i), 600 Z + dZ * stepFun(i) 601 ]; 602 } 603 604 return this.moveAlong(this.animationPath, time, 605 { callback: doneCallback }); 606 607 }, 608 609 /** 610 * Move along a path defined by an array of coordinates 611 * @param {number[][]} [traversePath] Array of path coordinates (either cartesian or homogenous). 612 * @param {number} [time] Number of milliseconds the animation should last. 613 * @param {Object} [options] 'callback' and 'interpolate'. see {@link JXG.CoordsElement#moveAlong}, 614 * @example 615 *const board = JXG.JSXGraph.initBoard('jxgbox') 616 *var view = board.create( 617 * 'view3d', 618 * [[-6, -3], [8, 8], 619 * [[-3, 3], [-3, 3], [-3, 3]]]); 620 * 621 * board.create('button', [-4, 4.5, 'start', () => { 622 * let A = view.create('point3d', [0, 0, 0]); 623 * A.moveAlong([[3, 3, 3], [-2, -1, -2], [-1, -1, -1], [-1, -2, 1]], 3000, 624 * { callback: () => board.create('text', [-4, 4, 'done!']) }) 625 *}]) 626 * 627 * </pre><div id="JXGa45032e5-a517-4f1d-868a-abc698d344cf" class="jxgbox" style="width: 300px; height: 300px;"></div> 628 * <script type="text/javascript"> 629 * (function() { 630 * const board = JXG.JSXGraph.initBoard("JXGa45032e5-a517-4f1d-868a-abc698d344cf") 631 * var view = board.create( 632 * 'view3d', 633 * [[-6, -3], [8, 8], 634 * [[-3, 3], [-3, 3], [-3, 3]]]); 635 * 636 * board.create('button', [-4, 4.5, 'start', () => { 637 * let A = view.create('point3d', [0, 0, 0]); 638 * A.moveAlong([[3, 3, 3], [-2, -1, -2], [-1, -1, -1], [-1, -2, 1]], 3000, 639 * { callback: () => board.create('text', [-4, 4, 'done!']) }) 640 * }]) 641 * 642 * })(); 643 * 644 * </script><pre> 645 * 646 */ 647 moveAlong: function (traversePath, time, options) { 648 let stepTime = time/traversePath.length; // will be same as this.board.attr.animationdelay if called by MoveTo 649 650 651 // unload the options 652 if (Type.isObject(options)) { 653 if ('callback' in options) 654 this.moveCallback = options.callback; 655 // TODO:add interpolation using Neville. How? easiest is add interpolation to path before start 656 // if ('interpolate' in options) interpolate = options.interpolate; 657 } 658 659 660 if (this.movePath.length > 0) { // existing move in progress 661 this.movePath = traversePath; // set the new path and return ?? 662 return; // promise is still outstanding 663 } 664 665 // no move currently in progress 666 this.movePath = traversePath; // set the new path and return a promise 667 return new Promise((resolve, reject) => { 668 this.moveInterval = setInterval(() => { 669 if (this.movePath.length > 0) { 670 let coord = this.movePath.shift(); 671 this.setPosition(coord, true); // no events during transit 672 this.board.update(this); 673 } 674 if (this.movePath.length === 0) { // now shorter than previous test 675 clearInterval(this.moveInterval); 676 resolve(); 677 if (Type.isFunction(this.moveCallback)) { 678 this.moveCallback(); // invoke the callback 679 } 680 } 681 }, stepTime); 682 }); 683 }, 684 685 686 687 688 // Not yet working 689 __evt__update3D: function (oc) { } 690 } 691 ); 692 693 /** 694 * @class A Point3D object is defined by three coordinates [x,y,z], or a function returning an array with three numbers. 695 * Alternatively, all numbers can also be provided as functions returning a number. 696 * 697 * @pseudo 698 * @name Point3D 699 * @augments JXG.Point3D 700 * @constructor 701 * @throws {Exception} If the element cannot be constructed with the given parent 702 * objects an exception is thrown. 703 * @param {number,function_number,function_number,function_JXG.GeometryElement3D} x,y,z,[slide=undefined] The coordinates are given as x, y, z consisting of numbers or functions. 704 * If an optional 3D element "slide" is supplied, the point is a glider on that element. At the time of version v1.11, only elements of type line3d are supperted as glider hosts. 705 * @param {array,function_JXG.GeometryElement3D} F,[slide=null] Alternatively, the coordinates can be supplied as 706 * <ul> 707 * <li>function returning an array [x,y,z] of length 3 of numbers or 708 * <li>array arr=[x,y,z] of length 3 consisting of numbers 709 * </ul> 710 * If an optional 3D element "slide" is supplied, the point is a glider on that element. 711 * 712 * @example 713 * var bound = [-5, 5]; 714 * var view = board.create('view3d', 715 * [[-6, -3], [8, 8], 716 * [bound, bound, bound]], 717 * {}); 718 * var p = view.create('point3d', [1, 2, 2], { name:'A', size: 5 }); 719 * var q = view.create('point3d', function() { return [p.X(), p.Y(), p.Z() - 3]; }, { name:'B', size: 3, fixed: true }); 720 * var w = view.create('point3d', [ () => p.X() + 3, () => p.Y(), () => p.Z() - 2], { name:'C', size: 3, fixed: true }); 721 * 722 * </pre><div id="JXGb9ee8f9f-3d2b-4f73-8221-4f82c09933f1" class="jxgbox" style="width: 300px; height: 300px;"></div> 723 * <script type="text/javascript"> 724 * (function() { 725 * var board = JXG.JSXGraph.initBoard('JXGb9ee8f9f-3d2b-4f73-8221-4f82c09933f1', 726 * {boundingbox: [-8, 8, 8,-8], axis: false, pan: {enabled: false}, showcopyright: false, shownavigation: false}); 727 * var bound = [-5, 5]; 728 * var view = board.create('view3d', 729 * [[-6, -3], [8, 8], 730 * [bound, bound, bound]], 731 * {}); 732 * var p = view.create('point3d', [1, 2, 2], { name:'A', size: 5 }); 733 * var q = view.create('point3d', function() { return [p.X(), p.Y(), p.Z() - 3]; }, { name:'B', size: 3 }); 734 * var w = view.create('point3d', [ () => p.X() + 3, () => p.Y(), () => p.Z() - 2], { name:'C', size: 3, fixed: true }); 735 * })(); 736 * 737 * </script><pre> 738 * 739 * @example 740 * // Glider on sphere 741 * var view = board.create( 742 * 'view3d', 743 * [[-6, -3], [8, 8], 744 * [[-3, 3], [-3, 3], [-3, 3]]], 745 * { 746 * depthOrder: { 747 * enabled: true 748 * }, 749 * projection: 'central', 750 * xPlaneRear: {fillOpacity: 0.2, gradient: null}, 751 * yPlaneRear: {fillOpacity: 0.2, gradient: null}, 752 * zPlaneRear: {fillOpacity: 0.2, gradient: null} 753 * } 754 * ); 755 * 756 * // Two points 757 * var center = view.create('point3d', [0, 0, 0], {withLabel: false, size: 2}); 758 * var point = view.create('point3d', [2, 0, 0], {withLabel: false, size: 2}); 759 * 760 * // Sphere 761 * var sphere = view.create('sphere3d', [center, point], {fillOpacity: 0.8}); 762 * 763 * // Glider on sphere 764 * var glide = view.create('point3d', [2, 2, 0, sphere], {withLabel: false, color: 'red', size: 4}); 765 * var l1 = view.create('line3d', [glide, center], { strokeWidth: 2, dash: 2 }); 766 * 767 * </pre><div id="JXG672fe3c7-e6fd-48e0-9a24-22f51f2dfa71" class="jxgbox" style="width: 300px; height: 300px;"></div> 768 * <script type="text/javascript"> 769 * (function() { 770 * var board = JXG.JSXGraph.initBoard('JXG672fe3c7-e6fd-48e0-9a24-22f51f2dfa71', 771 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 772 * var view = board.create( 773 * 'view3d', 774 * [[-6, -3], [8, 8], 775 * [[-3, 3], [-3, 3], [-3, 3]]], 776 * { 777 * depthOrder: { 778 * enabled: true 779 * }, 780 * projection: 'central', 781 * xPlaneRear: {fillOpacity: 0.2, gradient: null}, 782 * yPlaneRear: {fillOpacity: 0.2, gradient: null}, 783 * zPlaneRear: {fillOpacity: 0.2, gradient: null} 784 * } 785 * ); 786 * 787 * // Two points 788 * var center = view.create('point3d', [0, 0, 0], {withLabel: false, size: 2}); 789 * var point = view.create('point3d', [2, 0, 0], {withLabel: false, size: 2}); 790 * 791 * // Sphere 792 * var sphere = view.create('sphere3d', [center, point], {fillOpacity: 0.8}); 793 * 794 * // Glider on sphere 795 * var glide = view.create('point3d', [2, 2, 0, sphere], {withLabel: false, color: 'red', size: 4}); 796 * var l1 = view.create('line3d', [glide, center], { strokeWidth: 2, dash: 2 }); 797 * 798 * })(); 799 * 800 * </script><pre> 801 * 802 */ 803 JXG.createPoint3D = function (board, parents, attributes) { 804 // parents[0]: view 805 // followed by 806 // parents[1]: function or array 807 // or 808 // parents[1..3]: coordinates 809 810 var view = parents[0], 811 attr, F, slide, c2d, el, 812 base = null, 813 transform = null; 814 815 // If the last element of `parents` is a 3D object, 816 // the point is a glider on that element. 817 if (parents.length > 2 && 818 Type.exists(parents[parents.length - 1].is3D) && 819 !Type.isTransformationOrArray(parents[parents.length - 1]) 820 ) { 821 slide = parents.pop(); 822 } else { 823 slide = null; 824 } 825 826 if (parents.length === 2) { 827 // [view, array|fun] (Array [x, y, z] | function) returning [x, y, z] 828 F = parents[1]; 829 } else if (parents.length === 3 && 830 Type.isPoint3D(parents[1]) && 831 Type.isTransformationOrArray(parents[2]) 832 ) { 833 F = [0, 0, 0]; 834 base = parents[1]; 835 transform = parents[2]; 836 } else if (parents.length === 4) { 837 // [view, x, y, z], (3 numbers | functions) 838 F = parents.slice(1); 839 } else if (parents.length === 5) { 840 // [view, w, x, y, z], (4 numbers | functions) 841 F = parents.slice(1); 842 } else { 843 throw new Error( 844 "JSXGraph: Can't create point3d with parent types '" + 845 typeof parents[1] + 846 "' and '" + 847 typeof parents[2] + 848 "'." + 849 "\nPossible parent types: [[x,y,z]], [x,y,z], or [[x,y,z], slide], () => [x, y, z], or [point, transformation(s)]" 850 ); 851 // "\nPossible parent types: [[x,y,z]], [x,y,z], [element,transformation]"); // TODO 852 } 853 854 attr = Type.copyAttributes(attributes, board.options, 'point3d'); 855 el = new JXG.Point3D(view, F, slide, attr); 856 el.initCoords(); 857 if (base !== null && transform !== null) { 858 el.addTransform(base, transform); 859 } 860 861 c2d = view.project3DTo2D(el.coords); 862 863 attr = el.setAttr2D(attr); 864 el.element2D = view.create('point', c2d, attr); 865 el.element2D.view = view; 866 el.addChild(el.element2D); 867 el.inherits.push(el.element2D); 868 el.element2D.setParents(el); 869 870 // If this point is a glider, record that in the update tree 871 if (el.slide) { 872 el.slide.addChild(el); 873 el.setParents(el.slide); 874 } 875 if (base) { 876 el.setParents(base); 877 } 878 879 el._c2d = el.element2D.coords.usrCoords.slice(); // Store a copy of the coordinates to detect dragging 880 881 return el; 882 }; 883 884 JXG.registerElement("point3d", JXG.createPoint3D); 885 886