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/view3d'
 32 ], function (JXG, Const, Mat, Geometry, Type, ThreeD) {
 33     "use strict";
 34 
 35     /**
 36      * @class This element is used to provide a constructor for a 3D Point.
 37      * @pseudo
 38      * @description There are two possibilities to create a Line3D object.
 39      * <p>
 40      * First: the line in 3D is defined by two points in 3D (Point3D).
 41      * The points can be either existing points or coordinate arrays of
 42      * the form [x, y, z].
 43      * <p>Second: the line in 3D is defined by a point (or coordinate array [x, y, z])
 44      * a direction given as array [x, y, z] and an optional range
 45      * given as array [s, e]. The default value for the range is [-Infinity, Infinity].
 46      * <p>
 47      * All numbers can also be provided as functions returning a number.
 48      *
 49      * @name Point3D
 50      * @augments JXG.Point
 51      * @constructor
 52      * @type JXG.Point
 53      * @throws {Exception} If the element cannot be constructed with the given parent
 54      * objects an exception is thrown.
 55      * @param {JXG.Point_number,JXG.Point,JXG.Line,JXG.Circle} center,radius The center must be given as a {@link JXG.Point}, see {@link JXG.providePoints}, but the radius can be given
 56      * as a number (which will create a circle with a fixed radius), another {@link JXG.Point}, a {@link JXG.Line} (the distance of start and end point of the
 57      * line will determine the radius), or another {@link JXG.Circle}.
 58      *
 59      */
 60      ThreeD.createPoint = function (board, parents, attributes) {
 61         var view = parents[0],
 62             attr, update2D, D3,
 63             i, c2d,
 64             el;
 65 
 66         attr = Type.copyAttributes(attributes, board.options, 'point3d');
 67 
 68         D3 = {
 69             elType: 'point3d',
 70             coords: [1, 0, 0, 0],
 71             X: function () { return this.coords[1]; },
 72             Y: function () { return this.coords[2]; },
 73             Z: function () { return this.coords[3]; }
 74         };
 75 
 76         // If the last element of partents is a 3D object, the point is a glider
 77         // on that element.
 78         if (parents.length > 2 && Type.exists(parents[parents.length - 1].D3)) {
 79             D3.slide = parents.pop();
 80         } else {
 81             D3.slide = null;
 82         }
 83 
 84         if (parents.length === 2) {
 85             D3.F = parents[1]; // (Array [x, y, z] | function) returning [x, y, z]
 86             D3.coords = [1].concat(Type.evaluate(D3.F));
 87         } else if (parents.length === 4) {
 88             D3.F = parents.slice(1); // 3 numbers | functions
 89             for (i = 0; i < 3; i++) {
 90                 D3.coords[i + 1] = Type.evaluate(D3.F[i]);
 91             }
 92         } else {
 93             // Throw error
 94         }
 95 
 96         /**
 97          * Update the 4D coords array
 98          * @returns Object
 99          */
100         D3.updateCoords = function () {
101             var res, i;
102             if (Type.isFunction(this.F)) {
103                 res = Type.evaluate(this.F);
104                 this.coords = [1, res[0], res[1], res[2]];
105             } else {
106                 this.coords[0] = 1;
107                 for (i = 0; i < 3; i++) {
108                     if (Type.isFunction(this.F[i])) {
109                         this.coords[i + 1] = Type.evaluate(this.F[i]);
110                     }
111                 }
112             }
113             return this;
114         };
115         D3.updateCoords();
116 
117         c2d = view.project3DTo2D(D3.coords);
118         el = board.create('point', c2d, attr);
119         el.D3 = D3;
120         el.D3.c2d = el.coords.usrCoords.slice(); // Copy of the coordinates to detect dragging
121         update2D = el.update;
122 
123         if (el.D3.slide) {
124             el._minFunc = function (n, m, x, con) {
125                 var surface = el.D3.slide.D3,
126                     c3d = [1, surface.X(x[0], x[1]), surface.Y(x[0], x[1]), surface.Z(x[0], x[1])],
127                     c2d = view.project3DTo2D(c3d);
128 
129                 con[0] = el.X() - c2d[1];
130                 con[1] = el.Y() - c2d[2];
131 
132                 return con[0] * con[0] + con[1] * con[1];
133             };
134 
135             el.projectCoords2Surface = function () {
136                 var n = 2,		// # of variables
137                     m = 2, 		// number of constraints
138                     x = [0, 0],
139                     // Various Cobyla constants, see Cobyla docs in Cobyja.js
140                     rhobeg = 5.0,
141                     rhoend = 1.0e-6,
142                     iprint = 0,
143                     maxfun = 200,
144                     surface = this.D3.slide.D3,
145                     r, c3d, c2d;
146 
147                 if (Type.exists(this.D3.params)) {
148                     x = this.D3.params.slice();
149                 }
150                 r = Mat.Nlp.FindMinimum(this._minFunc, n, m, x, rhobeg, rhoend, iprint, maxfun);
151 
152                 c3d = [1, surface.X(x[0], x[1]), surface.Y(x[0], x[1]), surface.Z(x[0], x[1])];
153                 c2d = view.project3DTo2D(c3d);
154                 this.D3.params = x;
155                 this.D3.coords = c3d;
156                 this.coords.setCoordinates(Const.COORDS_BY_USER, c2d);
157                 this.D3.c2d = c2d;
158             };
159         }
160 
161         el.update = function (drag) {
162             var c3d, foot;
163             if (!this.needsUpdate) {
164                 return this;
165             }
166 
167             // Update is called in from two methods:
168             // Once in setToPositionDirectly and
169             // once in the subsequent board.update
170             if (this.draggable() &&
171                 Geometry.distance(this.D3.c2d, this.coords.usrCoords) !== 0) {
172 
173                 if (this.D3.slide) {
174                     this.projectCoords2Surface();
175                 } else {
176                     // Drag the point in its xy plane
177                     foot = [1, 0, 0, this.D3.coords[3]];
178                     c3d = view.project2DTo3DPlane(el, [1, 0, 0, 1], foot);
179                     if (c3d[0] !== 0) {
180                         this.D3.coords = view.project3DToCube(c3d);
181                     }
182                 }
183             } else {
184                 this.D3.updateCoords();
185                 // Update 2D point from its 3D view
186                 el.coords.setCoordinates(Const.COORDS_BY_USER,
187                     view.project3DTo2D([1, this.D3.X(), this.D3.Y(), this.D3.Z()])
188                 );
189             }
190             this.D3.c2d = el.coords.usrCoords.slice();
191 
192             update2D.apply(this, [drag]);
193             return this;
194         };
195 
196         return el;
197     };
198     JXG.registerElement('point3d', ThreeD.createPoint);
199 
200 });