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