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