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 import GeometryElement from "../base/element"; 36 import Composition from "../base/composition"; 37 38 /** 39 * 3D view inside of a JXGraph board. 40 * 41 * @class Creates a new 3D view. Do not use this constructor to create a 3D view. Use {@link JXG.Board#create} with 42 * type {@link View3D} instead. 43 * 44 * @augments JXG.GeometryElement 45 * @param {Array} parents Array consisting of lower left corner [x, y] of the view inside the board, [width, height] of the view 46 * and box size [[x1, x2], [y1,y2], [z1,z2]]. If the view's azimuth=0 and elevation=0, the 3D view will cover a rectangle with lower left corner 47 * [x,y] and side lengths [w, h] of the board. 48 */ 49 JXG.View3D = function (board, parents, attributes) { 50 this.constructor(board, attributes, Const.OBJECT_TYPE_VIEW3D, Const.OBJECT_CLASS_3D); 51 52 /** 53 * An associative array containing all geometric objects belonging to the view. 54 * Key is the id of the object and value is a reference to the object. 55 * @type Object 56 * @private 57 */ 58 this.objects = {}; 59 60 /** 61 * An array containing all geometric objects in this view in the order of construction. 62 * @type Array 63 * @private 64 */ 65 // this.objectsList = []; 66 67 /** 68 * An associative array / dictionary to store the objects of the board by name. The name of the object is the key and value is a reference to the object. 69 * @type Object 70 * @private 71 */ 72 this.elementsByName = {}; 73 74 /** 75 * Default axes of the 3D view, contains the axes of the view or null. 76 * 77 * @type {Object} 78 * @default null 79 */ 80 this.defaultAxes = null; 81 82 /** 83 * 3D-to-2D transformation matrix 84 * @type {Array} 3 x 4 matrix 85 * @private 86 */ 87 this.matrix3D = [ 88 [1, 0, 0, 0], 89 [0, 1, 0, 0], 90 [0, 0, 1, 0] 91 ]; 92 93 /** 94 * Lower left corner [x, y] of the 3D view if elevation and azimuth are set to 0. 95 * @type array 96 * @private 97 */ 98 this.llftCorner = parents[0]; 99 100 /** 101 * Width and height [w, h] of the 3D view if elevation and azimuth are set to 0. 102 * @type array 103 * @private 104 */ 105 this.size = parents[1]; 106 107 /** 108 * Bounding box (cube) [[x1, x2], [y1,y2], [z1,z2]] of the 3D view 109 * @type array 110 */ 111 this.bbox3D = parents[2]; 112 113 /** 114 * Distance of the view to the origin. In other words, its 115 * the radius of the sphere where the camera sits.view.board.update 116 * @type Number 117 */ 118 this.r = -1; 119 120 this.timeoutAzimuth = null; 121 122 this.id = this.board.setId(this, "V"); 123 this.board.finalizeAdding(this); 124 this.elType = "view3d"; 125 126 this.methodMap = Type.deepCopy(this.methodMap, { 127 // TODO 128 }); 129 }; 130 JXG.View3D.prototype = new GeometryElement(); 131 132 JXG.extend( 133 JXG.View3D.prototype, /** @lends JXG.View3D.prototype */ { 134 135 /** 136 * Creates a new 3D element of type elementType. 137 * @param {String} elementType Type of the element to be constructed given as a string e.g. 'point3d' or 'surface3d'. 138 * @param {Array} parents Array of parent elements needed to construct the element e.g. coordinates for a 3D point or two 139 * 3D points to construct a line. This highly depends on the elementType that is constructed. See the corresponding JXG.create* 140 * methods for a list of possible parameters. 141 * @param {Object} [attributes] An object containing the attributes to be set. This also depends on the elementType. 142 * Common attributes are name, visible, strokeColor. 143 * @returns {Object} Reference to the created element. This is usually a GeometryElement3D, but can be an array containing 144 * two or more elements. 145 */ 146 create: function (elementType, parents, attributes) { 147 var prefix = [], 148 // is3D = false, 149 el; 150 151 if (elementType.indexOf("3d") > 0) { 152 // is3D = true; 153 prefix.push(this); 154 } 155 el = this.board.create(elementType, prefix.concat(parents), attributes); 156 157 return el; 158 }, 159 160 /** 161 * Select a single or multiple elements at once. 162 * @param {String|Object|function} str The name, id or a reference to a JSXGraph 3D element in the 3D view. An object will 163 * be used as a filter to return multiple elements at once filtered by the properties of the object. 164 * @param {Boolean} onlyByIdOrName If true (default:false) elements are only filtered by their id, name or groupId. 165 * The advanced filters consisting of objects or functions are ignored. 166 * @returns {JXG.GeometryElement3D|JXG.Composition} 167 * @example 168 * // select the element with name A 169 * view.select('A'); 170 * 171 * // select all elements with strokecolor set to 'red' (but not '#ff0000') 172 * view.select({ 173 * strokeColor: 'red' 174 * }); 175 * 176 * // select all points on or below the x/y plane and make them black. 177 * view.select({ 178 * elType: 'point3d', 179 * Z: function (v) { 180 * return v <= 0; 181 * } 182 * }).setAttribute({color: 'black'}); 183 * 184 * // select all elements 185 * view.select(function (el) { 186 * return true; 187 * }); 188 */ 189 select: function (str, onlyByIdOrName) { 190 var flist, 191 olist, 192 i, 193 l, 194 s = str; 195 196 if (s === null) { 197 return s; 198 } 199 200 // It's a string, most likely an id or a name. 201 if (Type.isString(s) && s !== "") { 202 // Search by ID 203 if (Type.exists(this.objects[s])) { 204 s = this.objects[s]; 205 // Search by name 206 } else if (Type.exists(this.elementsByName[s])) { 207 s = this.elementsByName[s]; 208 // // Search by group ID 209 // } else if (Type.exists(this.groups[s])) { 210 // s = this.groups[s]; 211 } 212 213 // It's a function or an object, but not an element 214 } else if ( 215 !onlyByIdOrName && 216 (Type.isFunction(s) || (Type.isObject(s) && !Type.isFunction(s.setAttribute))) 217 ) { 218 flist = Type.filterElements(this.objectsList, s); 219 220 olist = {}; 221 l = flist.length; 222 for (i = 0; i < l; i++) { 223 olist[flist[i].id] = flist[i]; 224 } 225 s = new Composition(olist); 226 227 // It's an element which has been deleted (and still hangs around, e.g. in an attractor list 228 } else if ( 229 Type.isObject(s) && 230 Type.exists(s.id) && 231 !Type.exists(this.objects[s.id]) 232 ) { 233 s = null; 234 } 235 236 return s; 237 }, 238 239 update: function () { 240 // Update 3D-to-2D transformation matrix with the actual 241 // elevation and azimuth angles. 242 243 var e, r, a, f, mat, shift; 244 245 if ( 246 !Type.exists(this.el_slide) || 247 !Type.exists(this.az_slide) || 248 !this.needsUpdate 249 ) { 250 return this; 251 } 252 253 e = this.el_slide.Value(); 254 r = this.r; 255 a = this.az_slide.Value(); 256 f = r * Math.sin(e); 257 mat = [ 258 [1, 0, 0], 259 [0, 1, 0], 260 [0, 0, 1] 261 ]; 262 263 // Rotate the scenery around the center of the box, 264 // not around the origin 265 shift = [ 266 [1, 0, 0, 0], 267 [-0.5 * (this.bbox3D[0][0] + this.bbox3D[0][1]), 1, 0, 0], 268 [-0.5 * (this.bbox3D[1][0] + this.bbox3D[1][1]), 0, 1, 0], 269 [-0.5 * (this.bbox3D[2][0] + this.bbox3D[2][1]), 0, 0, 1] 270 ]; 271 272 // matrix3D projects homogeneous 3D coords in the View3D 273 // to homogeneous 2D coordinates in the board 274 this.matrix3D = [ 275 [1, 0, 0, 0], 276 [0, 1, 0, 0], 277 [0, 0, 1, 0] 278 ]; 279 280 this.matrix3D[1][1] = r * Math.cos(a); 281 this.matrix3D[1][2] = -r * Math.sin(a); 282 this.matrix3D[2][1] = f * Math.sin(a); 283 this.matrix3D[2][2] = f * Math.cos(a); 284 this.matrix3D[2][3] = Math.cos(e); 285 286 // Add a second transformation to scale and shift the projection 287 // on the board 288 mat[1][1] = this.size[0] / (this.bbox3D[0][1] - this.bbox3D[0][0]); // w / d_x 289 mat[2][2] = this.size[1] / (this.bbox3D[1][1] - this.bbox3D[1][0]); // h / d_y 290 mat[1][0] = this.llftCorner[0] + mat[1][1] * 0.5 * (this.bbox3D[0][1] - this.bbox3D[0][0]); // llft_x 291 mat[2][0] = this.llftCorner[1] + mat[2][2] * 0.5 * (this.bbox3D[1][1] - this.bbox3D[1][0]); // llft_y 292 293 // Combine the two projections 294 this.matrix3D = Mat.matMatMult(mat, 295 // Mat.matMatMult(shift2, 296 Mat.matMatMult(this.matrix3D, shift) 297 //) 298 ); 299 300 return this; 301 }, 302 303 updateRenderer: function () { 304 this.needsUpdate = false; 305 return this; 306 }, 307 308 removeObject: function(object, saveMethod) { 309 var i; 310 311 // this.board.removeObject(object, saveMethod); 312 if (Type.isArray(object)) { 313 for (i = 0; i < object.length; i++) { 314 this.removeObject(object[i]); 315 } 316 return this; 317 } 318 319 object = this.select(object); 320 321 // // If the object which is about to be removed unknown or a string, do nothing. 322 // // it is a string if a string was given and could not be resolved to an element. 323 if (!Type.exists(object) || Type.isString(object)) { 324 return this; 325 } 326 327 try { 328 // // remove all children. 329 // for (el in object.childElements) { 330 // if (object.childElements.hasOwnProperty(el)) { 331 // object.childElements[el].board.removeObject(object.childElements[el]); 332 // } 333 // } 334 335 delete this.objects[object.id]; 336 } catch (e) { 337 JXG.debug("View3D " + object.id + ": Could not be removed: " + e); 338 } 339 340 // this.update(); 341 342 this.board.removeObject(object, saveMethod); 343 344 return this; 345 }, 346 347 /** 348 * Project 3D coordinates to 2D board coordinates 349 * The 3D coordinates are provides as three numbers x, y, z or one array of length 3. 350 * 351 * @param {Number|Array} x 352 * @param {[Number]} y 353 * @param {[Number]} z 354 * @returns {Array} Array of length 3 containing the projection on to the board 355 * in homogeneous user coordinates. 356 */ 357 project3DTo2D: function (x, y, z) { 358 var vec; 359 if (arguments.length === 3) { 360 vec = [1, x, y, z]; 361 } else { 362 // Argument is an array 363 if (x.length === 3) { 364 vec = [1].concat(x); 365 } else { 366 vec = x; 367 } 368 } 369 return Mat.matVecMult(this.matrix3D, vec); 370 }, 371 372 /** 373 * Project a 2D coordinate to the plane defined by the point foot 374 * and the normal vector `normal`. 375 * 376 * @param {JXG.Point} point2d 377 * @param {Array} normal 378 * @param {Array} foot 379 * @returns {Array} of length 4 containing the projected 380 * point in homogeneous coordinates. 381 */ 382 project2DTo3DPlane: function (point2d, normal, foot) { 383 var mat, 384 rhs, 385 d, 386 le, 387 n = normal.slice(1), 388 sol = [1, 0, 0, 0]; 389 390 foot = foot || [1, 0, 0, 0]; 391 le = Mat.norm(n, 3); 392 d = Mat.innerProduct(foot.slice(1), n, 3) / le; 393 394 mat = this.matrix3D.slice(0, 3); // True copy 395 mat.push([0].concat(n)); 396 397 // 2D coordinates of point: 398 rhs = point2d.coords.usrCoords.concat([d]); 399 try { 400 // Prevent singularity in case elevation angle is zero 401 if (mat[2][3] === 1.0) { 402 mat[2][1] = mat[2][2] = Mat.eps * 0.001; 403 } 404 sol = Mat.Numerics.Gauss(mat, rhs); 405 } catch (err) { 406 sol = [0, NaN, NaN, NaN]; 407 } 408 409 return sol; 410 }, 411 412 /** 413 * Project a 2D coordinate to a new 3D position by keeping 414 * the 3D x, y coordinates and changing only the z coordinate. 415 * All horizontal moves of the 2D point are ignored. 416 * 417 * @param {JXG.Point} point2d 418 * @param {Array} coords3D 419 * @returns {Array} of length 4 containing the projected 420 * point in homogeneous coordinates. 421 */ 422 project2DTo3DVertical: function (point2d, coords3D) { 423 var m3D = this.matrix3D[2], 424 b = m3D[3], 425 rhs = point2d.coords.usrCoords[2]; // y in 2D 426 427 rhs -= m3D[0] * m3D[0] + m3D[1] * coords3D[1] + m3D[2] * coords3D[2]; 428 if (Math.abs(b) < Mat.eps) { 429 return coords3D; // No changes 430 } else { 431 return coords3D.slice(0, 3).concat([rhs / b]); 432 } 433 }, 434 435 /** 436 * Limit 3D coordinates to the bounding cube. 437 * 438 * @param {Array} c3d 3D coordinates [x,y,z] 439 * @returns Array with updated 3D coordinates. 440 */ 441 project3DToCube: function (c3d) { 442 var cube = this.bbox3D; 443 if (c3d[1] < cube[0][0]) { 444 c3d[1] = cube[0][0]; 445 } 446 if (c3d[1] > cube[0][1]) { 447 c3d[1] = cube[0][1]; 448 } 449 if (c3d[2] < cube[1][0]) { 450 c3d[2] = cube[1][0]; 451 } 452 if (c3d[2] > cube[1][1]) { 453 c3d[2] = cube[1][1]; 454 } 455 if (c3d[3] < cube[2][0]) { 456 c3d[3] = cube[2][0]; 457 } 458 if (c3d[3] > cube[2][1]) { 459 c3d[3] = cube[2][1]; 460 } 461 462 return c3d; 463 }, 464 465 /** 466 * Intersect a ray with the bounding cube of the 3D view. 467 * @param {Array} p 3D coordinates [x,y,z] 468 * @param {Array} d 3D direction vector of the line (array of length 3) 469 * @param {Number} r direction of the ray (positive if r > 0, negative if r < 0). 470 * @returns Affine ratio of the intersection of the line with the cube. 471 */ 472 intersectionLineCube: function (p, d, r) { 473 var rnew, i, r0, r1; 474 475 rnew = r; 476 for (i = 0; i < 3; i++) { 477 if (d[i] !== 0) { 478 r0 = (this.bbox3D[i][0] - p[i]) / d[i]; 479 r1 = (this.bbox3D[i][1] - p[i]) / d[i]; 480 if (r < 0) { 481 rnew = Math.max(rnew, Math.min(r0, r1)); 482 } else { 483 rnew = Math.min(rnew, Math.max(r0, r1)); 484 } 485 } 486 } 487 return rnew; 488 }, 489 490 /** 491 * Test if coordinates are inside of the bounding cube. 492 * @param {array} q 3D coordinates [x,y,z] of a point. 493 * @returns Boolean 494 */ 495 isInCube: function (q) { 496 return ( 497 q[0] > this.bbox3D[0][0] - Mat.eps && 498 q[0] < this.bbox3D[0][1] + Mat.eps && 499 q[1] > this.bbox3D[1][0] - Mat.eps && 500 q[1] < this.bbox3D[1][1] + Mat.eps && 501 q[2] > this.bbox3D[2][0] - Mat.eps && 502 q[2] < this.bbox3D[2][1] + Mat.eps 503 ); 504 }, 505 506 /** 507 * 508 * @param {JXG.Plane3D} plane1 509 * @param {JXG.Plane3D} plane2 510 * @param {JXG.Plane3D} d 511 * @returns {Array} of length 2 containing the coordinates of the defining points of 512 * of the intersection segment. 513 */ 514 intersectionPlanePlane: function (plane1, plane2, d) { 515 var ret = [[], []], 516 p, 517 dir, 518 r, 519 q; 520 521 d = d || plane2.d; 522 523 p = Mat.Geometry.meet3Planes( 524 plane1.normal, 525 plane1.d, 526 plane2.normal, 527 d, 528 Mat.crossProduct(plane1.normal, plane2.normal), 529 0 530 ); 531 dir = Mat.Geometry.meetPlanePlane( 532 plane1.vec1, 533 plane1.vec2, 534 plane2.vec1, 535 plane2.vec2 536 ); 537 r = this.intersectionLineCube(p, dir, Infinity); 538 q = Mat.axpy(r, dir, p); 539 if (this.isInCube(q)) { 540 ret[0] = q; 541 } 542 r = this.intersectionLineCube(p, dir, -Infinity); 543 q = Mat.axpy(r, dir, p); 544 if (this.isInCube(q)) { 545 ret[1] = q; 546 } 547 return ret; 548 }, 549 550 /** 551 * Generate mesh for a surface / plane. 552 * Returns array [dataX, dataY] for a JSXGraph curve's updateDataArray function. 553 * @param {Array,Function} func 554 * @param {Array} interval_u 555 * @param {Array} interval_v 556 * @returns Array 557 * @private 558 * 559 * @example 560 * var el = view.create('curve', [[], []]); 561 * el.updateDataArray = function () { 562 * var steps_u = Type.evaluate(this.visProp.stepsu), 563 * steps_v = Type.evaluate(this.visProp.stepsv), 564 * r_u = Type.evaluate(this.range_u), 565 * r_v = Type.evaluate(this.range_v), 566 * func, ret; 567 * 568 * if (this.F !== null) { 569 * func = this.F; 570 * } else { 571 * func = [this.X, this.Y, this.Z]; 572 * } 573 * ret = this.view.getMesh(func, 574 * r_u.concat([steps_u]), 575 * r_v.concat([steps_v])); 576 * 577 * this.dataX = ret[0]; 578 * this.dataY = ret[1]; 579 * }; 580 * 581 */ 582 getMesh: function (func, interval_u, interval_v) { 583 var i_u, i_v, u, v, 584 c2d, delta_u, delta_v, 585 p = [0, 0, 0], 586 steps_u = interval_u[2], 587 steps_v = interval_v[2], 588 dataX = [], 589 dataY = []; 590 591 delta_u = (Type.evaluate(interval_u[1]) - Type.evaluate(interval_u[0])) / steps_u; 592 delta_v = (Type.evaluate(interval_v[1]) - Type.evaluate(interval_v[0])) / steps_v; 593 594 for (i_u = 0; i_u <= steps_u; i_u++) { 595 u = interval_u[0] + delta_u * i_u; 596 for (i_v = 0; i_v <= steps_v; i_v++) { 597 v = interval_v[0] + delta_v * i_v; 598 if (Type.isFunction(func)) { 599 p = func(u, v); 600 } else { 601 p = [func[0](u, v), func[1](u, v), func[2](u, v)]; 602 } 603 c2d = this.project3DTo2D(p); 604 dataX.push(c2d[1]); 605 dataY.push(c2d[2]); 606 } 607 dataX.push(NaN); 608 dataY.push(NaN); 609 } 610 611 for (i_v = 0; i_v <= steps_v; i_v++) { 612 v = interval_v[0] + delta_v * i_v; 613 for (i_u = 0; i_u <= steps_u; i_u++) { 614 u = interval_u[0] + delta_u * i_u; 615 if (Type.isFunction(func)) { 616 p = func(u, v); 617 } else { 618 p = [func[0](u, v), func[1](u, v), func[2](u, v)]; 619 } 620 c2d = this.project3DTo2D(p); 621 dataX.push(c2d[1]); 622 dataY.push(c2d[2]); 623 } 624 dataX.push(NaN); 625 dataY.push(NaN); 626 } 627 628 return [dataX, dataY]; 629 }, 630 631 /** 632 * 633 */ 634 animateAzimuth: function () { 635 var s = this.az_slide._smin, 636 e = this.az_slide._smax, 637 sdiff = e - s, 638 newVal = this.az_slide.Value() + 0.1; 639 640 this.az_slide.position = (newVal - s) / sdiff; 641 if (this.az_slide.position > 1) { 642 this.az_slide.position = 0.0; 643 } 644 this.board.update(); 645 646 this.timeoutAzimuth = setTimeout( 647 function () { 648 this.animateAzimuth(); 649 }.bind(this), 650 200 651 ); 652 }, 653 654 /** 655 * 656 */ 657 stopAzimuth: function () { 658 clearTimeout(this.timeoutAzimuth); 659 this.timeoutAzimuth = null; 660 }, 661 662 /** 663 * Check if vertical dragging is enabled and which action is needed. 664 * Default is shiftKey. 665 * 666 * @returns Boolean 667 * @private 668 */ 669 isVerticalDrag: function () { 670 var b = this.board, 671 key; 672 if (!Type.evaluate(this.visProp.verticaldrag.enabled)) { 673 return false; 674 } 675 key = '_' + Type.evaluate(this.visProp.verticaldrag.key) + 'Key'; 676 return b[key]; 677 } 678 }); 679 680 /** 681 * @class This element creates a 3D view. 682 * @pseudo 683 * @description A View3D element provides the container and the methods to create and display 3D elements. 684 * It is contained in a JSXGraph board. 685 * @name View3D 686 * @augments JXG.View3D 687 * @constructor 688 * @type Object 689 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 690 * @param {Array_Array_Array} lower,dim,cube Here, lower is an array of the form [x, y] and 691 * dim is an array of the form [w, h]. 692 * The arrays [x, y] and [w, h] define the 2D frame into which the 3D cube is 693 * (roughly) projected. If the view's azimuth=0 and elevation=0, the 3D view will cover a rectangle with lower left corner 694 * [x,y] and side lengths [w, h] of the board. 695 * The array 'cube' is of the form [[x1, x2], [y1, y2], [z1, z2]] 696 * which determines the coordinate ranges of the 3D cube. 697 * 698 * @example 699 * var bound = [-5, 5]; 700 * var view = board.create('view3d', 701 * [[-6, -3], 702 * [8, 8], 703 * [bound, bound, bound]], 704 * { 705 * // Main axes 706 * axesPosition: 'center', 707 * xAxis: { strokeColor: 'blue', strokeWidth: 3}, 708 * 709 * // Planes 710 * xPlaneRear: { fillColor: 'yellow', mesh3d: {visible: false}}, 711 * yPlaneFront: { visible: true, fillColor: 'blue'}, 712 * 713 * // Axes on planes 714 * xPlaneRearYAxis: {strokeColor: 'red'}, 715 * xPlaneRearZAxis: {strokeColor: 'red'}, 716 * 717 * yPlaneFrontXAxis: {strokeColor: 'blue'}, 718 * yPlaneFrontZAxis: {strokeColor: 'blue'}, 719 * 720 * zPlaneFrontXAxis: {visible: false}, 721 * zPlaneFrontYAxis: {visible: false} 722 * }); 723 * 724 * </pre><div id="JXGdd06d90e-be5d-4531-8f0b-65fc30b1a7c7" class="jxgbox" style="width: 500px; height: 500px;"></div> 725 * <script type="text/javascript"> 726 * (function() { 727 * var board = JXG.JSXGraph.initBoard('JXGdd06d90e-be5d-4531-8f0b-65fc30b1a7c7', 728 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 729 * var bound = [-5, 5]; 730 * var view = board.create('view3d', 731 * [[-6, -3], [8, 8], 732 * [bound, bound, bound]], 733 * { 734 * // Main axes 735 * axesPosition: 'center', 736 * xAxis: { strokeColor: 'blue', strokeWidth: 3}, 737 * // Planes 738 * xPlaneRear: { fillColor: 'yellow', mesh3d: {visible: false}}, 739 * yPlaneFront: { visible: true, fillColor: 'blue'}, 740 * // Axes on planes 741 * xPlaneRearYAxis: {strokeColor: 'red'}, 742 * xPlaneRearZAxis: {strokeColor: 'red'}, 743 * yPlaneFrontXAxis: {strokeColor: 'blue'}, 744 * yPlaneFrontZAxis: {strokeColor: 'blue'}, 745 * zPlaneFrontXAxis: {visible: false}, 746 * zPlaneFrontYAxis: {visible: false} 747 * }); 748 * })(); 749 * 750 * </script><pre> 751 * 752 */ 753 JXG.createView3D = function (board, parents, attributes) { 754 var view, attr, 755 x, y, w, h, 756 coords = parents[0], // llft corner 757 size = parents[1]; // [w, h] 758 759 attr = Type.copyAttributes(attributes, board.options, "view3d"); 760 view = new JXG.View3D(board, parents, attr); 761 view.defaultAxes = view.create("axes3d", parents, attributes); 762 763 x = coords[0]; 764 y = coords[1]; 765 w = size[0]; 766 h = size[1]; 767 768 /** 769 * Slider to adapt azimuth angle 770 * @name JXG.View3D#az_slide 771 * @type {Slider} 772 */ 773 view.az_slide = board.create( 774 "slider", 775 [ 776 [x - 1, y - 2], 777 [x + w + 1, y - 2], 778 [0, 1.0, 2 * Math.PI] 779 ], 780 { 781 style: 6, 782 name: "az", 783 point1: { frozen: true }, 784 point2: { frozen: true } 785 } 786 ); 787 788 /** 789 * Slider to adapt elevation angle 790 * 791 * @name JXG.View3D#el_slide 792 * @type {Slider} 793 */ 794 view.el_slide = board.create( 795 "slider", 796 [ 797 [x - 1, y], 798 [x - 1, y + h], 799 [0, 0.3, Math.PI / 2] 800 ], 801 { 802 style: 6, 803 name: "el", 804 point1: { frozen: true }, 805 point2: { frozen: true } 806 } 807 ); 808 809 view.board.highlightInfobox = function (x, y, el) { 810 var d, i, c3d, foot, 811 pre = '<span style="color:black; font-size:200%">\u21C4 </span>', 812 brd = el.board, 813 arr, infobox, 814 p = null; 815 816 if (view.isVerticalDrag()) { 817 pre = '<span style="color:black; font-size:200%">\u21C5 </span>'; 818 } 819 // Search 3D parent 820 for (i = 0; i < el.parents.length; i++) { 821 p = brd.objects[el.parents[i]]; 822 if (p.is3D) { 823 break; 824 } 825 } 826 if (p) { 827 foot = [1, 0, 0, p.coords[3]]; 828 c3d = view.project2DTo3DPlane(p.element2D, [1, 0, 0, 1], foot); 829 if (!view.isInCube(c3d)) { 830 view.board.highlightCustomInfobox('', p); 831 return; 832 } 833 d = Type.evaluate(p.visProp.infoboxdigits); 834 infobox = view.board.infobox; 835 console.log(infobox.useLocale()) 836 if (d === 'auto') { 837 if (infobox.useLocale()) { 838 arr = [pre, '(', infobox.formatNumberLocale(p.X()), ' | ', infobox.formatNumberLocale(p.Y()), ' | ', infobox.formatNumberLocale(p.Z()), ')'] 839 } else { 840 arr = [pre, '(', Type.autoDigits(p.X()), ' | ', Type.autoDigits(p.Y()), ' | ', Type.autoDigits(p.Z()), ')'] 841 } 842 843 } else { 844 if (infobox.useLocale()) { 845 arr = [pre, '(', infobox.formatNumberLocale(p.X(), d), ' | ', infobox.formatNumberLocale(p.Y(), d), ' | ', infobox.formatNumberLocale(p.Z(), d), ')']; 846 } else { 847 arr = [pre, '(', Type.toFixed(p.X(), d), ' | ', Type.toFixed(p.Y(), d), ' | ', Type.toFixed(p.Z(), d), ')']; 848 } 849 } 850 view.board.highlightCustomInfobox(arr.join(''), p); 851 } else { 852 view.board.highlightCustomInfobox('(' + x + ', ' + y + ')', el); 853 } 854 }; 855 856 view.board.update(); 857 858 return view; 859 }; 860 861 JXG.registerElement("view3d", JXG.createView3D); 862 863 export default JXG.View3D; 864