1 /*
  2     Copyright 2008-2021
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
 29     and <http://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 
 33 /*global JXG: true, define: true, AMprocessNode: true, MathJax: true, document: true */
 34 /*jslint nomen: true, plusplus: true*/
 35 
 36 /* depends:
 37  jxg
 38  base/constants
 39  utils/event
 40  math/math
 41  */
 42 
 43 define([
 44     'jxg', 'base/constants', 'utils/event', 'utils/type', 'math/math'
 45 ], function (JXG, Const, EventEmitter, Type, Mat) {
 46 
 47     "use strict";
 48 
 49     /**
 50      * @fileoverview In this file the Coords object is defined, a class to manage all
 51      * properties and methods coordinates usually have.
 52      */
 53 
 54     /**
 55      * Constructs a new Coordinates object.
 56      * @class This is the Coordinates class.
 57      * All members a coordinate has to provide
 58      * are defined here.
 59      * @param {Number} method The type of coordinates given by the user. Accepted values are <b>COORDS_BY_SCREEN</b> and <b>COORDS_BY_USER</b>.
 60      * @param {Array} coordinates An array of affine coordinates.
 61      * @param {JXG.Board} board A reference to a board.
 62      * @oaram {Boolean} [emitter=true]
 63      * @borrows JXG.EventEmitter#on as this.on
 64      * @borrows JXG.EventEmitter#off as this.off
 65      * @borrows JXG.EventEmitter#triggerEventHandlers as this.triggerEventHandlers
 66      * @borrows JXG.EventEmitter#eventHandlers as this.eventHandlers
 67      * @constructor
 68      */
 69     JXG.Coords = function (method, coordinates, board, emitter) {
 70         /**
 71          * Stores the board the object is used on.
 72          * @type JXG.Board
 73          */
 74         this.board = board;
 75 
 76         /**
 77          * Stores coordinates for user view as homogeneous coordinates.
 78          * @type Array
 79          */
 80         this.usrCoords = [];
 81         //this.usrCoords = new Float64Array(3);
 82 
 83         /**
 84          * Stores coordinates for screen view as homogeneous coordinates.
 85          * @type Array
 86          */
 87         this.scrCoords = [];
 88         //this.scrCoords = new Float64Array(3);
 89 
 90         /**
 91          * If true, this coordinates object will emit update events every time
 92          * the coordinates are set.
 93          * @type {boolean}
 94          * @default true
 95          */
 96         this.emitter = !Type.exists(emitter) || emitter;
 97 
 98         if (this.emitter) {
 99             EventEmitter.eventify(this);
100         }
101         this.setCoordinates(method, coordinates, false, true);
102     };
103 
104     JXG.extend(JXG.Coords.prototype, /** @lends JXG.Coords.prototype */ {
105         /**
106          * Normalize homogeneous coordinates
107          * @private
108          */
109         normalizeUsrCoords: function () {
110             if (Math.abs(this.usrCoords[0]) > Mat.eps) {
111                 this.usrCoords[1] /= this.usrCoords[0];
112                 this.usrCoords[2] /= this.usrCoords[0];
113                 this.usrCoords[0] = 1.0;
114             }
115         },
116 
117         /**
118          * Compute screen coordinates out of given user coordinates.
119          * @private
120          */
121         usr2screen: function (doRound) {
122             var mround = Math.round,  // Is faster on IE, maybe slower with JIT compilers
123                 b = this.board,
124                 uc = this.usrCoords,
125                 oc = b.origin.scrCoords;
126 
127             if (doRound === true) {
128                 this.scrCoords[0] = mround(uc[0]);
129                 this.scrCoords[1] = mround(uc[0] * oc[1] + uc[1] * b.unitX);
130                 this.scrCoords[2] = mround(uc[0] * oc[2] - uc[2] * b.unitY);
131             } else {
132                 this.scrCoords[0] = uc[0];
133                 this.scrCoords[1] = uc[0] * oc[1] + uc[1] * b.unitX;
134                 this.scrCoords[2] = uc[0] * oc[2] - uc[2] * b.unitY;
135             }
136         },
137 
138         /**
139          * Compute user coordinates out of given screen coordinates.
140          * @private
141          */
142         screen2usr: function () {
143             var o = this.board.origin.scrCoords,
144                 sc = this.scrCoords,
145                 b = this.board;
146 
147             this.usrCoords[0] =  1.0;
148             this.usrCoords[1] = (sc[1] - o[1]) / b.unitX;
149             this.usrCoords[2] = (o[2] - sc[2]) / b.unitY;
150         },
151 
152         /**
153          * Calculate distance of one point to another.
154          * @param {Number} coord_type The type of coordinates used here. Possible values are <b>JXG.COORDS_BY_USER</b> and <b>JXG.COORDS_BY_SCREEN</b>.
155          * @param {JXG.Coords} coordinates The Coords object to which the distance is calculated.
156          * @returns {Number} The distance
157          */
158         distance: function (coord_type, coordinates) {
159             var sum = 0,
160                 c,
161                 ucr = this.usrCoords,
162                 scr = this.scrCoords,
163                 f;
164 
165             if (coord_type === Const.COORDS_BY_USER) {
166                 c = coordinates.usrCoords;
167                 f = ucr[0] - c[0];
168                 sum = f * f;
169 
170                 if (sum > Mat.eps * Mat.eps) {
171                     return Number.POSITIVE_INFINITY;
172                 }
173                 f = ucr[1] - c[1];
174                 sum += f * f;
175                 f = ucr[2] - c[2];
176                 sum += f * f;
177             } else {
178                 c = coordinates.scrCoords;
179                 //f = scr[0]-c[0];
180                 //sum = f*f;
181                 f = scr[1] - c[1];
182                 sum += f * f;
183                 f = scr[2] - c[2];
184                 sum += f * f;
185             }
186 
187             return Math.sqrt(sum);
188         },
189 
190         /**
191          * Set coordinates by either user coordinates or screen coordinates and recalculate the other one.
192          * @param {Number} coord_type The type of coordinates used here. Possible values are <b>COORDS_BY_USER</b> and <b>COORDS_BY_SCREEN</b>.
193          * @param {Array} coordinates An array of affine coordinates the Coords object is set to.
194          * @param {Boolean} [doRound=true] flag If true or null round the coordinates in usr2screen. This is used in smooth curve plotting.
195          * The IE needs rounded coordinates. Id doRound==false we have to round in updatePathString.
196          * @param {Boolean} [noevent=false]
197          * @returns {JXG.Coords} Reference to the coords object.
198          */
199         setCoordinates: function (coord_type, coordinates, doRound, noevent) {
200             var uc = this.usrCoords,
201                 sc = this.scrCoords,
202                 // Original values
203                 ou = [uc[0], uc[1], uc[2]],
204                 os = [sc[0], sc[1], sc[2]];
205 
206             if (coord_type === Const.COORDS_BY_USER) {
207                 if (coordinates.length === 2) { // Euclidean coordinates
208                     uc[0] = 1.0;
209                     uc[1] = coordinates[0];
210                     uc[2] = coordinates[1];
211                 } else { // Homogeneous coordinates (normalized)
212                     uc[0] = coordinates[0];
213                     uc[1] = coordinates[1];
214                     uc[2] = coordinates[2];
215                     this.normalizeUsrCoords();
216                 }
217                 this.usr2screen(doRound);
218             } else {
219                 if (coordinates.length === 2) { // Euclidean coordinates
220                     sc[1] = coordinates[0];
221                     sc[2] = coordinates[1];
222                 } else { // Homogeneous coordinates (normalized)
223                     sc[1] = coordinates[1];
224                     sc[2] = coordinates[2];
225                 }
226                 this.screen2usr();
227             }
228 
229             if (this.emitter && !noevent && (os[1] !== sc[1] || os[2] !== sc[2])) {
230                 this.triggerEventHandlers(['update'], [ou, os]);
231             }
232 
233             return this;
234         },
235 
236         /**
237         * Copy array, either srcCoords or usrCoords
238         * Uses slice() in case of standard arrays and set() in case of
239         * typed arrays.
240         * @private
241         * @param {String} obj Either 'srcCoords' or 'usrCoords'
242         * @param {Number} offset Offset, defaults to 0 if not given
243         * @returns {Array} Returns copy of the coords array either as standard array or as
244         *   typed array.
245         */
246         copy: function (obj, offset) {
247             if (offset === undefined) {
248                 offset = 0;
249             }
250 
251             return this[obj].slice(offset);
252         },
253 
254         /**
255          * Test if one of the usrCoords is NaN or the coordinates are infinite.
256          * @returns {Boolean} true if the coordinates are finite, false otherwise.
257          */
258         isReal: function() {
259             return (!isNaN(this.usrCoords[1] + this.usrCoords[2])) && (Math.abs(this.usrCoords[0]) > Mat.eps);
260         },
261 
262         /**
263          * Triggered whenever the coordinates change.
264          * @name JXG.Coords#update
265          * @param {Array} ou Old user coordinates
266          * @param {Array} os Old screen coordinates
267          * @event
268          */
269         __evt__update: function (ou, os) { },
270 
271         /**
272          * @ignore
273          */
274         __evt: function () {}
275     });
276 
277     return JXG.Coords;
278 });
279