1 /*
  2     Copyright 2008-2025
  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 import JXG from "../jxg.js";
 32 import Const from "../base/constants.js";
 33 import Mat from "../math/math.js";
 34 import Geometry from "../math/geometry.js";
 35 import Type from "../utils/type.js";
 36 
 37 /**
 38  * Constructor for 3D surfaces.
 39  * @class Creates a new 3D surface object. Do not use this constructor to create a 3D surface. Use {@link JXG.View3D#create} with type {@link Surface3D} instead.
 40  *
 41  * @augments JXG.GeometryElement3D
 42  * @augments JXG.GeometryElement
 43  * @param {View3D} view
 44  * @param {Function} F
 45  * @param {Function} X
 46  * @param {Function} Y
 47  * @param {Function} Z
 48  * @param {Array} range_u
 49  * @param {Array} range_v
 50  * @param {Object} attributes
 51  * @see JXG.Board#generateName
 52  */
 53 JXG.Surface3D = function (view, F, X, Y, Z, range_u, range_v, attributes) {
 54     this.constructor(
 55         view.board,
 56         attributes,
 57         Const.OBJECT_TYPE_SURFACE3D,
 58         Const.OBJECT_CLASS_3D
 59     );
 60     this.constructor3D(view, "surface3d");
 61 
 62     this.board.finalizeAdding(this);
 63 
 64     /**
 65      * Internal function defining the surface
 66      * without applying any transformations.
 67      *
 68      * @function
 69      * @param {Number} u
 70      * @param {Number} v
 71      * @returns Array [x, y, z] of length 3
 72      * @private
 73      */
 74     this._F = F;
 75 
 76     /**
 77      * Internal function which maps (u, v) to x; i.e. it defines the x-coordinate of the surface
 78      * without applying any transformations.
 79      * @function
 80      * @param {Number} u
 81      * @param {Number} v
 82      * @returns Number
 83      * @private
 84      */
 85     this._X = X;
 86 
 87     /**
 88      * Internal function which maps (u, v) to y; i.e. it defines the y-coordinate of the surface
 89      * without applying any transformations.
 90      * @function
 91      * @param {Number} u
 92      * @param {Number} v
 93      * @returns Number
 94      * @private
 95      */
 96     this._Y = Y;
 97 
 98     /**
 99      * Internal function which maps (u, v) to z; i.e. it defines the z-coordinate of the surface
100      * without applying any transformations.
101      * @function
102      * @param {Number} u
103      * @param {Number} v
104      * @returns Number
105      * @private
106      */
107     this._Z = Z;
108 
109     if (this._F !== null) {
110         this._X = function (u, v) {
111             return this._F(u, v)[0];
112         };
113         this._Y = function (u, v) {
114             return this._F(u, v)[1];
115         };
116         this._Z = function (u, v) {
117             return this._F(u, v)[2];
118         };
119     } else {
120         if (this._X !== null) {
121             this._F = function(u, v) {
122                 return [this._X(u, v), this._Y(u, v), this._Z(u, v)];
123             };
124         }
125     }
126 
127     this.range_u = range_u;
128     this.range_v = range_v;
129 
130     this.dataX = null;
131     this.dataY = null;
132     this.dataZ = null;
133     this.points = [];
134 
135     this.methodMap = Type.deepCopy(this.methodMap, {
136         // TODO
137     });
138 };
139 JXG.Surface3D.prototype = new JXG.GeometryElement();
140 Type.copyPrototypeMethods(JXG.Surface3D, JXG.GeometryElement3D, "constructor3D");
141 
142 JXG.extend(
143     JXG.Surface3D.prototype,
144     /** @lends JXG.Surface3D.prototype */ {
145 
146         updateWireframe: function () {
147             var steps_u, steps_v,
148                 i_u, i_v,
149                 r_u, r_v,
150                 s_u, s_v,
151                 e_u, e_v,
152                 delta_u, delta_v,
153                 u, v,
154                 c3d = [1, 0, 0, 0];
155 
156             this.points = [];
157 
158             steps_u = this.evalVisProp('stepsu');
159             steps_v = this.evalVisProp('stepsv');
160             r_u = Type.evaluate(this.range_u);
161             r_v = Type.evaluate(this.range_v);
162             s_u = Type.evaluate(r_u[0]);
163             s_v = Type.evaluate(r_v[0]);
164             e_u = Type.evaluate(r_u[1]);
165             e_v = Type.evaluate(r_v[1]);
166             delta_u = (e_u - s_u) / (steps_u);
167             delta_v = (e_v - s_v) / (steps_v);
168 
169             for (i_u = 0, u = s_u; i_u <= steps_u; i_u++, u += delta_u) {
170                 this.points.push([]);
171                 for (i_v = 0, v = s_v; i_v <= steps_v; i_v++, v += delta_v) {
172                     c3d = this.F(u, v);
173                     c3d.unshift(1);
174                     this.points[i_u].push(c3d);
175                 }
176             }
177 
178             return this;
179         },
180 
181         updateCoords: function () {
182             if (this._F !== null) {
183                 this.updateWireframe();
184             } else {
185                 this.updateTransform();
186             }
187             return this;
188         },
189 
190         /**
191          * Generic function which evaluates the function term of the surface
192          * and applies its transformations.
193          * @param {Number} u
194          * @param {Number} v
195          * @returns
196          */
197         evalF: function(u, v) {
198             var t, i,
199                 c3d = [0, 0, 0, 0];
200 
201             if (this.transformations.length === 0 || !Type.exists(this.baseElement)) {
202                 c3d = this._F(u, v);
203                 return c3d;
204             }
205 
206             t = this.transformations;
207             for (i = 0; i < t.length; i++) {
208                 t[i].update();
209             }
210 
211             if (this === this.baseElement) {
212                 c3d = this._F(u, v);
213             } else {
214                 c3d = this.baseElement.evalF(u, v);
215             }
216             c3d.unshift(1);
217             c3d = Mat.matVecMult(t[0].matrix, c3d);
218             for (i = 1; i < t.length; i++) {
219                 c3d = Mat.matVecMult(t[i].matrix, c3d);
220             }
221 
222             return c3d.slice(1);
223         },
224 
225         /**
226          * Function defining the surface plus applying transformations.
227          * @param {Number} u
228          * @param {Number} v
229         * @returns Array [x, y, z] of length 3
230          */
231         F: function(u, v) {
232             return this.evalF(u, v);
233         },
234 
235         /**
236         * Function which maps (u, v) to z; i.e. it defines the x-coordinate of the surface
237         * plus applying transformations.
238         * @param {Number} u
239         * @param {Number} v
240         * @returns Number
241         */
242         X: function(u, v) {
243             return this.evalF(u, v)[0];
244         },
245 
246         /**
247         * Function which maps (u, v) to y; i.e. it defines the y-coordinate of the surface
248         * plus applying transformations.
249         * @param {Number} u
250         * @param {Number} v
251         * @returns Number
252         */
253         Y: function(u, v) {
254             return this.evalF(u, v)[1];
255         },
256 
257         /**
258         * Function which maps (u, v) to z; i.e. it defines the z-coordinate of the surface
259         * plus applying transformations.
260         * @param {Number} u
261         * @param {Number} v
262         * @returns Number
263         */
264         Z: function(u, v) {
265             return this.evalF(u, v)[2];
266         },
267 
268         /**
269          * @class
270          * @ignore
271          */
272         updateDataArray2D: function () {
273             var i, j, len_u, len_v,
274                 dataX = [],
275                 dataY = [],
276                 c2d;
277 
278             len_u = this.points.length;
279             if (len_u !== 0) {
280                 len_v = this.points[0].length;
281 
282                 for (i = 0; i < len_u; i++) {
283                     for (j = 0; j < len_v; j++) {
284                         c2d = this.view.project3DTo2D(this.points[i][j]);
285                         dataX.push(c2d[1]);
286                         dataY.push(c2d[2]);
287                     }
288                     dataX.push(NaN);
289                     dataY.push(NaN);
290                 }
291 
292                 for (j = 0; j < len_v; j++) {
293                     for (i = 0; i < len_u; i++) {
294                         c2d = this.view.project3DTo2D(this.points[i][j]);
295                         dataX.push(c2d[1]);
296                         dataY.push(c2d[2]);
297                     }
298                     dataX.push(NaN);
299                     dataY.push(NaN);
300                 }
301             }
302 
303             return {X: dataX, Y: dataY};
304         },
305 
306         addTransform: function (el, transform) {
307             this.addTransformGeneric(el, transform);
308             return this;
309         },
310 
311         updateTransform: function () {
312             var t, c, i, j, k,
313                 len_u, len_v;
314 
315             if (this.transformations.length === 0 || this.baseElement === null ||
316                 Type.exists(this._F) // Transformations have only to be applied here
317                                      // if the curve is defined by arrays
318             ) {
319                 return this;
320             }
321 
322             t = this.transformations;
323             for (i = 0; i < t.length; i++) {
324                 t[i].update();
325             }
326             if (this !== this.baseElement) {
327                 this.points = [];
328             }
329 
330             len_u = this.baseElement.points.length;
331             if (len_u > 0) {
332                 len_v = this.baseElement.points[0].length;
333                 for (i = 0; i < len_u; i++) {
334                     if (this !== this.baseElement) {
335                         this.points.push([]);
336                     }
337                     for (j = 0; j < len_v; j++) {
338                         if (this === this.baseElement) {
339                             c = this.points[i][j];
340                         } else {
341                             c = this.baseElement.points[i][j];
342                         }
343                         for (k = 0; k < t.length; k++) {
344                             c = Mat.matVecMult(t[k].matrix, c);
345                         }
346 
347                         if (this === this.baseElement) {
348                             this.points[i][j] = c;
349                         } else {
350                             this.points[i].push(c);
351                         }
352                     }
353                 }
354             }
355 
356             return this;
357         },
358 
359         updateDataArray: function() { /* stub */ },
360 
361         update: function () {
362             if (this.needsUpdate) {
363                 this.updateDataArray();
364                 this.updateCoords();
365             }
366             return this;
367         },
368 
369         updateRenderer: function () {
370             this.needsUpdate = false;
371             return this;
372         },
373 
374         projectCoords: function (p, params) {
375             return Geometry.projectCoordsToParametric(p, this, 2, params);
376         }
377 
378         // projectScreenCoords: function (pScr, params) {
379         //     this.initParamsIfNeeded(params);
380         //     return Geometry.projectScreenCoordsToParametric(pScr, this, params);
381         // }
382     }
383 );
384 
385 /**
386  * @class A 3D parametric surface visualizes a map (u, v) → [X(u, v), Y(u, v), Z(u, v)].
387  * @pseudo
388  * @description A 3D parametric surface is defined by a function
389  *    <i>F: R<sup>2</sup> → R<sup>3</sup></i>.
390  *
391  * @name ParametricSurface3D
392  * @augments Curve
393  * @constructor
394  * @type Object
395  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
396  *
397  * @param {Function_Function_Function_Array,Function_Array,Function} F<sub>X</sub>,F<sub>Y</sub>,F<sub>Z</sub>,rangeU,rangeV F<sub>X</sub>(u,v), F<sub>Y</sub>(u,v), F<sub>Z</sub>(u,v)
398  * are functions returning a number, rangeU is the array containing lower and upper bound for the range of parameter u, rangeV is the array containing lower and
399  * upper bound for the range of parameter v. rangeU and rangeV may also be functions returning an array of length two.
400  * @param {Function_Array,Function_Array,Function} F,rangeU,rangeV Alternatively: F<sub>[X,Y,Z]</sub>(u,v)
401  * a function returning an array [x,y,z] of numbers, rangeU and rangeV as above.
402  *
403  * @example
404  * var view = board.create('view3d',
405  * 		        [[-6, -3], [8, 8],
406  * 		        [[-5, 5], [-5, 5], [-5, 5]]]);
407  *
408  * // Sphere
409  * var c = view.create('parametricsurface3d', [
410  *     (u, v) => 2 * Math.sin(u) * Math.cos(v),
411  *     (u, v) => 2 * Math.sin(u) * Math.sin(v),
412  *     (u, v) => 2 * Math.cos(u),
413  *     [0, 2 * Math.PI],
414  *     [0, Math.PI]
415  * ], {
416  *     strokeColor: '#ff0000',
417  *     stepsU: 30,
418  *     stepsV: 30
419  * });
420  *
421  * </pre><div id="JXG52da0ecc-1ba9-4d41-850c-36e5120025a5" class="jxgbox" style="width: 500px; height: 500px;"></div>
422  * <script type="text/javascript">
423  *     (function() {
424  *         var board = JXG.JSXGraph.initBoard('JXG52da0ecc-1ba9-4d41-850c-36e5120025a5',
425  *             {boundingbox: [-8, 8, 8,-8], axis: false, pan: {enabled: false}, showcopyright: false, shownavigation: false});
426  *     var view = board.create('view3d',
427  *            [[-6, -3], [8, 8],
428  *            [[-5, 5], [-5, 5], [-5, 5]]]);
429  *
430  *     // Sphere
431  *     var c = view.create('parametricsurface3d', [
432  *         (u, v) => 2 * Math.sin(u) * Math.cos(v),
433  *         (u, v) => 2 * Math.sin(u) * Math.sin(v),
434  *         (u, v) => 2 * Math.cos(u),
435  *         [0, 2 * Math.PI],
436  *         [0, Math.PI]
437  *     ], {
438  *         strokeColor: '#ff0000',
439  *         stepsU: 20,
440  *         stepsV: 20
441  *     });
442  *     })();
443  *
444  * </script><pre>
445  *
446  */
447 JXG.createParametricSurface3D = function (board, parents, attributes) {
448     var view = parents[0],
449         F, X, Y, Z,
450         range_u, range_v, attr,
451         base = null,
452         transform = null,
453         el;
454 
455     if (parents.length === 3) {
456         base = parents[1];
457         transform = parents[2];
458         F = null;
459         X = null;
460         Y = null;
461         Z = null;
462 
463     } else if (parents.length === 4) {
464         // [view, F, range_u, range_v]
465         F = parents[1];
466         range_u = parents[2];
467         range_v = parents[3];
468         X = null;
469         Y = null;
470         Z = null;
471     } else {
472         // [view, X, Y, Z, range_u, range_v]
473         X = parents[1];
474         Y = parents[2];
475         Z = parents[3];
476         range_u = parents[4];
477         range_v = parents[5];
478         F = null;
479     }
480 
481     attr = Type.copyAttributes(attributes, board.options, "surface3d");
482     el = new JXG.Surface3D(view, F, X, Y, Z, range_u, range_v, attr);
483 
484     attr = el.setAttr2D(attr);
485     el.element2D = view.create("curve", [[], []], attr);
486     el.element2D.view = view;
487     if (base !== null) {
488         el.addTransform(base, transform);
489         el.addParents(base);
490     }
491 
492     /**
493      * @class
494      * @ignore
495      */
496     el.element2D.updateDataArray = function () {
497         var ret = el.updateDataArray2D();
498         this.dataX = ret.X;
499         this.dataY = ret.Y;
500     };
501     el.addChild(el.element2D);
502     el.inherits.push(el.element2D);
503     el.element2D.setParents(el);
504 
505     el.element2D.prepareUpdate().update();
506     if (!board.isSuspendedUpdate) {
507         el.element2D.updateVisibility().updateRenderer();
508     }
509 
510     return el;
511 };
512 JXG.registerElement("parametricsurface3d", JXG.createParametricSurface3D);
513 
514 /**
515  * @class A 3D functiongraph  visualizes a map (x, y) → f(x, y).
516  * The graph is a {@link Curve3D} element.
517  * @pseudo
518  * @description A 3D function graph is defined by a function
519  *    <i>F: R<sup>2</sup> → R</i>.
520  *
521  * @name Functiongraph3D
522  * @augments ParametricSurface3D
523  * @constructor
524  * @type Object
525  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
526  * @param {Function,String_Array_Array} F,rangeX,rangeY  F(x,y) is a function returning a number (or a JessieCode string), rangeX is the array containing
527  * lower and upper bound for the range of x, rangeY is the array containing
528  * lower and upper bound for the range of y.
529  * @example
530  * var box = [-5, 5];
531  * var view = board.create('view3d',
532  *     [
533  *         [-6, -3], [8, 8],
534  *         [box, box, box]
535  *     ],
536  *     {
537  *         xPlaneRear: {visible: false},
538  *         yPlaneRear: {visible: false},
539  *     });
540  *
541  * // Function F to be plotted
542  * var F = (x, y) => Math.sin(x * y / 4);
543  *
544  * // 3D surface
545  * var c = view.create('functiongraph3d', [
546  *     F,
547  *     box, // () => [-s.Value()*5, s.Value() * 5],
548  *     box, // () => [-s.Value()*5, s.Value() * 5],
549  * ], {
550  *     strokeWidth: 0.5,
551  *     stepsU: 70,
552  *     stepsV: 70
553  * });
554  *
555  * </pre><div id="JXG87646dd4-9fe5-4c21-8734-089abc612515" class="jxgbox" style="width: 500px; height: 500px;"></div>
556  * <script type="text/javascript">
557  *     (function() {
558  *         var board = JXG.JSXGraph.initBoard('JXG87646dd4-9fe5-4c21-8734-089abc612515',
559  *             {boundingbox: [-8, 8, 8,-8], axis: false, pan: {enabled: false}, showcopyright: false, shownavigation: false});
560  *     var box = [-5, 5];
561  *     var view = board.create('view3d',
562  *         [
563  *             [-6, -3], [8, 8],
564  *             [box, box, box]
565  *         ],
566  *         {
567  *             xPlaneRear: {visible: false},
568  *             yPlaneRear: {visible: false},
569  *         });
570  *
571  *     // Function F to be plotted
572  *     var F = (x, y) => Math.sin(x * y / 4);
573  *
574  *     // 3D surface
575  *     var c = view.create('functiongraph3d', [
576  *         F,
577  *         box, // () => [-s.Value()*5, s.Value() * 5],
578  *         box, // () => [-s.Value()*5, s.Value() * 5],
579  *     ], {
580  *         strokeWidth: 0.5,
581  *         stepsU: 70,
582  *         stepsV: 70
583  *     });
584  *     })();
585  *
586  * </script><pre>
587  *
588  */
589 JXG.createFunctiongraph3D = function (board, parents, attributes) {
590     var view = parents[0],
591         X = function (u, v) {
592             return u;
593         },
594         Y = function (u, v) {
595             return v;
596         },
597         Z = Type.createFunction(parents[1], board, 'x, y'),
598         range_u = parents[2],
599         range_v = parents[3],
600         el;
601 
602     el = view.create("parametricsurface3d", [X, Y, Z, range_u, range_v], attributes);
603     el.elType = 'functiongraph3d';
604     return el;
605 };
606 JXG.registerElement("functiongraph3d", JXG.createFunctiongraph3D);
607