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