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