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