1 /* 2 Copyright 2008-2026 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 /** 32 * Create axes and rear and front walls of the 33 * view3d bounding box bbox3D. 34 */ 35 import JXG from "../jxg.js"; 36 import Type from "../utils/type.js"; 37 38 /** 39 * @class A container element that creates the axes and rear and front planes of a 3D view. 40 * @pseudo 41 * @description This element "axes3d" is used to create 42 * <ul> 43 * <li> 3D coordinate axes (either "axesPosition:'border'" or "axesPosition:'center'") 44 * <li> A point3d "O" (origin) if "axesPosition:'center'" 45 * <li> Rear and front planes in all three directions of the view3d element. 46 * <li> Coordinate axes on the rear and front planes 47 * </ul> 48 * 49 * @name Axes3D 50 * @constructor 51 * @type Object 52 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 53 * 54 */ 55 JXG.createAxes3D = function (board, parents, attributes) { 56 var view = parents[0], 57 directions = ["x", "y", "z"], 58 suffixAxis = "Axis", 59 sides = ["Rear", "Front"], 60 rear = [0, 0, 0], // x, y, z 61 front = [0, 0, 0], // x, y, z 62 i, j, k, i1, i2, attr, pos, 63 dir, dir1, len, 64 from, to, vec1, vec2, 65 range1, range2, 66 na, na_parent, 67 ticks_attr, 68 axes = {}; 69 70 if (Type.exists(view.bbox3D)) { 71 for (i = 0; i < directions.length; i++) { 72 rear[i] = view.bbox3D[i][0]; 73 front[i] = view.bbox3D[i][1]; 74 } 75 } else { 76 for (i = 0; i < directions.length; i++) { 77 rear[i] = parents[1][i]; 78 front[i] = parents[2][i]; 79 } 80 } 81 82 // Main 3D axes 83 attr = Type.copyAttributes(attributes, board.options, 'axes3d'); 84 // Position of the main axes can not be changed during run time 85 pos = attr.axesposition; 86 87 for (i = 0; i < directions.length; i++) { 88 // Run through ['x', 'y', 'z'] 89 dir = directions[i]; 90 na = dir + suffixAxis; 91 92 if (pos === 'center') { 93 // Axes centered 94 from = [0, 0, 0]; 95 to = [0, 0, 0]; 96 to[i] = front[i]; 97 axes[na] = view.create('axis3d', [from, to], attr[na.toLowerCase()]); 98 axes[na].view = view; 99 } else if (pos === 'border') { 100 // Axes bordered 101 na += 'Border'; 102 from = rear.slice(); 103 to = front.slice(); 104 if (dir === 'z') { 105 from[1] = front[1]; 106 to[0] = rear[0]; 107 } else if (dir === 'x') { 108 from = [rear[0], front[1], rear[2]]; 109 to = [front[0], front[1], rear[2]]; 110 } else { 111 from = [front[0], rear[1], rear[2]]; 112 to = [front[0], front[1], rear[2]]; 113 } 114 to[i] = front[i]; 115 // attr[na.toLowerCase()].lastArrow = false; 116 axes[na] = view.create('axis3d', [from, to], attr[na.toLowerCase()]); 117 axes[na].view = view; 118 119 ticks_attr = attr[na.toLowerCase()].ticks3d; 120 ticks_attr.element3d = true; // Needed to avoid update during change of view 121 len = front[i] - rear[i]; 122 if (dir === 'x') { 123 axes[na + "Ticks"] = view.create("ticks3d", [from, [1, 0, 0], len, [0, 1, 0]], ticks_attr); 124 } else if (dir === 'y') { 125 axes[na + "Ticks"] = view.create("ticks3d", [from, [0, 1, 0], len, [1, 0, 0]], ticks_attr); 126 } else { 127 axes[na + "Ticks"] = view.create("ticks3d", [from, [0, 0, 1], len, [0, 1, 0]], ticks_attr); 128 } 129 axes[na + "Ticks"].view = view; 130 } 131 } 132 133 if (pos === 'center') { 134 // Origin (2D point) 135 axes.O = view.create( 136 "intersection", 137 [axes[directions[0] + suffixAxis], axes[directions[1] + suffixAxis]], 138 { 139 name: "", 140 visible: false, 141 withLabel: false 142 } 143 ); 144 axes.O.view = view; 145 } else { 146 axes.O = null; 147 } 148 149 // Front and rear planes 150 for (i = 0; i < directions.length; i++) { 151 // Run through ['x', 'y', 'z'] 152 i1 = (i + 1) % 3; 153 i2 = (i + 2) % 3; 154 155 dir = directions[i]; 156 for (j = 0; j < sides.length; j++) { 157 // Run through ['Rear', 'Front'] 158 // attr = Type.copyAttributes(attributes, board.options, 'axes3d'); 159 160 na = dir + "Plane" + sides[j]; 161 162 from = [0, 0, 0]; 163 from[i] = j === 0 ? rear[i] : front[i]; 164 vec1 = [0, 0, 0]; 165 vec2 = [0, 0, 0]; 166 vec1[i1] = 1; 167 vec2[i2] = 1; 168 range1 = [rear[i1], front[i1]]; 169 range2 = [rear[i2], front[i2]]; 170 171 attr = Type.copyAttributes(attributes, board.options, "axes3d", na); 172 axes[na] = view.create('plane3d', [from, vec1, vec2, range1, range2], attr); 173 axes[na].elType = 'axisplane3d'; 174 } 175 } 176 177 // Axes on front and rear planes 178 for (i = 0; i < directions.length; i++) { 179 // Run through ['x', 'y', 'z'] 180 dir = directions[i]; 181 for (j = 0; j < sides.length; j++) { 182 for (k = 1; k <= 2; k++) { 183 i1 = (i + k) % 3; 184 dir1 = directions[i1]; 185 na = dir + "Plane" + sides[j] + dir1.toUpperCase() + 'Axis'; 186 na_parent = dir + "Plane" + sides[j]; 187 188 from = [0, 0, 0]; 189 to = [0, 0, 0]; 190 from[i] = to[i] = j === 0 ? rear[i] : front[i]; 191 192 from[i1] = rear[i1]; 193 to[i1] = front[i1]; 194 195 attr = Type.copyAttributes(attributes, board.options, "axes3d", na); 196 axes[na] = view.create("axis3d", [from, to], attr); 197 axes[na].view = view; 198 axes[na_parent].addChild(axes[na]); 199 //if (Type.exists(axes[na_parent].element2D)) { 200 // TODO: Access of element2D is not nice 201 axes[na_parent].element2D.inherits.push(axes[na]); 202 //} 203 204 } 205 } 206 } 207 208 return axes; 209 }; 210 JXG.registerElement("axes3d", JXG.createAxes3D); 211 212 /** 213 * @class A 3D axis element is a line together with optional ticks and labels. 214 * @pseudo 215 * @description Simple element 3d axis as used with "axesPosition:center". No ticks and no label (yet). 216 * <p> 217 * At the time being, the input arrays are NOT dynamic, i.e. can not be given as functions. 218 * 219 * @name Axis3D 220 * @augments Arrow 221 * @constructor 222 * @type Object 223 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 224 * @param {Array_Array} start,end Two arrays of length 3 for the start point and the end point of the axis. 225 * 226 */ 227 JXG.createAxis3D = function (board, parents, attributes) { 228 var view = parents[0], 229 attr = Type.copyAttributes(attributes, board.options, 'axis3d'); 230 231 return view.create('line3d', parents.slice(1), attr); 232 }; 233 JXG.registerElement('axis3d', JXG.createAxis3D); 234 235 // JXG.createAxis3DOld = function (board, parents, attributes) { 236 // var view = parents[0], 237 // attr, 238 // start = parents[1], 239 // end = parents[2], 240 // el_start, 241 // el_end, 242 // el; 243 // 244 // // Use 2D points to create axis 245 // attr = Type.copyAttributes(attributes.point1, board.options, "axis3d", 'point1'); 246 // attr.element3d = true; // Needed to avoid update during change of view 247 // el_start = view.create( 248 // "point", 249 // [ 250 // (function (xx, yy, zz) { 251 // return function () { 252 // return view.project3DTo2D(xx, yy, zz)[1]; 253 // }; 254 // })(start[0], start[1], start[2]), 255 // (function (xx, yy, zz) { 256 // return function () { 257 // return view.project3DTo2D(xx, yy, zz)[2]; 258 // }; 259 // })(start[0], start[1], start[2]) 260 // ], 261 // attr 262 // ); 263 // 264 // attr = Type.copyAttributes(attributes.point2, board.options, "axis3d", 'point2'); 265 // attr.element3d = true; // Needed to avoid update during change of view 266 // el_end = view.create( 267 // "point", 268 // [ 269 // (function (xx, yy, zz) { 270 // return function () { 271 // return view.project3DTo2D(xx, yy, zz)[1]; 272 // }; 273 // })(end[0], end[1], end[2]), 274 // (function (xx, yy, zz) { 275 // return function () { 276 // return view.project3DTo2D(xx, yy, zz)[2]; 277 // }; 278 // })(end[0], end[1], end[2]) 279 // ], 280 // attr 281 // ); 282 // 283 // attr = Type.copyAttributes(attributes, board.options, 'axis3d'); 284 // attr.element3d = true; // Needed to avoid update during change of view 285 // el = view.create("arrow", [el_start, el_end], attr); 286 // 287 // return el; 288 // }; 289 290 /** 291 * @class Display a rectangular mesh on a 3D plane element. 292 * @pseudo 293 * @description Create a (rectangular) mesh - i.e. grid lines - on a plane3D element. 294 * <p> 295 * At the time being, the mesh is not connected to the plane. The connecting element is simply the 296 * parameter point. 297 * 298 * @name Mesh3D 299 * @augments Curve 300 * @constructor 301 * @type Object 302 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 303 * @param {Array_Array_Array_Array_Array} point,direction1,direction2,range1,range2 point is an array of length 3 304 * determining the starting point of the grid. direction1 and direction2 are arrays of length 3 for the directions of the grid. 305 * range1 and range2 (arrays of length 2) give the respective ranges. 306 * All parameters can be supplied as functions returning an appropriate data type. 307 * 308 */ 309 JXG.createMesh3D = function (board, parents, attributes) { 310 var view = parents[0], 311 attr, el; 312 313 attr = Type.copyAttributes(attributes, board.options, 'mesh3d'); 314 attr.element3d = true; // Needed to avoid update during change of view 315 el = view.create("curve", [[], []], attr); 316 317 el.point = parents[1]; 318 el.direction1 = parents[2]; 319 el.direction2 = parents[3]; 320 el.range1 = parents[4]; 321 el.range2 = parents[5]; 322 323 /** 324 * @ignore 325 */ 326 el.updateDataArray = function () { 327 var range1 = Type.evaluate(this.range1), 328 range2 = Type.evaluate(this.range2), 329 s1 = range1[0], 330 e1 = range1[1], 331 s2 = range2[0], 332 e2 = range2[1], 333 l1, l2, res, i, 334 v1 = [0, 0, 0], 335 v2 = [0, 0, 0], 336 step_u = this.evalVisProp('stepwidthu'), 337 step_v = this.evalVisProp('stepwidthv'), 338 q = [0, 0, 0]; 339 340 this.dataX = []; 341 this.dataY = []; 342 343 if (Type.isFunction(this.point)) { 344 q = this.point(); 345 } else { 346 if (Type.isPoint3D(this.point)) { 347 q = this.point.coords; 348 } else { 349 for (i = 0; i < this.point.length; i++) { 350 q[i] = Type.evaluate(this.point[i]); 351 } 352 } 353 } 354 if (Type.isFunction(this.direction1)) { 355 v1 = Type.evaluate(this.direction1); 356 } else { 357 for (i = 0; i < this.direction1.length; i++) { 358 v1[i] = Type.evaluate(this.direction1[i]); 359 } 360 } 361 if (Type.isFunction(this.direction2)) { 362 v2 = Type.evaluate(this.direction2); 363 } else { 364 for (i = 0; i < this.direction2.length; i++) { 365 v2[i] = Type.evaluate(this.direction2[i]); 366 } 367 } 368 if (q.length === 4) { 369 q = q.slice(1); 370 } 371 if (v1.length === 4) { 372 v1 = v1.slice(1); 373 } 374 if (v2.length === 4) { 375 v2 = v2.slice(1); 376 } 377 378 l1 = JXG.Math.norm(v1, 3); 379 l2 = JXG.Math.norm(v2, 3); 380 for (i = 0; i < 3; i++) { 381 v1[i] /= l1; 382 v2[i] /= l2; 383 } 384 385 // sol = Mat.Geometry.getPlaneBounds(v1, v2, q, s1, e1); 386 // if (sol !== null) { 387 // s1 = sol[0]; 388 // e1 = sol[1]; 389 // s2 = sol[2]; 390 // e2 = sol[3]; 391 // } 392 393 res = view.getMesh( 394 [ 395 function (u, v) { 396 return q[0] + u * v1[0] + v * v2[0]; 397 }, 398 function (u, v) { 399 return q[1] + u * v1[1] + v * v2[1]; 400 }, 401 function (u, v) { 402 return q[2] + u * v1[2] + v * v2[2]; 403 } 404 ], 405 [Math.ceil(s1), Math.floor(e1), (Math.ceil(e1) - Math.floor(s1)) / step_u], 406 [Math.ceil(s2), Math.floor(e2), (Math.ceil(e2) - Math.floor(s2)) / step_v] 407 ); 408 this.dataX = res[0]; 409 this.dataY = res[1]; 410 }; 411 412 return el; 413 }; 414 415 JXG.registerElement("mesh3d", JXG.createMesh3D); 416