1 /*
  2     Copyright 2008-2024
  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 /**
 32  * Create axes and rear and front walls of the
 33  * view3d bounding box bbox3D.
 34  */
 35 import JXG from "../jxg.js";
 36 import Type from "../utils/type.js";
 37 
 38 /**
 39  * @class This element creates the axis and plane elements of a 3D view.
 40  * @pseudo
 41  * @description This element "axes3d" is used to create
 42  *  <ul>
 43  *   <li> 3D coordinate axes (either "axesPosition:'border'" or "axesPosition:'center'")
 44  *   <li> A point3d "O" (origin) if "axesPosition:'center'"
 45  *   <li> Rear and front planes in all three directions of the view3d element.
 46  *   <li> Coordinate axes on the rear and front planes
 47  *  </ul>
 48  *
 49  * @name Axes3D
 50  * @constructor
 51  * @type Object
 52  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
 53  *
 54  */
 55 JXG.createAxes3D = function (board, parents, attributes) {
 56     var view = parents[0],
 57     directions = ["x", "y", "z"],
 58     suffixAxis = "Axis",
 59     sides = ["Rear", "Front"],
 60     rear = [0, 0, 0],           // x, y, z
 61     front = [0, 0, 0],          // x, y, z
 62     i, j, k, i1, i2, attr, pos,
 63     dir, dir1, len,
 64     from, to, vec1, vec2,
 65     range1, range2,
 66     na, na_parent,
 67     ticks_attr,
 68     axes = {};
 69 
 70     if (Type.exists(view.bbox3D)) {
 71         for (i = 0; i < directions.length; i++) {
 72             rear[i] = view.bbox3D[i][0];
 73             front[i] = view.bbox3D[i][1];
 74         }
 75     } else {
 76         for (i = 0; i < directions.length; i++) {
 77             rear[i] = parents[1][i];
 78             front[i] = parents[2][1];
 79         }
 80     }
 81 
 82     // Main 3D axes
 83     attr = Type.copyAttributes(attributes, board.options, "axes3d");
 84     pos = attr.axesposition;
 85 
 86     for (i = 0; i < directions.length; i++) {
 87         // Run through ['x', 'y', 'z']
 88         dir = directions[i];
 89         na = dir + suffixAxis;
 90 
 91         if (pos === "center") {
 92             // Axes centered
 93             from = [0, 0, 0];
 94             to = [0, 0, 0];
 95             to[i] = front[i];
 96             axes[na] = view.create("axis3d", [from, to], attr[na.toLowerCase()]);
 97             axes[na].view = view;
 98         } else if (pos === 'border') {
 99             // Axes bordered
100             na += "Border";
101             from = rear.slice();
102             to = front.slice();
103             if (dir === 'z') {
104                 from[1] = front[1];
105                 to[0] = rear[0];
106             } else if (dir === 'x') {
107                 from = [rear[0], front[1], rear[2]];
108                 to = [front[0], front[1], rear[2]];
109             } else {
110                 from = [front[0], rear[1], rear[2]];
111                 to = [front[0], front[1], rear[2]];
112             }
113             to[i] = front[i];
114             // attr[na.toLowerCase()].lastArrow = false;
115             axes[na] = view.create("axis3d", [from, to], attr[na.toLowerCase()]);
116             axes[na].view = view;
117 
118             ticks_attr = attr[na.toLowerCase()].ticks3d;
119             len = front[i] - rear[i];
120             if (dir === 'x') {
121                 axes[na + "Ticks"] = view.create("ticks3d", [from, [1, 0, 0], len, [0, 1, 0]], ticks_attr);
122             } else if (dir === 'y') {
123                 axes[na + "Ticks"] = view.create("ticks3d", [from, [0, 1, 0], len, [1, 0, 0]], ticks_attr);
124             } else {
125                 axes[na + "Ticks"] = view.create("ticks3d", [from, [0, 0, 1], len, [0, 1, 0]], ticks_attr);
126             }
127             axes[na + "Ticks"].view = view;
128         }
129     }
130 
131     if (pos === 'center') {
132         // Origin (2D point)
133         axes.O = view.create(
134             "intersection",
135             [axes[directions[0] + suffixAxis], axes[directions[1] + suffixAxis]],
136             {
137                 name: "",
138                 visible: false,
139                 withLabel: false
140             }
141         );
142         axes.O.view = view;
143     } else {
144         axes.O = null;
145     }
146 
147     // Front and rear planes
148     for (i = 0; i < directions.length; i++) {
149         // Run through ['x', 'y', 'z']
150         i1 = (i + 1) % 3;
151         i2 = (i + 2) % 3;
152 
153         dir = directions[i];
154         for (j = 0; j < sides.length; j++) {
155             // Run through ['Rear', 'Front']
156 
157             from = [0, 0, 0];
158             from[i] = j === 0 ? rear[i] : front[i];
159             vec1 = [0, 0, 0];
160             vec2 = [0, 0, 0];
161             vec1[i1] = 1;
162             vec2[i2] = 1;
163             range1 = [rear[i1], front[i1]];
164             range2 = [rear[i2], front[i2]];
165             na = dir + "Plane" + sides[j];
166             attr = Type.copyAttributes(attributes, board.options, "axes3d", na);
167             axes[na] = view.create("plane3d", [from, vec1, vec2, range1, range2], attr);
168             axes[na].elType = "axisplane3d";
169         }
170     }
171 
172     // Axes on front and rear planes
173     for (i = 0; i < directions.length; i++) {
174         // Run through ['x', 'y', 'z']
175         dir = directions[i];
176         for (j = 0; j < sides.length; j++) {
177             for (k = 1; k <= 2; k++) {
178                 i1 = (i + k) % 3;
179                 dir1 = directions[i1];
180                 na = dir + "Plane" + sides[j] + dir1.toUpperCase() + "Axis";
181                 na_parent = dir + "Plane" + sides[j];
182 
183                 from = [0, 0, 0];
184                 to = [0, 0, 0];
185                 from[i] = to[i] = j === 0 ? rear[i] : front[i];
186 
187                 from[i1] = rear[i1];
188                 to[i1] = front[i1];
189 
190                 attr = Type.copyAttributes(attributes, board.options, "axes3d", na);
191                 axes[na] = view.create("axis3d", [from, to], attr);
192                 axes[na].view = view;
193                 axes[na_parent].addChild(axes[na]);
194                 axes[na_parent].element2D.inherits.push(axes[na]); // TODO: Access of element2D is not nice
195             }
196         }
197     }
198 
199     return axes;
200 };
201 JXG.registerElement("axes3d", JXG.createAxes3D);
202 
203 /**
204  * @class This element creates a 3D axis.
205  * @pseudo
206  * @description Simple element 3d axis as used with "axesPosition:center". No ticks and no label (yet).
207  * <p>
208  * At the time being, the input arrays are NOT dynamic, i.e. can not be given as functions.
209  *
210  * @name Axis3D
211  * @augments Arrow
212  * @constructor
213  * @type Object
214  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
215  * @param {Array_Array} start,end Two arrays of length 3 for the start point and the end point of the axis.
216  *
217  */
218 JXG.createAxis3D = function (board, parents, attributes) {
219     var view = parents[0],
220         attr,
221         start = parents[1],
222         end = parents[2],
223         el_start,
224         el_end,
225         el;
226 
227     // Use 2D points to create axis
228     attr = Type.copyAttributes(attributes.point1, board.options, "axis3d", "point1");
229     el_start = view.create(
230         "point",
231         [
232             (function (xx, yy, zz) {
233                 return function () {
234                     return view.project3DTo2D(xx, yy, zz)[1];
235                 };
236             })(start[0], start[1], start[2]),
237             (function (xx, yy, zz) {
238                 return function () {
239                     return view.project3DTo2D(xx, yy, zz)[2];
240                 };
241             })(start[0], start[1], start[2])
242         ],
243         attr
244     );
245 
246     attr = Type.copyAttributes(attributes.point2, board.options, "axis3d", "point2");
247     el_end = view.create(
248         "point",
249         [
250             (function (xx, yy, zz) {
251                 return function () {
252                     return view.project3DTo2D(xx, yy, zz)[1];
253                 };
254             })(end[0], end[1], end[2]),
255             (function (xx, yy, zz) {
256                 return function () {
257                     return view.project3DTo2D(xx, yy, zz)[2];
258                 };
259             })(end[0], end[1], end[2])
260         ],
261         attr
262     );
263 
264     attr = Type.copyAttributes(attributes, board.options, "axis3d");
265     el = view.create("arrow", [el_start, el_end], attr);
266 
267     return el;
268 };
269 JXG.registerElement("axis3d", JXG.createAxis3D);
270 
271 /**
272  * @class This element creates a 3D (rectangular) mesh.
273  * @pseudo
274  * @description Create a (rectangular) mesh - i.e. grid lines - on a plane3D element.
275  * <p>
276  * At the time being, the mesh is not connected to the plane. The connecting element is simply the
277  * parameter point.
278  *
279  * @name Mesh3D
280  * @augments Curve
281  * @constructor
282  * @type Object
283  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
284  * @param {Array_Array_Array_Array_Array_Number} point,direction1,range1,direction2,range2,[stepWidth=1] point is an array of length 3
285  * determining the starting point of the grid. direction1 and direction2 are arrays of length 3 for the directions of the grid.
286  * range1 and range2 (arrays of length 2) give the respective ranges. stepWidth is the increment of the grid lines.
287  * All parameters can be supplied as functions returning an appropriate data type.
288  *
289  */
290 JXG.createMesh3D = function (board, parents, attr) {
291     var view = parents[0],
292         point = parents[1],
293         dir1 = parents[2],
294         range1 = parents[3],
295         dir2 = parents[4],
296         range2 = parents[5],
297         step = parents[6] || 1,
298         el;
299 
300     el = view.create("curve", [[], []], attr);
301 
302     el.point = point;
303     el.direction1 = dir1;
304     el.range1 = range1;
305     el.direction2 = dir2;
306     el.range2 = range2;
307     el.step = step;
308 
309     /**
310      * @ignore
311      */
312     el.updateDataArray = function () {
313         var range1 = Type.evaluate(this.range1),
314             range2 = Type.evaluate(this.range2),
315             s1 = range1[0],
316             e1 = range1[1],
317             s2 = range2[0],
318             e2 = range2[1],
319             l1, l2, res, i,
320             v1 = [0, 0, 0],
321             v2 = [0, 0, 0],
322             step = Type.evaluate(this.step),
323             q = [0, 0, 0];
324 
325         this.dataX = [];
326         this.dataY = [];
327 
328         if (Type.isFunction(this.point)) {
329             q = this.point().slice(1);
330         } else {
331             for (i = 0; i < 3; i++) {
332                 q[i] = Type.evaluate(this.point[i]);
333             }
334         }
335         for (i = 0; i < 3; i++) {
336             v1[i] = Type.evaluate(this.direction1[i]);
337             v2[i] = Type.evaluate(this.direction2[i]);
338         }
339         l1 = JXG.Math.norm(v1, 3);
340         l2 = JXG.Math.norm(v2, 3);
341         for (i = 0; i < 3; i++) {
342             v1[i] /= l1;
343             v2[i] /= l2;
344         }
345 
346         // sol = Mat.Geometry.getPlaneBounds(v1, v2, q, s1, e1);
347         // if (sol !== null) {
348         //     s1 = sol[0];
349         //     e1 = sol[1];
350         //     s2 = sol[2];
351         //     e2 = sol[3];
352         // }
353 
354         res = view.getMesh(
355             [
356                 function (u, v) {
357                     return q[0] + u * v1[0] + v * v2[0];
358                 },
359                 function (u, v) {
360                     return q[1] + u * v1[1] + v * v2[1];
361                 },
362                 function (u, v) {
363                     return q[2] + u * v1[2] + v * v2[2];
364                 }
365             ],
366             [Math.ceil(s1), Math.floor(e1), (Math.ceil(e1) - Math.floor(s1)) / step],
367             [Math.ceil(s2), Math.floor(e2), (Math.ceil(e2) - Math.floor(s2)) / step]
368         );
369         this.dataX = res[0];
370         this.dataY = res[1];
371     };
372 
373     return el;
374 };
375 
376 JXG.registerElement("mesh3d", JXG.createMesh3D);
377