      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