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, 134 /** @lends JXG.View3D.prototype */ { 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; 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 this.matrix3D = [ 264 [1, 0, 0, 0], 265 [0, 1, 0, 0], 266 [0, 0, 1, 0] 267 ]; 268 269 this.matrix3D[1][1] = r * Math.cos(a); 270 this.matrix3D[1][2] = -r * Math.sin(a); 271 this.matrix3D[2][1] = f * Math.sin(a); 272 this.matrix3D[2][2] = f * Math.cos(a); 273 this.matrix3D[2][3] = Math.cos(e); 274 275 mat[1][1] = this.size[0] / (this.bbox3D[0][1] - this.bbox3D[0][0]); // w / d_x 276 mat[2][2] = this.size[1] / (this.bbox3D[1][1] - this.bbox3D[1][0]); // h / d_y 277 mat[1][0] = this.llftCorner[0] - mat[1][1] * this.bbox3D[0][0]; // llft_x 278 mat[2][0] = this.llftCorner[1] - mat[2][2] * this.bbox3D[1][0]; // llft_y 279 this.matrix3D = Mat.matMatMult(mat, this.matrix3D); 280 281 return this; 282 }, 283 284 updateRenderer: function () { 285 this.needsUpdate = false; 286 return this; 287 }, 288 289 /** 290 * Project 3D coordinates to 2D board coordinates 291 * The 3D coordinates are provides as three numbers x, y, z or one array of length 3. 292 * 293 * @param {Number|Array} x 294 * @param {[Number]} y 295 * @param {[Number]} z 296 * @returns {Array} Array of length 3 containing the projection on to the board 297 * in homogeneous user coordinates. 298 */ 299 project3DTo2D: function (x, y, z) { 300 var vec; 301 if (arguments.length === 3) { 302 vec = [1, x, y, z]; 303 } else { 304 // Argument is an array 305 if (x.length === 3) { 306 vec = [1].concat(x); 307 } else { 308 vec = x; 309 } 310 } 311 return Mat.matVecMult(this.matrix3D, vec); 312 }, 313 314 /** 315 * Project a 2D coordinate to the plane defined by the point foot 316 * and the normal vector `normal`. 317 * 318 * @param {JXG.Point} point 319 * @param {Array} normal 320 * @param {Array} foot 321 * @returns {Array} of length 4 containing the projected 322 * point in homogeneous coordinates. 323 */ 324 project2DTo3DPlane: function (point2d, normal, foot) { 325 var mat, 326 rhs, 327 d, 328 le, 329 n = normal.slice(1), 330 sol = [1, 0, 0, 0]; 331 332 foot = foot || [1, 0, 0, 0]; 333 le = Mat.norm(n, 3); 334 d = Mat.innerProduct(foot.slice(1), n, 3) / le; 335 336 mat = this.matrix3D.slice(0, 3); // True copy 337 mat.push([0].concat(n)); 338 339 // 2D coordinates of point: 340 rhs = point2d.coords.usrCoords.concat([d]); 341 try { 342 // Prevent singularity in case elevation angle is zero 343 if (mat[2][3] === 1.0) { 344 mat[2][1] = mat[2][2] = Mat.eps * 0.001; 345 } 346 sol = Mat.Numerics.Gauss(mat, rhs); 347 } catch (err) { 348 sol = [0, NaN, NaN, NaN]; 349 } 350 351 return sol; 352 }, 353 354 /** 355 * Limit 3D coordinates to the bounding cube. 356 * 357 * @param {Array} c3d 3D coordinates [x,y,z] 358 * @returns Array with updated 3D coordinates. 359 */ 360 project3DToCube: function (c3d) { 361 var cube = this.bbox3D; 362 if (c3d[1] < cube[0][0]) { 363 c3d[1] = cube[0][0]; 364 } 365 if (c3d[1] > cube[0][1]) { 366 c3d[1] = cube[0][1]; 367 } 368 if (c3d[2] < cube[1][0]) { 369 c3d[2] = cube[1][0]; 370 } 371 if (c3d[2] > cube[1][1]) { 372 c3d[2] = cube[1][1]; 373 } 374 if (c3d[3] < cube[2][0]) { 375 c3d[3] = cube[2][0]; 376 } 377 if (c3d[3] > cube[2][1]) { 378 c3d[3] = cube[2][1]; 379 } 380 381 return c3d; 382 }, 383 384 /** 385 * Intersect a ray with the bounding cube of the 3D view. 386 * @param {Array} p 3D coordinates [x,y,z] 387 * @param {Array} d 3D direction vector of the line (array of length 3) 388 * @param {Number} r direction of the ray (positive if r > 0, negative if r < 0). 389 * @returns Affine ratio of the intersection of the line with the cube. 390 */ 391 intersectionLineCube: function (p, d, r) { 392 var rnew, i, r0, r1; 393 394 rnew = r; 395 for (i = 0; i < 3; i++) { 396 if (d[i] !== 0) { 397 r0 = (this.bbox3D[i][0] - p[i]) / d[i]; 398 r1 = (this.bbox3D[i][1] - p[i]) / d[i]; 399 if (r < 0) { 400 rnew = Math.max(rnew, Math.min(r0, r1)); 401 } else { 402 rnew = Math.min(rnew, Math.max(r0, r1)); 403 } 404 } 405 } 406 return rnew; 407 }, 408 409 /** 410 * Test if coordinates are inside of the bounding cube. 411 * @param {array} q 3D coordinates [x,y,z] of a point. 412 * @returns Boolean 413 */ 414 isInCube: function (q) { 415 return ( 416 q[0] > this.bbox3D[0][0] - Mat.eps && 417 q[0] < this.bbox3D[0][1] + Mat.eps && 418 q[1] > this.bbox3D[1][0] - Mat.eps && 419 q[1] < this.bbox3D[1][1] + Mat.eps && 420 q[2] > this.bbox3D[2][0] - Mat.eps && 421 q[2] < this.bbox3D[2][1] + Mat.eps 422 ); 423 }, 424 425 /** 426 * 427 * @param {JXG.Plane3D} plane1 428 * @param {JXG.Plane3D} plane2 429 * @param {JXG.Plane3D} d 430 * @returns {Array} of length 2 containing the coordinates of the defining points of 431 * of the intersection segment. 432 */ 433 intersectionPlanePlane: function (plane1, plane2, d) { 434 var ret = [[], []], 435 p, 436 dir, 437 r, 438 q; 439 440 d = d || plane2.d; 441 442 p = Mat.Geometry.meet3Planes( 443 plane1.normal, 444 plane1.d, 445 plane2.normal, 446 d, 447 Mat.crossProduct(plane1.normal, plane2.normal), 448 0 449 ); 450 dir = Mat.Geometry.meetPlanePlane( 451 plane1.vec1, 452 plane1.vec2, 453 plane2.vec1, 454 plane2.vec2 455 ); 456 r = this.intersectionLineCube(p, dir, Infinity); 457 q = Mat.axpy(r, dir, p); 458 if (this.isInCube(q)) { 459 ret[0] = q; 460 } 461 r = this.intersectionLineCube(p, dir, -Infinity); 462 q = Mat.axpy(r, dir, p); 463 if (this.isInCube(q)) { 464 ret[1] = q; 465 } 466 return ret; 467 }, 468 469 /** 470 * Generate mesh for a surface / plane. 471 * Returns array [dataX, dataY] for a JSXGraph curve's updateDataArray function. 472 * @param {Array,Function} func 473 * @param {Array} interval_u 474 * @param {Array} interval_v 475 * @returns Array 476 * @private 477 * 478 * @example 479 * var el = view.create('curve', [[], []]); 480 * el.updateDataArray = function () { 481 * var steps_u = Type.evaluate(this.visProp.stepsu), 482 * steps_v = Type.evaluate(this.visProp.stepsv), 483 * r_u = Type.evaluate(this.range_u), 484 * r_v = Type.evaluate(this.range_v), 485 * func, ret; 486 * 487 * if (this.F !== null) { 488 * func = this.F; 489 * } else { 490 * func = [this.X, this.Y, this.Z]; 491 * } 492 * ret = this.view.getMesh(func, 493 * r_u.concat([steps_u]), 494 * r_v.concat([steps_v])); 495 * 496 * this.dataX = ret[0]; 497 * this.dataY = ret[1]; 498 * }; 499 * 500 */ 501 getMesh: function (func, interval_u, interval_v) { 502 var i_u, 503 i_v, 504 u, 505 v, 506 c2d, 507 delta_u, 508 delta_v, 509 p = [0, 0, 0], 510 steps_u = interval_u[2], 511 steps_v = interval_v[2], 512 dataX = [], 513 dataY = []; 514 515 delta_u = (Type.evaluate(interval_u[1]) - Type.evaluate(interval_u[0])) / steps_u; 516 delta_v = (Type.evaluate(interval_v[1]) - Type.evaluate(interval_v[0])) / steps_v; 517 518 for (i_u = 0; i_u <= steps_u; i_u++) { 519 u = interval_u[0] + delta_u * i_u; 520 for (i_v = 0; i_v <= steps_v; i_v++) { 521 v = interval_v[0] + delta_v * i_v; 522 if (Type.isFunction(func)) { 523 p = func(u, v); 524 } else { 525 p = [func[0](u, v), func[1](u, v), func[2](u, v)]; 526 } 527 c2d = this.project3DTo2D(p); 528 dataX.push(c2d[1]); 529 dataY.push(c2d[2]); 530 } 531 dataX.push(NaN); 532 dataY.push(NaN); 533 } 534 535 for (i_v = 0; i_v <= steps_v; i_v++) { 536 v = interval_v[0] + delta_v * i_v; 537 for (i_u = 0; i_u <= steps_u; i_u++) { 538 u = interval_u[0] + delta_u * i_u; 539 if (Type.isFunction(func)) { 540 p = func(u, v); 541 } else { 542 p = [func[0](u, v), func[1](u, v), func[2](u, v)]; 543 } 544 c2d = this.project3DTo2D(p); 545 dataX.push(c2d[1]); 546 dataY.push(c2d[2]); 547 } 548 dataX.push(NaN); 549 dataY.push(NaN); 550 } 551 552 return [dataX, dataY]; 553 }, 554 555 /** 556 * 557 */ 558 animateAzimuth: function () { 559 var s = this.az_slide._smin, 560 e = this.az_slide._smax, 561 sdiff = e - s, 562 newVal = this.az_slide.Value() + 0.1; 563 564 this.az_slide.position = (newVal - s) / sdiff; 565 if (this.az_slide.position > 1) { 566 this.az_slide.position = 0.0; 567 } 568 this.board.update(); 569 570 this.timeoutAzimuth = setTimeout( 571 function () { 572 this.animateAzimuth(); 573 }.bind(this), 574 200 575 ); 576 }, 577 578 /** 579 * 580 */ 581 stopAzimuth: function () { 582 clearTimeout(this.timeoutAzimuth); 583 this.timeoutAzimuth = null; 584 } 585 } 586 ); 587 588 /** 589 * @class This element creates a 3D view. 590 * @pseudo 591 * @description A View3D element provides the container and the methods to create and display 3D elements. 592 * It is contained in a JSXGraph board. 593 * @name View3D 594 * @augments JXG.View3D 595 * @constructor 596 * @type Object 597 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 598 * @param {Array_Array_Array} lower,dim,cube Here, lower is an array of the form [x, y] and 599 * dim is an array of the form [w, h]. 600 * The arrays [x, y] and [w, h] define the 2D frame into which the 3D cube is 601 * (roughly) projected. If the view's azimuth=0 and elevation=0, the 3D view will cover a rectangle with lower left corner 602 * [x,y] and side lengths [w, h] of the board. 603 * The array 'cube' is of the form [[x1, x2], [y1, y2], [z1, z2]] 604 * which determines the coordinate ranges of the 3D cube. 605 * 606 * @example 607 * var bound = [-5, 5]; 608 * var view = board.create('view3d', 609 * [[-6, -3], 610 * [8, 8], 611 * [bound, bound, bound]], 612 * { 613 * // Main axes 614 * axesPosition: 'center', 615 * xAxis: { strokeColor: 'blue', strokeWidth: 3}, 616 * 617 * // Planes 618 * xPlaneRear: { fillColor: 'yellow', mesh3d: {visible: false}}, 619 * yPlaneFront: { visible: true, fillColor: 'blue'}, 620 * 621 * // Axes on planes 622 * xPlaneRearYAxis: {strokeColor: 'red'}, 623 * xPlaneRearZAxis: {strokeColor: 'red'}, 624 * 625 * yPlaneFrontXAxis: {strokeColor: 'blue'}, 626 * yPlaneFrontZAxis: {strokeColor: 'blue'}, 627 * 628 * zPlaneFrontXAxis: {visible: false}, 629 * zPlaneFrontYAxis: {visible: false} 630 * }); 631 * 632 * </pre><div id="JXGdd06d90e-be5d-4531-8f0b-65fc30b1a7c7" class="jxgbox" style="width: 500px; height: 500px;"></div> 633 * <script type="text/javascript"> 634 * (function() { 635 * var board = JXG.JSXGraph.initBoard('JXGdd06d90e-be5d-4531-8f0b-65fc30b1a7c7', 636 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 637 * var bound = [-5, 5]; 638 * var view = board.create('view3d', 639 * [[-6, -3], [8, 8], 640 * [bound, bound, bound]], 641 * { 642 * // Main axes 643 * axesPosition: 'center', 644 * xAxis: { strokeColor: 'blue', strokeWidth: 3}, 645 * // Planes 646 * xPlaneRear: { fillColor: 'yellow', mesh3d: {visible: false}}, 647 * yPlaneFront: { visible: true, fillColor: 'blue'}, 648 * // Axes on planes 649 * xPlaneRearYAxis: {strokeColor: 'red'}, 650 * xPlaneRearZAxis: {strokeColor: 'red'}, 651 * yPlaneFrontXAxis: {strokeColor: 'blue'}, 652 * yPlaneFrontZAxis: {strokeColor: 'blue'}, 653 * zPlaneFrontXAxis: {visible: false}, 654 * zPlaneFrontYAxis: {visible: false} 655 * }); 656 * })(); 657 * 658 * </script><pre> 659 * 660 */ 661 JXG.createView3D = function (board, parents, attributes) { 662 var view, 663 attr, 664 x, 665 y, 666 w, 667 h, 668 coords = parents[0], // llft corner 669 size = parents[1]; // [w, h] 670 671 attr = Type.copyAttributes(attributes, board.options, "view3d"); 672 view = new JXG.View3D(board, parents, attr); 673 view.defaultAxes = view.create("axes3d", parents, attributes); 674 675 x = coords[0]; 676 y = coords[1]; 677 w = size[0]; 678 h = size[1]; 679 680 /** 681 * Slider to adapt azimuth angle 682 * @name JXG.View3D#az_slide 683 * @type {Slider} 684 */ 685 view.az_slide = board.create( 686 "slider", 687 [ 688 [x - 1, y - 2], 689 [x + w + 1, y - 2], 690 [0, 1.0, 2 * Math.PI] 691 ], 692 { 693 style: 6, 694 name: "az", 695 point1: { frozen: true }, 696 point2: { frozen: true } 697 } 698 ); 699 700 /** 701 * Slider to adapt elevation angle 702 * 703 * @name JXG.View3D#el_slide 704 * @type {Slider} 705 */ 706 view.el_slide = board.create( 707 "slider", 708 [ 709 [x - 1, y], 710 [x - 1, y + h], 711 [0, 0.3, Math.PI / 2] 712 ], 713 { 714 style: 6, 715 name: "el", 716 point1: { frozen: true }, 717 point2: { frozen: true } 718 } 719 ); 720 721 view.board.highlightInfobox = function (x, y, el) { 722 var d, 723 i, 724 c3d, 725 foot, 726 brd = el.board, 727 p = null; 728 729 // Search 3D parent 730 for (i = 0; i < el.parents.length; i++) { 731 p = brd.objects[el.parents[i]]; 732 if (p.is3D) { 733 break; 734 } 735 } 736 if (p) { 737 foot = [1, 0, 0, p.coords[3]]; 738 c3d = view.project2DTo3DPlane(p.element2D, [1, 0, 0, 1], foot); 739 if (!view.isInCube(c3d)) { 740 view.board.highlightCustomInfobox("", p); 741 return; 742 } 743 d = Type.evaluate(p.visProp.infoboxdigits); 744 if (d === "auto") { 745 view.board.highlightCustomInfobox( 746 "(" + 747 Type.autoDigits(p.X()) + 748 " | " + 749 Type.autoDigits(p.Y()) + 750 " | " + 751 Type.autoDigits(p.Z()) + 752 ")", 753 p 754 ); 755 } else { 756 view.board.highlightCustomInfobox( 757 "(" + 758 Type.toFixed(p.X(), d) + 759 " | " + 760 Type.toFixed(p.Y(), d) + 761 " | " + 762 Type.toFixed(p.Z(), d) + 763 ")", 764 p 765 ); 766 } 767 } else { 768 view.board.highlightCustomInfobox("(" + x + ", " + y + ")", el); 769 } 770 }; 771 772 view.board.update(); 773 774 return view; 775 }; 776 JXG.registerElement("view3d", JXG.createView3D); 777 778 export default JXG.View3D; 779