1 /*
  2     Copyright 2008-2026
  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 A container element that creates the axes and rear and front planes 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][i];
 79         }
 80     }
 81 
 82     // Main 3D axes
 83     attr = Type.copyAttributes(attributes, board.options, 'axes3d');
 84 
 85     // Position of the main axes can not be changed during run time
 86     pos = attr.axesposition;
 87 
 88     for (i = 0; i < directions.length; i++) {
 89         // Run through ['x', 'y', 'z']
 90         dir = directions[i];
 91         na = dir + suffixAxis;
 92 
 93         if (pos === 'center') {
 94             // Axes centered
 95             from = [0, 0, 0];
 96             to = [0, 0, 0];
 97             to[i] = front[i];
 98             axes[na] = view.create('axis3d', [from, to], attr[na.toLowerCase()]);
 99             axes[na].view = view;
100         } else if (pos === 'border') {
101             // Axes bordered
102             na += 'Border';
103             from = rear.slice();
104             to = front.slice();
105             if (dir === 'z') {
106                 from[1] = front[1];
107                 to[0] = rear[0];
108             } else if (dir === 'x') {
109                 from = [rear[0], front[1], rear[2]];
110                 to = [front[0], front[1], rear[2]];
111             } else {
112                 from = [front[0], rear[1], rear[2]];
113                 to = [front[0], front[1], rear[2]];
114             }
115             to[i] = front[i];
116             // attr[na.toLowerCase()].lastArrow = false;
117             axes[na] = view.create('axis3d', [from, to], attr[na.toLowerCase()]);
118             axes[na].view = view;
119 
120             ticks_attr = attr[na.toLowerCase()].ticks3d;
121             ticks_attr.element3d = true;  // Needed to avoid update during change of view
122             len = front[i] - rear[i];
123             if (dir === 'x') {
124                 axes[na + "Ticks"] = view.create("ticks3d", [from, [1, 0, 0], len, [0, 1, 0]], ticks_attr);
125             } else if (dir === 'y') {
126                 axes[na + "Ticks"] = view.create("ticks3d", [from, [0, 1, 0], len, [1, 0, 0]], ticks_attr);
127             } else {
128                 axes[na + "Ticks"] = view.create("ticks3d", [from, [0, 0, 1], len, [0, 1, 0]], ticks_attr);
129             }
130             axes[na + "Ticks"].view = view;
131         }
132     }
133 
134     if (pos === 'center') {
135         // Origin (2D point)
136         axes.O = view.create(
137             "intersection",
138             [axes[directions[0] + suffixAxis], axes[directions[1] + suffixAxis]],
139             {
140                 name: "",
141                 visible: false,
142                 withLabel: false
143             }
144         );
145         axes.O.view = view;
146     } else {
147         axes.O = null;
148     }
149 
150     // Front and rear planes
151     for (i = 0; i < directions.length; i++) {
152         // Run through ['x', 'y', 'z']
153         i1 = (i + 1) % 3;
154         i2 = (i + 2) % 3;
155 
156         dir = directions[i];
157         for (j = 0; j < sides.length; j++) {
158             // Run through ['Rear', 'Front']
159             // attr = Type.copyAttributes(attributes, board.options, 'axes3d');
160 
161             na = dir + "Plane" + sides[j];
162 
163             from = [0, 0, 0];
164             from[i] = j === 0 ? rear[i] : front[i];
165             vec1 = [0, 0, 0];
166             vec2 = [0, 0, 0];
167             vec1[i1] = 1;
168             vec2[i2] = 1;
169             range1 = [rear[i1], front[i1]];
170             range2 = [rear[i2], front[i2]];
171 
172             attr = Type.copyAttributes(attributes, board.options, "axes3d", na);
173             axes[na] = view.create('plane3d', [from, vec1, vec2, range1, range2], attr);
174             axes[na].elType = 'axisplane3d';
175         }
176     }
177 
178     // Axes on front and rear planes
179     for (i = 0; i < directions.length; i++) {
180         // Run through ['x', 'y', 'z']
181         dir = directions[i];
182         for (j = 0; j < sides.length; j++) {
183             for (k = 1; k <= 2; k++) {
184                 i1 = (i + k) % 3;
185                 dir1 = directions[i1];
186                 na = dir + "Plane" + sides[j] + dir1.toUpperCase() + 'Axis';
187                 na_parent = dir + "Plane" + sides[j];
188 
189                 from = [0, 0, 0];
190                 to = [0, 0, 0];
191                 from[i] = to[i] = j === 0 ? rear[i] : front[i];
192 
193                 from[i1] = rear[i1];
194                 to[i1] = front[i1];
195 
196                 attr = Type.copyAttributes(attributes, board.options, "axes3d", na);
197                 axes[na] = view.create("axis3d", [from, to], attr);
198                 axes[na].view = view;
199                 axes[na_parent].addChild(axes[na]);
200                 //if (Type.exists(axes[na_parent].element2D)) {
201                     // TODO: Access of element2D is not nice
202                     axes[na_parent].element2D.inherits.push(axes[na]);
203                 //}
204 
205             }
206         }
207     }
208 
209     return axes;
210 };
211 JXG.registerElement("axes3d", JXG.createAxes3D);
212 
213 /**
214  * @class A 3D axis element is a line together with optional ticks and labels.
215  * @pseudo
216  * @description Simple element 3d axis as used with "axesPosition:center". No ticks and no label (yet).
217  * <p>
218  * At the time being, the input arrays are NOT dynamic, i.e. can not be given as functions.
219  *
220  * @name Axis3D
221  * @augments Arrow
222  * @constructor
223  * @type Object
224  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
225  * @param {Array_Array} start,end Two arrays of length 3 for the start point and the end point of the axis.
226  *
227  */
228 JXG.createAxis3D = function (board, parents, attributes) {
229     var view = parents[0],
230         attr = Type.copyAttributes(attributes, board.options, 'axis3d');
231 
232     return view.create('line3d', parents.slice(1), attr);
233 };
234 JXG.registerElement('axis3d', JXG.createAxis3D);
235 
236 // JXG.createAxis3DOld = function (board, parents, attributes) {
237 //     var view = parents[0],
238 //         attr,
239 //         start = parents[1],
240 //         end = parents[2],
241 //         el_start,
242 //         el_end,
243 //         el;
244 //
245 //     // Use 2D points to create axis
246 //     attr = Type.copyAttributes(attributes.point1, board.options, "axis3d", 'point1');
247 //     attr.element3d = true;  // Needed to avoid update during change of view
248 //     el_start = view.create(
249 //         "point",
250 //         [
251 //             (function (xx, yy, zz) {
252 //                 return function () {
253 //                     return view.project3DTo2D(xx, yy, zz)[1];
254 //                 };
255 //             })(start[0], start[1], start[2]),
256 //             (function (xx, yy, zz) {
257 //                 return function () {
258 //                     return view.project3DTo2D(xx, yy, zz)[2];
259 //                 };
260 //             })(start[0], start[1], start[2])
261 //         ],
262 //         attr
263 //     );
264 //
265 //     attr = Type.copyAttributes(attributes.point2, board.options, "axis3d", 'point2');
266 //     attr.element3d = true;  // Needed to avoid update during change of view
267 //     el_end = view.create(
268 //         "point",
269 //         [
270 //             (function (xx, yy, zz) {
271 //                 return function () {
272 //                     return view.project3DTo2D(xx, yy, zz)[1];
273 //                 };
274 //             })(end[0], end[1], end[2]),
275 //             (function (xx, yy, zz) {
276 //                 return function () {
277 //                     return view.project3DTo2D(xx, yy, zz)[2];
278 //                 };
279 //             })(end[0], end[1], end[2])
280 //         ],
281 //         attr
282 //     );
283 //
284 //     attr = Type.copyAttributes(attributes, board.options, 'axis3d');
285 //     attr.element3d = true;  // Needed to avoid update during change of view
286 //     el = view.create("arrow", [el_start, el_end], attr);
287 //
288 //     return el;
289 // };
290 
291 /**
292  * @class Display a rectangular mesh on a 3D plane element.
293  * @pseudo
294  * @description Create a (rectangular) mesh - i.e. grid lines - on a plane3D element.
295  * <p>
296  * At the time being, the mesh is not connected to the plane. The connecting element is simply the
297  * parameter point.
298  *
299  * @name Mesh3D
300  * @augments Curve
301  * @constructor
302  * @type Object
303  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
304  * @param {Array_Array_Array_Array_Array} point,direction1,direction2,range1,range2 point is an array of length 3
305  * determining the starting point of the grid. direction1 and direction2 are arrays of length 3 for the directions of the grid.
306  * range1 and range2 (arrays of length 2) give the respective ranges.
307  * All parameters can be supplied as functions returning an appropriate data type.
308  *
309  */
310 JXG.createMesh3D = function (board, parents, attributes) {
311     var view = parents[0],
312         attr, el;
313 
314     attr = Type.copyAttributes(attributes, board.options, 'mesh3d');
315     attr.element3d = true;  // Needed to avoid update during change of view
316     el = view.create("curve", [[], []], attr);
317 
318     el.point = parents[1];
319     el.direction1 = parents[2];
320     el.direction2 = parents[3];
321     el.range1 = parents[4];
322     el.range2 = parents[5];
323 
324     /**
325      * @ignore
326      */
327     el.updateDataArray = function () {
328         var range1 = Type.evaluate(this.range1),
329             range2 = Type.evaluate(this.range2),
330             s1 = range1[0],
331             e1 = range1[1],
332             s2 = range2[0],
333             e2 = range2[1],
334             l1, l2, res, i,
335             v1 = [0, 0, 0],
336             v2 = [0, 0, 0],
337             step_u = this.evalVisProp('stepwidthu'),
338             step_v = this.evalVisProp('stepwidthv'),
339             q = [0, 0, 0];
340 
341         this.dataX = [];
342         this.dataY = [];
343 
344         if (Type.isFunction(this.point)) {
345             q = this.point();
346         } else {
347             if (Type.isPoint3D(this.point)) {
348                 q = this.point.coords;
349             } else {
350                 for (i = 0; i < this.point.length; i++) {
351                     q[i] = Type.evaluate(this.point[i]);
352                 }
353             }
354         }
355         if (Type.isFunction(this.direction1)) {
356             v1 = Type.evaluate(this.direction1);
357         } else {
358             for (i = 0; i < this.direction1.length; i++) {
359                 v1[i] = Type.evaluate(this.direction1[i]);
360             }
361         }
362         if (Type.isFunction(this.direction2)) {
363             v2 = Type.evaluate(this.direction2);
364         } else {
365             for (i = 0; i < this.direction2.length; i++) {
366                 v2[i] = Type.evaluate(this.direction2[i]);
367             }
368         }
369         if (q.length === 4) {
370             q = q.slice(1);
371         }
372         if (v1.length === 4) {
373             v1 = v1.slice(1);
374         }
375         if (v2.length === 4) {
376             v2 = v2.slice(1);
377         }
378 
379         l1 = JXG.Math.norm(v1, 3);
380         l2 = JXG.Math.norm(v2, 3);
381         for (i = 0; i < 3; i++) {
382             v1[i] /= l1;
383             v2[i] /= l2;
384         }
385 
386         // sol = Mat.Geometry.getPlaneBounds(v1, v2, q, s1, e1);
387         // if (sol !== null) {
388         //     s1 = sol[0];
389         //     e1 = sol[1];
390         //     s2 = sol[2];
391         //     e2 = sol[3];
392         // }
393 
394         res = view.getMesh(
395             [
396                 function (u, v) {
397                     return q[0] + u * v1[0] + v * v2[0];
398                 },
399                 function (u, v) {
400                     return q[1] + u * v1[1] + v * v2[1];
401                 },
402                 function (u, v) {
403                     return q[2] + u * v1[2] + v * v2[2];
404                 }
405             ],
406             [Math.ceil(s1), Math.floor(e1), (Math.ceil(e1) - Math.floor(s1)) / step_u],
407             [Math.ceil(s2), Math.floor(e2), (Math.ceil(e2) - Math.floor(s2)) / step_v]
408         );
409         this.dataX = res[0];
410         this.dataY = res[1];
411     };
412 
413     return el;
414 };
415 
416 JXG.registerElement("mesh3d", JXG.createMesh3D);
417