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 /**
 32  * Create linear spaces of dimension at least one,
 33  * i.e. lines and planes.
 34  */
 35 define(['jxg', 'utils/type', 'math/math', 'math/geometry', '3d/view3d'
 36 ], function (JXG, Type, Mat, Geometry, ThreeD) {
 37     "use strict";
 38 
 39     /**
 40      * @class This element is used to provide a constructor for a 3D line.
 41      * @pseudo
 42      * @description There are two possibilities to create a Line3D object.
 43      * <p>
 44      * First: the line in 3D is defined by two points in 3D (Point3D).
 45      * The points can be either existing points or coordinate arrays of
 46      * the form [x, y, z].
 47      * <p>Second: the line in 3D is defined by a point (or coordinate array [x, y, z])
 48      * a direction given as array [x, y, z] and an optional range
 49      * given as array [s, e]. The default value for the range is [-Infinity, Infinity].
 50      * <p>
 51      * All numbers can also be provided as functions returning a number.
 52      *
 53      * @name Line3D
 54      * @augments JXG.Curve
 55      * @constructor
 56      * @type JXG.Curve
 57      * @throws {Exception} If the element cannot be constructed with the given parent
 58      * objects an exception is thrown.
 59      * @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
 60      * 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
 61      * line will determine the radius), or another {@link JXG.Circle}.
 62      *
 63      */
 64     ThreeD.createLine = function (board, parents, attributes) {
 65         var view = parents[0],
 66             attr, D3, point, point1, point2,
 67             el;
 68 
 69         // Range
 70         D3 = {
 71             elType: 'line3d',
 72             range: parents[3] || [-Infinity, Infinity]
 73         };
 74 
 75         // Point
 76         if (Type.isPoint(parents[1])) {
 77             point = parents[1];
 78         } else {
 79             point = view.create('point3d', parents[1], { visible: false, name: '', withLabel: false });
 80         }
 81         D3.point = point;
 82 
 83         // Direction
 84         if (Type.isPoint(parents[2]) && Type.exists(parents[2].D3)) {
 85             // Line defined by two points
 86 
 87             point1 = point;
 88             point2 = parents[2];
 89             D3.direction = function () {
 90                 return [
 91                     point2.D3.X() - point.D3.X(),
 92                     point2.D3.Y() - point.D3.Y(),
 93                     point2.D3.Z() - point.D3.Z()
 94                 ];
 95             };
 96             D3.range = [0, 1];
 97         } else {
 98             // Line defined by point, direction and range
 99 
100             // Directions are handled as arrays of length 4,
101             // i.e. with homogeneous coordinates.
102             if (Type.isFunction(parents[2])) {
103                 D3.direction = parents[2];
104             } else if (parents[2].length === 3) {
105                 D3.direction = [1].concat(parents[2]);
106             } else if (parents[2].length === 4) {
107                 D3.direction = parents[2];
108             } else {
109                 // Throw error
110             }
111 
112             // Direction given as array
113             D3.getPointCoords = function (r) {
114                 var p = [],
115                     d = [],
116                     i;
117 
118                 p.push(point.D3.X());
119                 p.push(point.D3.Y());
120                 p.push(point.D3.Z());
121 
122                 if (Type.isFunction(D3.direction)) {
123                     d = D3.direction();
124                 } else {
125                     for (i = 1; i < 4; i++) {
126                         d.push(Type.evaluate(D3.direction[i]));
127                     }
128                 }
129                 if (Math.abs(r) === Infinity) {
130                     r = view.intersectionLineCube(p, d, r);
131                 }
132                 return [
133                     p[0] + d[0] * r,
134                     p[1] + d[1] * r,
135                     p[2] + d[2] * r
136                 ];
137 
138             };
139 
140             attr = Type.copyAttributes(attributes, board.options, 'line3d', 'point1');
141             point1 = view.create('point3d', [
142                 function () {
143                     return D3.getPointCoords(Type.evaluate(D3.range[0]));
144                 }
145             ], attr);
146             attr = Type.copyAttributes(attributes, board.options, 'line3d', 'point2');
147             point2 = view.create('point3d', [
148                 function () {
149                     return D3.getPointCoords(Type.evaluate(D3.range[1]));
150                 }
151             ], attr);
152         }
153 
154         attr = Type.copyAttributes(attributes, board.options, 'line3d');
155         el = view.create('segment', [point1, point2], attr);
156         el.point1 = point1;
157         el.point2 = point2;
158         point1.addChild(el);
159         point2.addChild(el);
160         el.D3 = D3;
161 
162         return el;
163     };
164     JXG.registerElement('line3d', ThreeD.createLine);
165 
166     ThreeD.createPlane = function (board, parents, attributes) {
167         var view = parents[0],
168             attr, D3,
169             point,
170             vec1 = parents[2],
171             vec2 = parents[3],
172             el, grid, update;
173 
174         // D3: {
175         //    point,
176         //    vec1,
177         //    vec2,
178         //    poin1,
179         //    point2,
180         //    normal array of len 3
181         //    d
182         // }
183         D3 = {
184             elType: 'plane3d',
185             dir1: [],
186             dir2: [],
187             range1: parents[4],
188             range2: parents[5],
189             vec1: vec1,
190             vec2: vec2
191         };
192 
193         if (Type.isPoint(parents[1])) {
194             point = parents[1];
195         } else {
196             point = view.create('point3d', parents[1], { visible: false, name: '', withLabel: false });
197         }
198         D3.point = point;
199 
200         D3.updateNormal = function () {
201             var i;
202             for (i = 0; i < 3; i++) {
203                 D3.dir1[i] = Type.evaluate(D3.vec1[i]);
204                 D3.dir2[i] = Type.evaluate(D3.vec2[i]);
205             }
206             D3.normal = Mat.crossProduct(D3.dir1, D3.dir2);
207             D3.d = Mat.innerProduct(D3.point.D3.coords.slice(1), D3.normal, 3);
208         };
209         D3.updateNormal();
210 
211         attr = Type.copyAttributes(attributes, board.options, 'plane3d');
212         el = board.create('curve', [[], []], attr);
213         el.D3 = D3;
214 
215         el.updateDataArray = function () {
216             var s1, e1, s2, e2,
217                 c2d, l1, l2,
218                 planes = ['xPlaneRear', 'yPlaneRear', 'zPlaneRear'],
219                 points = [],
220                 v1 = [0, 0, 0],
221                 v2 = [0, 0, 0],
222                 q = [0, 0, 0],
223                 p = [0, 0, 0], d, i, j, a, b, first, pos, pos_akt;
224 
225             this.dataX = [];
226             this.dataY = [];
227 
228             this.D3.updateNormal();
229 
230             // Infinite plane
231             if (this.D3.elType !== 'axisplane3d' && view.defaultAxes &&
232                 (!D3.range1 || !D3.range2)
233                 ) {
234 
235                 // Start with the rear plane.
236                 // Determine the intersections with the view bbox3d
237                 // For each face of the bbox3d we determine two points
238                 // which are the ends of the intersection line.
239                 // We start with the three rear planes.
240                 for (j = 0; j < planes.length; j++) {
241                     p = view.intersectionPlanePlane(this, view.defaultAxes[planes[j]]);
242 
243                     if (p[0].length === 3 && p[1].length === 3) {
244                         // This test is necessary to filter out intersection lines which are
245                         // identical to intersections of axis planes (they would occur twice).
246                         for (i = 0; i < points.length; i++) {
247                             if ((Geometry.distance(p[0], points[i][0], 3) < Mat.eps && Geometry.distance(p[1], points[i][1], 3) < Mat.eps) ||
248                                 (Geometry.distance(p[0], points[i][1], 3) < Mat.eps && Geometry.distance(p[1], points[i][0], 3) < Mat.eps)) {
249                                 break;
250                             }
251                         }
252                         if (i === points.length) {
253                             points.push(p.slice());
254                         }
255                     }
256 
257                     // Point on the front plane of the bbox3d
258                     p = [0, 0, 0];
259                     p[j] = view.D3.bbox3d[j][1];
260 
261                     // d is the rhs of the Hesse normal form of the front plane.
262                     d = Mat.innerProduct(p, view.defaultAxes[planes[j]].D3.normal, 3);
263                     p = view.intersectionPlanePlane(this, view.defaultAxes[planes[j]], d);
264 
265                     if (p[0].length === 3 && p[1].length === 3) {
266                         // Do the same test as above
267                         for (i = 0; i < points.length; i++) {
268                             if ((Geometry.distance(p[0], points[i][0], 3) < Mat.eps && Geometry.distance(p[1], points[i][1], 3) < Mat.eps) ||
269                                 (Geometry.distance(p[0], points[i][1], 3) < Mat.eps && Geometry.distance(p[1], points[i][0], 3) < Mat.eps)) {
270                                 break;
271                             }
272                         }
273                         if (i === points.length) {
274                             points.push(p.slice());
275                         }
276                     }
277                 }
278 
279                 // Concatenate the intersection points to a polygon.
280                 // If all wents well, each intersection should appear
281                 // twice in the list.
282                 first = 0;
283                 pos = first;
284                 i = 0;
285                 do {
286                     p = points[pos][i];
287                     if (p.length === 3) {
288                         c2d = view.project3DTo2D(p);
289                         this.dataX.push(c2d[1]);
290                         this.dataY.push(c2d[2]);
291                     }
292                     i = (i + 1) % 2;
293                     p = points[pos][i];
294 
295                     pos_akt = pos;
296                     for (j = 0; j < points.length; j++) {
297                         if (j !== pos && Geometry.distance(p, points[j][0]) < Mat.eps) {
298                             pos = j;
299                             i = 0;
300                             break;
301                         }
302                         if (j !== pos && Geometry.distance(p, points[j][1]) < Mat.eps) {
303                             pos = j;
304                             i = 1;
305                             break;
306                         }
307                     }
308                     if (pos === pos_akt) {
309                         console.log("Error: update plane3d: did not find next", pos);
310                         break;
311                     }
312                 } while (pos !== first);
313                 c2d = view.project3DTo2D(points[first][0]);
314                 this.dataX.push(c2d[1]);
315                 this.dataY.push(c2d[2]);
316 
317             } else {
318                 // 3D bounded flat
319                 s1 = Type.evaluate(this.D3.range1[0]);
320                 e1 = Type.evaluate(this.D3.range1[1]);
321                 s2 = Type.evaluate(this.D3.range2[0]);
322                 e2 = Type.evaluate(this.D3.range2[1]);
323 
324                 q = this.D3.point.D3.coords.slice(1);
325 
326                 v1 = this.D3.dir1.slice();
327                 v2 = this.D3.dir2.slice();
328                 l1 = Mat.norm(v1, 3);
329                 l2 = Mat.norm(v2, 3);
330                 for (i = 0; i < 3; i++) {
331                     v1[i] /= l1;
332                     v2[i] /= l2;
333                 }
334 
335                 for (j = 0; j < 4; j++) {
336                     switch (j) {
337                         case 0: a = s1; b = s2; break;
338                         case 1: a = e1; b = s2; break;
339                         case 2: a = e1; b = e2; break;
340                         case 3: a = s1; b = e2;
341                     }
342                     for (i = 0; i < 3; i++) {
343                         p[i] = q[i] + a * v1[i] + b * v2[i];
344                     }
345                     c2d = view.project3DTo2D(p);
346                     this.dataX.push(c2d[1]);
347                     this.dataY.push(c2d[2]);
348                 }
349                 // Close the curve
350                 this.dataX.push(this.dataX[0]);
351                 this.dataY.push(this.dataY[0]);
352             }
353         };
354 
355         attr = Type.copyAttributes(attributes.mesh3d, board.options, 'mesh3d');
356 
357         if (D3.range1 && D3.range2) {
358             grid = view.create('mesh3d', [point.D3.coords.slice(1), vec1, D3.range1, vec2, D3.range2], attr);
359             el.grid = grid;
360             el.inherits.push(grid);
361         }
362 
363         // update = el.update;
364         // el.update = function () {
365         //     if (el.needsUpdate) {
366         //         update.apply(el);
367         //     }
368         //     return this;
369         // };
370 
371         return el;
372     };
373     JXG.registerElement('plane3d', ThreeD.createPlane);
374 
375 });