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