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