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