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 import JXG from "../jxg.js"; 32 import Const from "../base/constants.js"; 33 import Geometry from "../math/geometry.js"; 34 import Type from "../utils/type.js"; 35 import Mat from "../math/math.js"; 36 37 /** 38 * Constructor for 3D curves. 39 * @class Creates a new 3D curve object. Do not use this constructor to create a 3D curve. Use {@link JXG.View3D#create} with type {@link Curve3D} 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 49 * @param {Object} attributes 50 * @see JXG.Board#generateName 51 */ 52 JXG.Curve3D = function (view, F, X, Y, Z, range, attributes) { 53 this.constructor(view.board, attributes, Const.OBJECT_TYPE_CURVE3D, Const.OBJECT_CLASS_3D); 54 this.constructor3D(view, "curve3d"); 55 56 this.board.finalizeAdding(this); 57 58 /** 59 * @function 60 * @ignore 61 */ 62 this.F = F; 63 64 /** 65 * Function which maps u to x; i.e. it defines the x-coordinate of the curve 66 * @function 67 * @returns Number 68 */ 69 this.X = X; 70 71 /** 72 * Function which maps u to y; i.e. it defines the y-coordinate of the curve 73 * @function 74 * @returns Number 75 */ 76 this.Y = Y; 77 78 /** 79 * Function which maps u to z; i.e. it defines the x-coordinate of the curve 80 * @function 81 * @returns Number 82 */ 83 this.Z = Z; 84 85 this.dataX = null; 86 this.dataY = null; 87 this.dataZ = null; 88 89 if (this.F !== null) { 90 this.X = function (u) { 91 return this.F(u)[0]; 92 }; 93 this.Y = function (u) { 94 return this.F(u)[1]; 95 }; 96 this.Z = function (u) { 97 return this.F(u)[2]; 98 }; 99 } 100 101 this.range = range; 102 103 this.methodMap = Type.deepCopy(this.methodMap, { 104 // TODO 105 }); 106 }; 107 JXG.Curve3D.prototype = new JXG.GeometryElement(); 108 Type.copyPrototypeMethods(JXG.Curve3D, JXG.GeometryElement3D, "constructor3D"); 109 110 JXG.extend( 111 JXG.Curve3D.prototype, 112 /** @lends JXG.Curve3D.prototype */ { 113 updateDataArray2D: function () { 114 var steps = Type.evaluate(this.visProp.numberpointshigh), 115 r, s, e, delta, c2d, u, dataX, dataY, 116 i, 117 p = [0, 0, 0]; 118 119 dataX = []; 120 dataY = []; 121 if (Type.exists(this.dataX)) { 122 steps = this.dataX.length; 123 for (u = 0; u < steps; u++) { 124 p = [this.dataX[u], this.dataY[u], this.dataZ[u]]; 125 c2d = this.view.project3DTo2D(p); 126 dataX.push(c2d[1]); 127 dataY.push(c2d[2]); 128 } 129 } else if (Type.isArray(this.X)) { 130 steps = this.X.length; 131 for (u = 0; u < steps; u++) { 132 p = [this.X[u], this.Y[u], this.Z[u]]; 133 c2d = this.view.project3DTo2D(p); 134 dataX.push(c2d[1]); 135 dataY.push(c2d[2]); 136 } 137 } else { 138 r = Type.evaluate(this.range); 139 s = Type.evaluate(r[0]); 140 e = Type.evaluate(r[1]); 141 delta = (e - s) / (steps - 1); 142 for (i = 0, u = s; i < steps && u <= e; i++, u += delta) { 143 if (this.F !== null) { 144 p = this.F(u); 145 } else { 146 p = [this.X(u), this.Y(u), this.Z(u)]; 147 } 148 c2d = this.view.project3DTo2D(p); 149 dataX.push(c2d[1]); 150 dataY.push(c2d[2]); 151 } 152 } 153 return { X: dataX, Y: dataY }; 154 }, 155 156 updateDataArray: function() { 157 }, 158 159 update: function () { 160 // if (this.needsUpdate) { 161 this.updateDataArray(); 162 // } 163 return this; 164 }, 165 166 updateRenderer: function () { 167 this.needsUpdate = false; 168 return this; 169 }, 170 171 initParamsIfNeeded: function (params) { 172 if (params.length === 0) { 173 params.unshift(0.5*(this.range[0] + this.range[1])); 174 } 175 }, 176 177 projectCoords: function (p, params) { 178 this.initParamsIfNeeded(params); 179 return Geometry.projectCoordsToParametric(p, this, params); 180 }, 181 182 projectScreenCoords: function (pScr, params) { 183 this.initParamsIfNeeded(params); 184 return Geometry.projectScreenCoordsToParametric(pScr, this, params); 185 } 186 } 187 ); 188 189 /** 190 * @class This element creates a 3D parametric curve. 191 * @pseudo 192 * @description A 3D parametric curve is defined by a function 193 * <i>F: R<sup>1</sup> → R<sup>3</sup></i>. 194 * 195 * @name Curve3D 196 * @augments Curve 197 * @constructor 198 * @type Object 199 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 200 * @param {Function_Function_Function_Array,Function} F<sub>X</sub>,F<sub>Y</sub>,F<sub>Z</sub>,range 201 * F<sub>X</sub>(u), F<sub>Y</sub>(u), F<sub>Z</sub>(u) are functions returning a number, range is the array containing 202 * lower and upper bound for the range of the parameter u. range may also be a function returning an array of length two. 203 * @param {Function_Array,Function} F,range Alternatively: F<sub>[X,Y,Z]</sub>(u) a function returning an array [x,y,z] of 204 * numbers, range as above. 205 * @param {Array_Array_Array} X,Y,Z Three arrays containing the coordinate points which define the curve. 206 * @example 207 * // create a simple curve in 3d 208 * var bound = [-1.5, 1.5]; 209 * var view=board.create('view3d', 210 * [[-4, -4],[8, 8], 211 * [bound, bound, bound]], 212 * {}); 213 * var curve = view.create('curve3d', [(u)=>Math.cos(u), (u)=>Math.sin(u), (u)=>(u/Math.PI)-1,[0,2*Math.PI] ]); 214 * </pre><div id="JXG0f35a50e-e99d-11e8-a1ca-04d3b0c2aad3" class="jxgbox" style="width: 300px; height: 300px;"></div> 215 * <script type="text/javascript"> 216 * (function() { 217 * var board = JXG.JSXGraph.initBoard('JXG0f35a50e-e99d-11e8-a1ca-04d3b0c2aad3', 218 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 219 * // create a simple curve in 3d 220 * var bound = [-1.5, 1.5]; 221 * var view=board.create('view3d', 222 * [[-4, -4],[8, 8], 223 * [bound, bound, bound]], 224 * {}); 225 * var curve = view.create('curve3d', [(u)=>Math.cos(u), (u)=>Math.sin(u), (u)=>(u/Math.PI)-1,[0,2*Math.PI] ]); 226 * })(); 227 * </script><pre> 228 */ 229 JXG.createCurve3D = function (board, parents, attributes) { 230 var view = parents[0], 231 F, X, Y, Z, range, attr, el; 232 233 if (parents.length === 3) { 234 F = parents[1]; 235 range = parents[2]; 236 X = null; 237 Y = null; 238 Z = null; 239 } else { 240 X = parents[1]; 241 Y = parents[2]; 242 Z = parents[3]; 243 range = parents[4]; 244 F = null; 245 } 246 // TODO Throw error 247 248 attr = Type.copyAttributes(attributes, board.options, "curve3d"); 249 el = new JXG.Curve3D(view, F, X, Y, Z, range, attr); 250 251 attr = el.setAttr2D(attr); 252 el.element2D = view.create("curve", [[], []], attr); 253 el.element2D.view = view; 254 255 /** 256 * @class 257 * @ignore 258 */ 259 el.element2D.updateDataArray = function () { 260 var ret = el.updateDataArray2D(); 261 this.dataX = ret.X; 262 this.dataY = ret.Y; 263 }; 264 el.addChild(el.element2D); 265 el.inherits.push(el.element2D); 266 el.element2D.setParents(el); 267 268 el.element2D.prepareUpdate().update(); 269 if (!board.isSuspendedUpdate) { 270 el.element2D.updateVisibility().updateRenderer(); 271 } 272 273 return el; 274 }; 275 276 JXG.registerElement("curve3d", JXG.createCurve3D); 277 278 /** 279 * @class 3D vector field. 280 * <p> 281 * Plot a vector field either given by three functions 282 * f1(x, y, z), f2(x, y, z), and f3(x, y, z) or by a function f(x, y, z) 283 * returning an array of size 3. 284 * 285 * @pseudo 286 * @name Vectorfield3D 287 * @augments JXG.Curve3D 288 * @constructor 289 * @type JXG.Curve3D 290 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 291 * Parameter options: 292 * @param {Array|Function|String} F Either an array containing three functions f1(x, y, z), f2(x, y, z), 293 * and f3(x, y) or function f(x, y, z) returning an array of length 3. 294 * @param {Array} xData Array of length 3 containing start value for x, number of steps, 295 * end value of x. The vector field will contain (number of steps) + 1 vectors in direction of x. 296 * @param {Array} yData Array of length 3 containing start value for y, number of steps, 297 * end value of y. The vector field will contain (number of steps) + 1 vectors in direction of y. 298 * @param {Array} zData Array of length 3 containing start value for z, number of steps, 299 * end value of z. The vector field will contain (number of steps) + 1 vectors in direction of z. 300 */ 301 JXG.createVectorfield3D = function (board, parents, attributes) { 302 var view = parents[0], 303 el, attr; 304 305 if (!(parents.length >= 5 && 306 (Type.isArray(parents[1]) || Type.isFunction(parents[0]) || Type.isString(parents[0])) && 307 (Type.isArray(parents[2]) && parents[1].length === 3) && 308 (Type.isArray(parents[3]) && parents[2].length === 3) && 309 (Type.isArray(parents[4]) && parents[3].length === 3) 310 )) { 311 throw new Error( 312 "JSXGraph: Can't create vector field 3D with parent types " + 313 "'" + typeof parents[0] + "', " + 314 "'" + typeof parents[1] + "', " + 315 "'" + typeof parents[2] + "'." + 316 "'" + typeof parents[1] + "', " 317 ); 318 } 319 320 attr = Type.copyAttributes(attributes, board.options, 'vectorfield3d'); 321 el = view.create('curve3d', [[], [], []], attr); 322 323 /** 324 * Set the defining functions of 3D vector field. 325 * @memberOf Vectorfield3D 326 * @name setF 327 * @function 328 * @param {Array|Function} func Either an array containing three functions f1(x, y, z), 329 * f2(x, y, z), and f3(x, y, z) or function f(x, y, z) returning an array of length 3. 330 * @returns {Object} Reference to the 3D vector field object. 331 * 332 * @example 333 * field.setF([(x, y, z) => Math.sin(y), (x, y, z) => Math.cos(x), (x, y, z) => z]); 334 * board.update(); 335 * 336 */ 337 el.setF = function (func, varnames) { 338 var f0, f1, f2; 339 if (Type.isArray(func)) { 340 f0 = Type.createFunction(func[0], this.board, varnames); 341 f1 = Type.createFunction(func[1], this.board, varnames); 342 f2 = Type.createFunction(func[2], this.board, varnames); 343 /** 344 * @ignore 345 */ 346 this.F = function (x, y, z) { 347 return [f0(x, y, z), f1(x, y, z), f2(x, y, z)]; 348 }; 349 } else { 350 this.F = Type.createFunction(func, el.board, varnames); 351 } 352 return this; 353 }; 354 355 el.setF(parents[1], 'x, y, z'); 356 el.xData = parents[2]; 357 el.yData = parents[3]; 358 el.zData = parents[4]; 359 360 el.updateDataArray = function () { 361 var k, i, j, 362 v, nrm, 363 x, y, z, 364 scale = Type.evaluate(this.visProp.scale), 365 start = [ 366 Type.evaluate(this.xData[0]), 367 Type.evaluate(this.yData[0]), 368 Type.evaluate(this.zData[0]) 369 ], 370 steps = [ 371 Type.evaluate(this.xData[1]), 372 Type.evaluate(this.yData[1]), 373 Type.evaluate(this.zData[1]) 374 ], 375 end = [ 376 Type.evaluate(this.xData[2]), 377 Type.evaluate(this.yData[2]), 378 Type.evaluate(this.zData[2]) 379 ], 380 delta = [ 381 (end[0] - start[0]) / steps[0], 382 (end[1] - start[1]) / steps[1], 383 (end[2] - start[2]) / steps[2] 384 ], 385 phi, theta1, theta2, theta, 386 showArrow = Type.evaluate(this.visProp.arrowhead.enabled), 387 leg, leg_x, leg_y, leg_z, alpha; 388 389 if (showArrow) { 390 // Arrow head style 391 // leg = 8; 392 // alpha = Math.PI * 0.125; 393 leg = Type.evaluate(this.visProp.arrowhead.size); 394 alpha = Type.evaluate(this.visProp.arrowhead.angle); 395 leg_x = leg / board.unitX; 396 leg_y = leg / board.unitY; 397 leg_z = leg / Math.sqrt(board.unitX * board.unitY); 398 } 399 400 this.dataX = []; 401 this.dataY = []; 402 this.dataZ = []; 403 for (i = 0, x = start[0]; i <= steps[0]; x += delta[0], i++) { 404 for (j = 0, y = start[1]; j <= steps[1]; y += delta[1], j++) { 405 for (k = 0, z = start[2]; k <= steps[2]; z += delta[2], k++) { 406 v = this.F(x, y, z); 407 nrm = Mat.norm(v); 408 if (nrm < Number.EPSILON) { 409 continue; 410 } 411 412 v[0] *= scale; 413 v[1] *= scale; 414 v[2] *= scale; 415 Type.concat(this.dataX, [x, x + v[0], NaN]); 416 Type.concat(this.dataY, [y, y + v[1], NaN]); 417 Type.concat(this.dataZ, [z, z + v[2], NaN]); 418 419 if (showArrow) { 420 // Arrow head 421 nrm *= scale; 422 phi = Math.atan2(v[1], v[0]); 423 theta = Math.asin(v[2] / nrm); 424 theta1 = theta - alpha; 425 theta2 = theta + alpha; 426 Type.concat(this.dataX, [ 427 x + v[0] - leg_x * Math.cos(phi) * Math.cos(theta1), 428 x + v[0], 429 x + v[0] - leg_x * Math.cos(phi) * Math.cos(theta2), 430 NaN]); 431 Type.concat(this.dataY, [ 432 y + v[1] - leg_y * Math.sin(phi) * Math.cos(theta1), 433 y + v[1], 434 y + v[1] - leg_y * Math.sin(phi) * Math.cos(theta2), 435 NaN]); 436 Type.concat(this.dataZ, [ 437 z + v[2] - leg_z * Math.sin(theta2), 438 z + v[2], 439 z + v[2] - leg_z * Math.sin(theta1), 440 NaN]); 441 } 442 } 443 } 444 } 445 }; 446 447 el.methodMap = Type.deepCopy(el.methodMap, { 448 setF: "setF" 449 }); 450 451 return el; 452 }; 453 454 JXG.registerElement("vectorfield3D", JXG.createVectorfield3D); 455