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