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 curves. 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 */ 207 JXG.createCurve3D = function (board, parents, attributes) { 208 var view = parents[0], 209 F, X, Y, Z, range, attr, el; 210 211 if (parents.length === 3) { 212 F = parents[1]; 213 range = parents[2]; 214 X = null; 215 Y = null; 216 Z = null; 217 } else { 218 X = parents[1]; 219 Y = parents[2]; 220 Z = parents[3]; 221 range = parents[4]; 222 F = null; 223 } 224 // TODO Throw error 225 226 attr = Type.copyAttributes(attributes, board.options, "curve3d"); 227 el = new JXG.Curve3D(view, F, X, Y, Z, range, attr); 228 229 attr = el.setAttr2D(attr); 230 el.element2D = view.create("curve", [[], []], attr); 231 el.element2D.view = view; 232 233 /** 234 * @class 235 * @ignore 236 */ 237 el.element2D.updateDataArray = function () { 238 var ret = el.updateDataArray2D(); 239 this.dataX = ret.X; 240 this.dataY = ret.Y; 241 }; 242 el.addChild(el.element2D); 243 el.inherits.push(el.element2D); 244 el.element2D.setParents(el); 245 246 el.element2D.prepareUpdate().update(); 247 if (!board.isSuspendedUpdate) { 248 el.element2D.updateVisibility().updateRenderer(); 249 } 250 251 return el; 252 }; 253 254 JXG.registerElement("curve3d", JXG.createCurve3D); 255 256 /** 257 * @class 3D vector field. 258 * <p> 259 * Plot a vector field either given by three functions 260 * f1(x, y, z), f2(x, y, z), and f3(x, y, z) or by a function f(x, y, z) 261 * returning an array of size 3. 262 * 263 * @pseudo 264 * @name Vectorfield3D 265 * @augments JXG.Curve3D 266 * @constructor 267 * @type JXG.Curve3D 268 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 269 * Parameter options: 270 * @param {Array|Function|String} F Either an array containing three functions f1(x, y, z), f2(x, y, z), 271 * and f3(x, y) or function f(x, y, z) returning an array of length 3. 272 * @param {Array} xData Array of length 3 containing start value for x, number of steps, 273 * end value of x. The vector field will contain (number of steps) + 1 vectors in direction of x. 274 * @param {Array} yData Array of length 3 containing start value for y, number of steps, 275 * end value of y. The vector field will contain (number of steps) + 1 vectors in direction of y. 276 * @param {Array} zData Array of length 3 containing start value for z, number of steps, 277 * end value of z. The vector field will contain (number of steps) + 1 vectors in direction of z. 278 */ 279 JXG.createVectorfield3D = function (board, parents, attributes) { 280 var view = parents[0], 281 el, attr; 282 283 if (!(parents.length >= 5 && 284 (Type.isArray(parents[1]) || Type.isFunction(parents[0]) || Type.isString(parents[0])) && 285 (Type.isArray(parents[2]) && parents[1].length === 3) && 286 (Type.isArray(parents[3]) && parents[2].length === 3) && 287 (Type.isArray(parents[4]) && parents[3].length === 3) 288 )) { 289 throw new Error( 290 "JSXGraph: Can't create vector field 3D with parent types " + 291 "'" + typeof parents[0] + "', " + 292 "'" + typeof parents[1] + "', " + 293 "'" + typeof parents[2] + "'." + 294 "'" + typeof parents[1] + "', " 295 ); 296 } 297 298 attr = Type.copyAttributes(attributes, board.options, 'vectorfield3d'); 299 el = view.create('curve3d', [[], [], []], attr); 300 301 /** 302 * Set the defining functions of 3D vector field. 303 * @memberOf Vectorfield3D 304 * @name setF 305 * @function 306 * @param {Array|Function} func Either an array containing three functions f1(x, y, z), 307 * f2(x, y, z), and f3(x, y, z) or function f(x, y, z) returning an array of length 3. 308 * @returns {Object} Reference to the 3D vector field object. 309 * 310 * @example 311 * field.setF([(x, y, z) => Math.sin(y), (x, y, z) => Math.cos(x), (x, y, z) => z]); 312 * board.update(); 313 * 314 */ 315 el.setF = function (func, varnames) { 316 var f0, f1, f2; 317 if (Type.isArray(func)) { 318 f0 = Type.createFunction(func[0], this.board, varnames); 319 f1 = Type.createFunction(func[1], this.board, varnames); 320 f2 = Type.createFunction(func[2], this.board, varnames); 321 /** 322 * @ignore 323 */ 324 this.F = function (x, y, z) { 325 return [f0(x, y, z), f1(x, y, z), f2(x, y, z)]; 326 }; 327 } else { 328 this.F = Type.createFunction(func, el.board, varnames); 329 } 330 return this; 331 }; 332 333 el.setF(parents[1], 'x, y, z'); 334 el.xData = parents[2]; 335 el.yData = parents[3]; 336 el.zData = parents[4]; 337 338 el.updateDataArray = function () { 339 var k, i, j, 340 v, nrm, 341 x, y, z, 342 scale = Type.evaluate(this.visProp.scale), 343 start = [ 344 Type.evaluate(this.xData[0]), 345 Type.evaluate(this.yData[0]), 346 Type.evaluate(this.zData[0]) 347 ], 348 steps = [ 349 Type.evaluate(this.xData[1]), 350 Type.evaluate(this.yData[1]), 351 Type.evaluate(this.zData[1]) 352 ], 353 end = [ 354 Type.evaluate(this.xData[2]), 355 Type.evaluate(this.yData[2]), 356 Type.evaluate(this.zData[2]) 357 ], 358 delta = [ 359 (end[0] - start[0]) / steps[0], 360 (end[1] - start[1]) / steps[1], 361 (end[2] - start[2]) / steps[2] 362 ], 363 phi, theta1, theta2, theta, 364 showArrow = Type.evaluate(this.visProp.arrowhead.enabled), 365 leg, leg_x, leg_y, leg_z, alpha; 366 367 if (showArrow) { 368 // Arrow head style 369 // leg = 8; 370 // alpha = Math.PI * 0.125; 371 leg = Type.evaluate(this.visProp.arrowhead.size); 372 alpha = Type.evaluate(this.visProp.arrowhead.angle); 373 leg_x = leg / board.unitX; 374 leg_y = leg / board.unitY; 375 leg_z = leg / Math.sqrt(board.unitX * board.unitY); 376 } 377 378 this.dataX = []; 379 this.dataY = []; 380 this.dataZ = []; 381 for (i = 0, x = start[0]; i <= steps[0]; x += delta[0], i++) { 382 for (j = 0, y = start[1]; j <= steps[1]; y += delta[1], j++) { 383 for (k = 0, z = start[2]; k <= steps[2]; z += delta[2], k++) { 384 v = this.F(x, y, z); 385 nrm = Mat.norm(v); 386 if (nrm < Number.EPSILON) { 387 continue; 388 } 389 390 v[0] *= scale; 391 v[1] *= scale; 392 v[2] *= scale; 393 Type.concat(this.dataX, [x, x + v[0], NaN]); 394 Type.concat(this.dataY, [y, y + v[1], NaN]); 395 Type.concat(this.dataZ, [z, z + v[2], NaN]); 396 397 if (showArrow) { 398 // Arrow head 399 nrm *= scale; 400 phi = Math.atan2(v[1], v[0]); 401 theta = Math.asin(v[2] / nrm); 402 theta1 = theta - alpha; 403 theta2 = theta + alpha; 404 Type.concat(this.dataX, [ 405 x + v[0] - leg_x * Math.cos(phi) * Math.cos(theta1), 406 x + v[0], 407 x + v[0] - leg_x * Math.cos(phi) * Math.cos(theta2), 408 NaN]); 409 Type.concat(this.dataY, [ 410 y + v[1] - leg_y * Math.sin(phi) * Math.cos(theta1), 411 y + v[1], 412 y + v[1] - leg_y * Math.sin(phi) * Math.cos(theta2), 413 NaN]); 414 Type.concat(this.dataZ, [ 415 z + v[2] - leg_z * Math.sin(theta2), 416 z + v[2], 417 z + v[2] - leg_z * Math.sin(theta1), 418 NaN]); 419 } 420 } 421 } 422 } 423 }; 424 425 el.methodMap = Type.deepCopy(el.methodMap, { 426 setF: "setF" 427 }); 428 429 return el; 430 }; 431 432 JXG.registerElement("vectorfield3D", JXG.createVectorfield3D); 433