1 /*
  2     Copyright 2008-2023
  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";
 32 import Const from "../base/constants";
 33 import Mat from "../math/math";
 34 import Geometry from "../math/geometry";
 35 import Type from "../utils/type";
 36 //, GeometryElement3D) {
 37 
 38 /**
 39  * A 3D point is the basic geometric element.
 40  * @class Creates a new 3D point object. Do not use this constructor to create a 3D point. Use {@link JXG.View3D#create} with
 41  * type {@link Point3D} instead.
 42  * @augments JXG.GeometryElement3D
 43  * @augments JXG.GeometryElement
 44  * @param {JXG.View3D} view The 3D view the point is drawn on.
 45  * @param {Function,Array} F Array of numbers, array of functions or function returning an array with defines the user coordinates of the point.
 46  * @parame {JXG.GeometryElement3D} slide Object the 3D point should be bound to. If null, the point is a free point.
 47  * @param {Object} attributes An object containing visual properties like in {@link JXG.Options#point3d} and
 48  * {@link JXG.Options#elements}, and optional a name and an id.
 49  * @see JXG.Board#generateName
 50  */
 51 JXG.Point3D = function (view, F, slide, attributes) {
 52     this.constructor(view.board, attributes, Const.OBJECT_TYPE_POINT3D, Const.OBJECT_CLASS_3D);
 53     this.constructor3D(view, "point3d");
 54 
 55     this.board.finalizeAdding(this);
 56 
 57     /**
 58      * Homogeneous coordinates of a Point3D, i.e. array of length 4: [w, x, y, z]. Usually, w=1 for finite points and w=0 for points
 59      * which are infinitely far.
 60      *
 61      * @example
 62      *   p.coords;
 63      *
 64      * @name Point3D#coords
 65      * @type Array
 66      * @private
 67      */
 68     this.coords = [0, 0, 0, 0];
 69 
 70     /**
 71      * Function or array of functions or array of numbers defining the coordinates of the point, used in {@link updateCoords}.
 72      *
 73      * @name F
 74      * @memberOf Point3D
 75      * @function
 76      * @private
 77      *
 78      * @see updateCoords
 79      */
 80     this.F = F;
 81 
 82     /**
 83      * Optional slide element, i.e. element the Point3D lives on.
 84      *
 85      * @example
 86      *   p.slide;
 87      *
 88      * @name Point3D#slide
 89      * @type JXG.GeometryElement3D
 90      * @default null
 91      * @private
 92      *
 93      */
 94     this.slide = slide;
 95 
 96     /**
 97      * Get x-coordinate of a 3D point.
 98      *
 99      * @name X
100      * @memberOf Point3D
101      * @function
102      * @returns {Number}
103      *
104      * @example
105      *   p.X();
106      */
107     this.X = function () {
108         return this.coords[1];
109     };
110 
111     /**
112      * Get y-coordinate of a 3D point.
113      *
114      * @name Y
115      * @memberOf Point3D
116      * @function
117      * @returns Number
118      *
119      * @example
120      *   p.Y();
121      */
122     this.Y = function () {
123         return this.coords[2];
124     };
125 
126     /**
127      * Get z-coordinate of a 3D point.
128      *
129      * @name Z
130      * @memberOf Point3D
131      * @function
132      * @returns Number
133      *
134      * @example
135      *   p.Z();
136      */
137     this.Z = function () {
138         return this.coords[3];
139     };
140 
141     /**
142      * Store the last position of the 2D point for the optimizer.
143      *
144      * @type Array
145      * @private
146      */
147     this._params = null;
148 
149     this._c2d = null;
150 
151     this.methodMap = Type.deepCopy(this.methodMap, {
152         // TODO
153     });
154 };
155 JXG.Point3D.prototype = new JXG.GeometryElement();
156 Type.copyPrototypeMethods(JXG.Point3D, JXG.GeometryElement3D, "constructor3D");
157 
158 JXG.extend(
159     JXG.Point3D.prototype,
160     /** @lends JXG.Point3D.prototype */ {
161         /**
162          * Update the homogeneous coords array.
163          *
164          * @name updateCoords
165          * @memberOf Point3D
166          * @function
167          * @returns {Object} Reference to the Point3D object
168          * @private
169          * @example
170          *    p.updateCoords();
171          */
172         updateCoords: function () {
173             var i;
174 
175             if (Type.isFunction(this.F)) {
176                 this.coords = [1].concat(Type.evaluate(this.F));
177             } else {
178                 this.coords[0] = 1;
179                 for (i = 0; i < 3; i++) {
180                     // Attention: if F is array of numbers, coords are not updated.
181                     // Otherwise, dragging will not work anymore.
182                     if (Type.isFunction(this.F[i])) {
183                         this.coords[i + 1] = Type.evaluate(this.F[i]);
184                     }
185                 }
186             }
187             return this;
188         },
189 
190         /**
191          * Initialize the coords array.
192          *
193          * @private
194          * @returns {Object} Reference to the Point3D object
195          */
196         initCoords: function () {
197             var i;
198 
199             if (Type.isFunction(this.F)) {
200                 this.coords = [1].concat(Type.evaluate(this.F));
201             } else {
202                 this.coords[0] = 1;
203                 for (i = 0; i < 3; i++) {
204                     this.coords[i + 1] = Type.evaluate(this.F[i]);
205                 }
206             }
207             return this;
208         },
209 
210         /**
211          * Normalize homogeneous coordinates such the the first coordinate (the w-coordinate is equal to 1 or 0)-
212          *
213          * @name normalizeCoords
214          * @memberOf Point3D
215          * @function
216          * @returns {Object} Reference to the Point3D object
217          * @private
218          * @example
219          *    p.normalizeCoords();
220          */
221         normalizeCoords: function () {
222             if (Math.abs(this.coords[0]) > Mat.eps) {
223                 this.coords[1] /= this.coords[0];
224                 this.coords[2] /= this.coords[0];
225                 this.coords[3] /= this.coords[0];
226                 this.coords[0] = 1.0;
227             }
228             return this;
229         },
230 
231         /**
232          * Set the position of a 3D point.
233          *
234          * @name setPosition
235          * @memberOf Point3D
236          * @function
237          * @param {Array} coords 3D coordinates. Either of the form [x,y,z] (Euclidean) or [w,x,y,z] (homogeneous).
238          * @param {Boolean} [noevent] If true, no events are triggered.
239          * @returns {Object} Reference to the Point3D object
240          *
241          * @example
242          *    p.setPosition([1, 3, 4]);
243          */
244         setPosition: function (coords, noevent) {
245             var c = this.coords,
246                 oc = this.coords.slice(); // Copy of original values
247 
248             if (coords.length === 3) {
249                 // Euclidean coordinates
250                 c[0] = 1.0;
251                 c[1] = coords[0];
252                 c[2] = coords[1];
253                 c[3] = coords[2];
254             } else {
255                 // Homogeneous coordinates (normalized)
256                 c[0] = coords[0];
257                 c[1] = coords[1];
258                 c[2] = coords[2];
259                 c[3] = coords[2];
260                 this.normalizeCoords();
261             }
262 
263             // console.log(el.emitter, !noevent, oc[0] !== c[0] || oc[1] !== c[1] || oc[2] !== c[2] || oc[3] !== c[3]);
264             // Not yet working
265             // if (el.emitter && !noevent &&
266             //     (oc[0] !== c[0] || oc[1] !== c[1] || oc[2] !== c[2] || oc[3] !== c[3])) {
267             //     this.triggerEventHandlers(['update3D'], [oc]);
268             // }
269             return this;
270         },
271 
272         update: function (drag) {
273             var c3d, foot;
274 
275             // Update is called from two methods:
276             // Once in setToPosition and
277             // once in the subsequent board.update
278             if (
279                 this.element2D.draggable() &&
280                 Geometry.distance(this._c2d, this.element2D.coords.usrCoords) !== 0
281             ) {
282                 if (this.slide) {
283                     this.projectCoords2Surface();
284                 } else {
285                     if (this.view.isVerticalDrag()) {
286                         // Drag the point in its vertical to the xy plane
287                         c3d = this.view.project2DTo3DVertical(this.element2D, this.coords);
288                     } else {
289                         // Drag the point in its xy plane
290                         foot = [1, 0, 0, this.coords[3]];
291                         c3d = this.view.project2DTo3DPlane(this.element2D, [1, 0, 0, 1], foot);
292                     }
293                     if (c3d[0] !== 0) {
294                         this.coords = this.view.project3DToCube(c3d);
295                     }
296                 }
297             } else {
298                 this.updateCoords();
299                 // Update 2D point from its 3D view
300                 this.element2D.coords.setCoordinates(
301                     Const.COORDS_BY_USER,
302                     this.view.project3DTo2D([1, this.X(), this.Y(), this.Z()])
303                 );
304             }
305             this._c2d = this.element2D.coords.usrCoords.slice();
306 
307             return this;
308         },
309 
310         updateRenderer: function () {
311             this.needsUpdate = false;
312             return this;
313         },
314 
315         projectCoords2Surface: function () {
316             var n = 2, // # of variables
317                 m = 2, // number of constraints
318                 x = [0, 0],
319                 // Various Cobyla constants, see Cobyla docs in Cobyja.js
320                 rhobeg = 5.0,
321                 rhoend = 1.0e-6,
322                 iprint = 0,
323                 maxfun = 200,
324                 surface = this.slide,
325                 that = this,
326                 r,
327                 c3d,
328                 c2d,
329                 _minFunc;
330 
331             if (surface === null) {
332                 return;
333             }
334 
335             _minFunc = function (n, m, x, con) {
336                 var c3d = [
337                         1,
338                         surface.X(x[0], x[1]),
339                         surface.Y(x[0], x[1]),
340                         surface.Z(x[0], x[1])
341                     ],
342                     c2d = that.view.project3DTo2D(c3d);
343 
344                 con[0] = that.element2D.X() - c2d[1];
345                 con[1] = that.element2D.Y() - c2d[2];
346 
347                 return con[0] * con[0] + con[1] * con[1];
348             };
349             if (Type.exists(this._params)) {
350                 x = this._params.slice();
351             }
352             r = Mat.Nlp.FindMinimum(_minFunc, n, m, x, rhobeg, rhoend, iprint, maxfun);
353 
354             c3d = [1, surface.X(x[0], x[1]), surface.Y(x[0], x[1]), surface.Z(x[0], x[1])];
355             c2d = this.view.project3DTo2D(c3d);
356             this._params = x;
357             this.coords = c3d;
358             this.element2D.coords.setCoordinates(Const.COORDS_BY_USER, c2d);
359             this._c2d = c2d;
360         },
361 
362         // Not yet working
363         __evt__update3D: function (oc) {}
364     }
365 );
366 
367 /**
368  * @class This element is used to provide a constructor for a 3D Point.
369  * @pseudo
370  * @description A Point3D object is defined by 3 coordinates [x,y,z].
371  * <p>
372  * All numbers can also be provided as functions returning a number.
373  *
374  * @name Point3D
375  * @augments JXG.Point3D
376  * @constructor
377  * @throws {Exception} If the element cannot be constructed with the given parent
378  * objects an exception is thrown.
379  * @param {number,function_number,function_number,function} x,y,z The coordinates are given as x, y, z consisting of numbers of functions.
380  * @param {array,function} F Alternatively, the coordinates can be supplied as
381  *  <ul>
382  *   <li>array arr=[x,y,z] of length 3 consisting of numbers or
383  *   <li>function returning an array [x,y,z] of length 3 of numbers.
384  * </ul>
385  *
386  * @example
387  *    var bound = [-5, 5];
388  *    var view = board.create('view3d',
389  *        [[-6, -3], [8, 8],
390  *        [bound, bound, bound]],
391  *        {});
392  *    var p = view.create('point3d', [1, 2, 2], { name:'A', size: 5 });
393  *    var q = view.create('point3d', function() { return [p.X(), p.Y(), p.Z() - 3]; }, { name:'B', size: 5, fixed: true });
394  *
395  * </pre><div id="JXGb9ee8f9f-3d2b-4f73-8221-4f82c09933f1" class="jxgbox" style="width: 300px; height: 300px;"></div>
396  * <script type="text/javascript">
397  *     (function() {
398  *         var board = JXG.JSXGraph.initBoard('JXGb9ee8f9f-3d2b-4f73-8221-4f82c09933f1',
399  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
400  *         var bound = [-5, 5];
401  *         var view = board.create('view3d',
402  *             [[-6, -3], [8, 8],
403  *             [bound, bound, bound]],
404  *             {});
405  *         var p = view.create('point3d', [1, 2, 2], { name:'A', size: 5 });
406  *         var q = view.create('point3d', function() { return [p.X(), p.Y(), p.Z() - 3]; }, { name:'B', size: 5 });
407  *     })();
408  *
409  * </script><pre>
410  *
411  */
412 JXG.createPoint3D = function (board, parents, attributes) {
413     //   parents[0]: view
414     // followed by
415     //   parents[1]: function or array
416     // or
417     //   parents[1..3]: coordinates
418 
419     var view = parents[0],
420         attr, F, slide, c2d, el;
421 
422     // If the last element of parents is a 3D object,
423     // the point is a glider on that element.
424     if (parents.length > 2 && Type.exists(parents[parents.length - 1].is3D)) {
425         slide = parents.pop();
426     } else {
427         slide = null;
428     }
429 
430     if (parents.length === 2) {
431         // [view, array|fun] (Array [x, y, z] | function) returning [x, y, z]
432         F = parents[1];
433     } else if (parents.length === 4) {
434         // [view, x, y, z], (3 numbers | functions)
435         F = parents.slice(1);
436     } else {
437         throw new Error(
438             "JSXGraph: Can't create point3d with parent types '" +
439                 typeof parents[0] +
440                 "' and '" +
441                 typeof parents[1] +
442                 "'." +
443                 "\nPossible parent types: [[x,y,z]], [x,y,z]"
444         );
445         //  "\nPossible parent types: [[x,y,z]], [x,y,z], [element,transformation]"); // TODO
446     }
447 
448     attr = Type.copyAttributes(attributes, board.options, 'point3d');
449     el = new JXG.Point3D(view, F, slide, attr);
450     el.initCoords();
451 
452     c2d = view.project3DTo2D(el.coords);
453 
454     attr = el.setAttr2D(attr);
455     el.element2D = view.create('point', c2d, attr);
456     el.addChild(el.element2D);
457     el.inherits.push(el.element2D);
458     el.element2D.setParents(el);
459 
460     el._c2d = el.element2D.coords.usrCoords.slice(); // Store a copy of the coordinates to detect dragging
461 
462     return el;
463 };
464 
465 JXG.registerElement("point3d", JXG.createPoint3D);
466