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