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 import JXG from "../jxg.js"; 32 import Const from "../base/constants.js"; 33 import Mat from "../math/math.js"; 34 import Geometry from "../math/geometry.js"; 35 import Type from "../utils/type.js"; 36 //, GeometryElement3D) { 37 38 /** 39 * A 3D point is the basic geometric element. 40 * @class Creates a new 3D point object. Do not use this constructor to create a 3D point. Use {@link JXG.View3D#create} with 41 * type {@link Point3D} instead. 42 * @augments JXG.GeometryElement3D 43 * @augments JXG.GeometryElement 44 * @param {JXG.View3D} view The 3D view the point is drawn on. 45 * @param {Function|Array} F Array of numbers, array of functions or function returning an array with defines the user coordinates of the point. 46 * @param {JXG.GeometryElement3D} slide Object the 3D point should be bound to. If null, the point is a free point. 47 * @param {Object} attributes An object containing visual properties like in {@link JXG.Options#point3d} and 48 * {@link JXG.Options#elements}, and optional a name and an id. 49 * @see JXG.Board#generateName 50 */ 51 JXG.Point3D = function (view, F, slide, attributes) { 52 this.constructor(view.board, attributes, Const.OBJECT_TYPE_POINT3D, Const.OBJECT_CLASS_3D); 53 this.constructor3D(view, "point3d"); 54 55 this.board.finalizeAdding(this); 56 57 // add the new point to its view's point list 58 if (view.visProp.depthorderpoints) { 59 view.points.push(this); 60 } 61 62 /** 63 * Homogeneous coordinates of a Point3D, i.e. array of length 4: [w, x, y, z]. Usually, w=1 for finite points and w=0 for points 64 * which are infinitely far. 65 * 66 * @example 67 * p.coords; 68 * 69 * @name Point3D#coords 70 * @type Array 71 * @private 72 */ 73 this.coords = [0, 0, 0, 0]; 74 75 /** 76 * Function or array of functions or array of numbers defining the coordinates of the point, used in {@link updateCoords}. 77 * 78 * @name Point3D#F 79 * @function 80 * @private 81 * 82 * @see updateCoords 83 */ 84 this.F = F; 85 86 /** 87 * Optional slide element, i.e. element the Point3D lives on. 88 * 89 * @example 90 * p.slide; 91 * 92 * @name Point3D#slide 93 * @type JXG.GeometryElement3D 94 * @default null 95 * @private 96 * 97 */ 98 this.slide = slide; 99 100 /** 101 * Get x-coordinate of a 3D point. 102 * 103 * @name X 104 * @memberOf Point3D 105 * @function 106 * @returns {Number} 107 * 108 * @example 109 * p.X(); 110 */ 111 this.X = function () { 112 return this.coords[1]; 113 }; 114 115 /** 116 * Get y-coordinate of a 3D point. 117 * 118 * @name Y 119 * @memberOf Point3D 120 * @function 121 * @returns Number 122 * 123 * @example 124 * p.Y(); 125 */ 126 this.Y = function () { 127 return this.coords[2]; 128 }; 129 130 /** 131 * Get z-coordinate of a 3D point. 132 * 133 * @name Z 134 * @memberOf Point3D 135 * @function 136 * @returns Number 137 * 138 * @example 139 * p.Z(); 140 */ 141 this.Z = function () { 142 return this.coords[3]; 143 }; 144 145 /** 146 * Store the last position of the 2D point for the optimizer. 147 * 148 * @type Array 149 * @private 150 */ 151 this._params = []; 152 153 this._c2d = null; 154 155 this.methodMap = Type.deepCopy(this.methodMap, { 156 // TODO 157 }); 158 }; 159 JXG.Point3D.prototype = new JXG.GeometryElement(); 160 Type.copyPrototypeMethods(JXG.Point3D, JXG.GeometryElement3D, "constructor3D"); 161 162 JXG.extend( 163 JXG.Point3D.prototype, 164 /** @lends JXG.Point3D.prototype */ { 165 /** 166 * Update the homogeneous coords array. 167 * 168 * @name updateCoords 169 * @memberOf Point3D 170 * @function 171 * @returns {Object} Reference to the Point3D object 172 * @private 173 * @example 174 * p.updateCoords(); 175 */ 176 updateCoords: function () { 177 var i; 178 179 if (Type.isFunction(this.F)) { 180 // this.coords = [1].concat(Type.evaluate(this.F)); 181 this.coords = Type.evaluate(this.F); 182 this.coords.unshift(1); 183 } else { 184 this.coords[0] = 1; 185 for (i = 0; i < 3; i++) { 186 // Attention: if F is array of numbers, coords are not updated. 187 // Otherwise, dragging will not work anymore. 188 if (Type.isFunction(this.F[i])) { 189 this.coords[i + 1] = Type.evaluate(this.F[i]); 190 } 191 } 192 } 193 return this; 194 }, 195 196 /** 197 * Initialize the coords array. 198 * 199 * @private 200 * @returns {Object} Reference to the Point3D object 201 */ 202 initCoords: function () { 203 var i; 204 205 if (Type.isFunction(this.F)) { 206 // this.coords = [1].concat(Type.evaluate(this.F)); 207 this.coords = Type.evaluate(this.F); 208 this.coords.unshift(1); 209 } else { 210 this.coords[0] = 1; 211 for (i = 0; i < 3; i++) { 212 this.coords[i + 1] = Type.evaluate(this.F[i]); 213 } 214 } 215 return this; 216 }, 217 218 /** 219 * Normalize homogeneous coordinates such the the first coordinate (the w-coordinate is equal to 1 or 0)- 220 * 221 * @name normalizeCoords 222 * @memberOf Point3D 223 * @function 224 * @returns {Object} Reference to the Point3D object 225 * @private 226 * @example 227 * p.normalizeCoords(); 228 */ 229 normalizeCoords: function () { 230 if (Math.abs(this.coords[0]) > Mat.eps) { 231 this.coords[1] /= this.coords[0]; 232 this.coords[2] /= this.coords[0]; 233 this.coords[3] /= this.coords[0]; 234 this.coords[0] = 1.0; 235 } 236 return this; 237 }, 238 239 /** 240 * Set the position of a 3D point. 241 * 242 * @name setPosition 243 * @memberOf Point3D 244 * @function 245 * @param {Array} coords 3D coordinates. Either of the form [x,y,z] (Euclidean) or [w,x,y,z] (homogeneous). 246 * @param {Boolean} [noevent] If true, no events are triggered. 247 * @returns {Object} Reference to the Point3D object 248 * 249 * @example 250 * p.setPosition([1, 3, 4]); 251 */ 252 setPosition: function (coords, noevent) { 253 var c = this.coords; 254 // oc = this.coords.slice(); // Copy of original values 255 256 if (coords.length === 3) { 257 // Euclidean coordinates 258 c[0] = 1.0; 259 c[1] = coords[0]; 260 c[2] = coords[1]; 261 c[3] = coords[2]; 262 } else { 263 // Homogeneous coordinates (normalized) 264 c[0] = coords[0]; 265 c[1] = coords[1]; 266 c[2] = coords[2]; 267 c[3] = coords[2]; 268 this.normalizeCoords(); 269 } 270 271 // console.log(el.emitter, !noevent, oc[0] !== c[0] || oc[1] !== c[1] || oc[2] !== c[2] || oc[3] !== c[3]); 272 // Not yet working TODO 273 // if (el.emitter && !noevent && 274 // (oc[0] !== c[0] || oc[1] !== c[1] || oc[2] !== c[2] || oc[3] !== c[3])) { 275 // this.triggerEventHandlers(['update3D'], [oc]); 276 // } 277 return this; 278 }, 279 280 update: function (drag) { 281 var c3d, foot, res; 282 283 // Update is called from board.updateElements 284 if ( 285 this.element2D.draggable() && 286 Geometry.distance(this._c2d, this.element2D.coords.usrCoords) !== 0 287 ) { 288 289 // 290 if (this.slide) { 291 this.coords = this.slide.projectScreenCoords( 292 [this.element2D.X(), this.element2D.Y()], 293 this._params 294 ); 295 this.element2D.coords.setCoordinates( 296 Const.COORDS_BY_USER, 297 this.view.project3DTo2D(this.coords) 298 ); 299 } else { 300 if (this.view.isVerticalDrag()) { 301 // Drag the point in its vertical to the xy plane 302 // If the point is outside of bbox3d, 303 // c3d is already corrected. 304 c3d = this.view.project2DTo3DVertical(this.element2D, this.coords); 305 } else { 306 // Drag the point in its xy plane 307 foot = [1, 0, 0, this.coords[3]]; 308 c3d = this.view.project2DTo3DPlane(this.element2D, [1, 0, 0, 1], foot); 309 } 310 311 if (c3d[0] !== 0) { 312 // Check if c3d is inside of view.bbox3d 313 // Otherwise, the coords have to be corrected below. 314 res = this.view.project3DToCube(c3d); 315 this.coords = res[0]; 316 317 if (res[1]) { 318 // The 3D coordinates have been corrected, now 319 // also correct the 2D element. 320 this.element2D.coords.setCoordinates( 321 Const.COORDS_BY_USER, 322 this.view.project3DTo2D(this.coords) 323 ); 324 } 325 } 326 } 327 } else { 328 // Update 2D point from its 3D view 329 this.updateCoords(); 330 if (this.slide) { 331 this.coords = this.slide.projectCoords( 332 [this.X(), this.Y(), this.Z()], 333 this._params 334 ); 335 } 336 this.element2D.coords.setCoordinates( 337 Const.COORDS_BY_USER, 338 this.view.project3DTo2D([1, this.X(), this.Y(), this.Z()]) 339 ); 340 } 341 this._c2d = this.element2D.coords.usrCoords.slice(); 342 343 return this; 344 }, 345 346 updateRenderer: function () { 347 this.needsUpdate = false; 348 return this; 349 }, 350 351 /** 352 * Check whether a point's homogeneous coordinate vector is zero. 353 * @returns {Boolean} True if the coordinate vector is zero; false otherwise. 354 */ 355 isIllDefined: function () { 356 return Type.cmpArrays(this.coords, [0, 0, 0, 0]); 357 }, 358 359 /** 360 * Calculate the distance from one point to another. If one of the points is on the plane at infinity, return positive infinity. 361 * @param {JXG.Point3D} pt The point to which the distance is calculated. 362 * @returns {Number} The distance 363 */ 364 distance: function (pt) { 365 var eps_sq = Mat.eps * Mat.eps, 366 c_this = this.coords, 367 c_pt = pt.coords; 368 369 if (c_this[0] * c_this[0] > eps_sq && c_pt[0] * c_pt[0] > eps_sq) { 370 return Mat.hypot( 371 c_pt[1] - c_this[1], 372 c_pt[2] - c_this[2], 373 c_pt[3] - c_this[3] 374 ); 375 } else { 376 return Number.POSITIVE_INFINITY; 377 } 378 }, 379 380 // Not yet working 381 __evt__update3D: function (oc) {} 382 } 383 ); 384 385 /** 386 * @class This element is used to provide a constructor for a 3D Point. 387 * @pseudo 388 * @description A Point3D object is defined by 3 coordinates [x,y,z]. 389 * <p> 390 * All numbers can also be provided as functions returning a number. 391 * 392 * @name Point3D 393 * @augments JXG.Point3D 394 * @constructor 395 * @throws {Exception} If the element cannot be constructed with the given parent 396 * objects an exception is thrown. 397 * @param {number,function_number,function_number,function} x,y,z The coordinates are given as x, y, z consisting of numbers of functions. 398 * @param {array,function} F Alternatively, the coordinates can be supplied as 399 * <ul> 400 * <li>array arr=[x,y,z] of length 3 consisting of numbers or 401 * <li>function returning an array [x,y,z] of length 3 of numbers. 402 * </ul> 403 * 404 * @example 405 * var bound = [-5, 5]; 406 * var view = board.create('view3d', 407 * [[-6, -3], [8, 8], 408 * [bound, bound, bound]], 409 * {}); 410 * var p = view.create('point3d', [1, 2, 2], { name:'A', size: 5 }); 411 * var q = view.create('point3d', function() { return [p.X(), p.Y(), p.Z() - 3]; }, { name:'B', size: 5, fixed: true }); 412 * 413 * </pre><div id="JXGb9ee8f9f-3d2b-4f73-8221-4f82c09933f1" class="jxgbox" style="width: 300px; height: 300px;"></div> 414 * <script type="text/javascript"> 415 * (function() { 416 * var board = JXG.JSXGraph.initBoard('JXGb9ee8f9f-3d2b-4f73-8221-4f82c09933f1', 417 * {boundingbox: [-8, 8, 8,-8], axis: false, pan: {enabled: false}, showcopyright: false, shownavigation: false}); 418 * var bound = [-5, 5]; 419 * var view = board.create('view3d', 420 * [[-6, -3], [8, 8], 421 * [bound, bound, bound]], 422 * {}); 423 * var p = view.create('point3d', [1, 2, 2], { name:'A', size: 5 }); 424 * var q = view.create('point3d', function() { return [p.X(), p.Y(), p.Z() - 3]; }, { name:'B', size: 5 }); 425 * })(); 426 * 427 * </script><pre> 428 * 429 */ 430 JXG.createPoint3D = function (board, parents, attributes) { 431 // parents[0]: view 432 // followed by 433 // parents[1]: function or array 434 // or 435 // parents[1..3]: coordinates 436 437 var view = parents[0], 438 attr, F, slide, c2d, el; 439 440 // If the last element of parents is a 3D object, 441 // the point is a glider on that element. 442 if (parents.length > 2 && Type.exists(parents[parents.length - 1].is3D)) { 443 slide = parents.pop(); 444 } else { 445 slide = null; 446 } 447 448 if (parents.length === 2) { 449 // [view, array|fun] (Array [x, y, z] | function) returning [x, y, z] 450 F = parents[1]; 451 } else if (parents.length === 4) { 452 // [view, x, y, z], (3 numbers | functions) 453 F = parents.slice(1); 454 } else { 455 throw new Error( 456 "JSXGraph: Can't create point3d with parent types '" + 457 typeof parents[0] + 458 "' and '" + 459 typeof parents[1] + 460 "'." + 461 "\nPossible parent types: [[x,y,z]], [x,y,z]" 462 ); 463 // "\nPossible parent types: [[x,y,z]], [x,y,z], [element,transformation]"); // TODO 464 } 465 466 attr = Type.copyAttributes(attributes, board.options, 'point3d'); 467 el = new JXG.Point3D(view, F, slide, attr); 468 el.initCoords(); 469 470 c2d = view.project3DTo2D(el.coords); 471 472 attr = el.setAttr2D(attr); 473 el.element2D = view.create('point', c2d, attr); 474 el.element2D.view = view; 475 el.addChild(el.element2D); 476 el.inherits.push(el.element2D); 477 el.element2D.setParents(el); 478 479 // if this point is a glider, record that in the update tree 480 if (el.slide) { 481 el.slide.addChild(el); 482 el.setParents(el.slide); 483 } 484 485 el._c2d = el.element2D.coords.usrCoords.slice(); // Store a copy of the coordinates to detect dragging 486 487 return el; 488 }; 489 490 JXG.registerElement("point3d", JXG.createPoint3D); 491