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