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