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