1 /*
  2     Copyright 2008-2013
  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         /**
 82          * Stores coordinates for screen view as homogeneous coordinates.
 83          * @type Array
 84          */
 85         this.scrCoords = [];
 86 
 87         /**
 88          * If true, this coordinates object will emit update events every time
 89          * the coordinates are set.
 90          * @type {boolean}
 91          * @default true
 92          */
 93         this.emitter = !Type.exists(emitter) || emitter;
 94 
 95         if (this.emitter) {
 96             EventEmitter.eventify(this);
 97         }
 98         this.setCoordinates(method, coordinates, true, true);
 99     };
100 
101     JXG.extend(JXG.Coords.prototype, /** @lends JXG.Coords.prototype */ {
102         /**
103          * Normalize homogeneous coordinates
104          * @private
105          */
106         normalizeUsrCoords: function () {
107             var eps = Mat.eps;
108             if (Math.abs(this.usrCoords[0]) > eps) {
109                 this.usrCoords[1] /= this.usrCoords[0];
110                 this.usrCoords[2] /= this.usrCoords[0];
111                 this.usrCoords[0] = 1.0;
112             }
113         },
114 
115         /**
116          * Compute screen coordinates out of given user coordinates.
117          * @private
118          */
119         usr2screen: function (doRound) {
120             var mround = Math.round,  // Is faster on IE, maybe slower with JIT compilers
121                 b = this.board,
122                 uc = this.usrCoords,
123                 oc = b.origin.scrCoords;
124 
125             if (doRound === null || doRound) {
126                 this.scrCoords[0] = mround(uc[0]);
127                 this.scrCoords[1] = mround(uc[0] * oc[1] + uc[1] * b.unitX);
128                 this.scrCoords[2] = mround(uc[0] * oc[2] - uc[2] * b.unitY);
129             } else {
130                 this.scrCoords[0] = uc[0];
131                 this.scrCoords[1] = uc[0] * oc[1] + uc[1] * b.unitX;
132                 this.scrCoords[2] = uc[0] * oc[2] - uc[2] * b.unitY;
133             }
134         },
135 
136         /**
137          * Compute user coordinates out of given screen coordinates.
138          * @private
139          */
140         screen2usr: function () {
141             var o = this.board.origin.scrCoords,
142                 sc = this.scrCoords,
143                 b = this.board;
144 
145             this.usrCoords[0] =  1.0;
146             this.usrCoords[1] = (sc[1] - o[1]) / b.unitX;
147             this.usrCoords[2] = (o[2] - sc[2]) / b.unitY;
148         },
149 
150         /**
151          * Calculate distance of one point to another.
152          * @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>.
153          * @param {JXG.Coords} coordinates The Coords object to which the distance is calculated.
154          * @returns {Number} The distance
155          */
156         distance: function (coord_type, coordinates) {
157             var sum = 0,
158                 c,
159                 ucr = this.usrCoords,
160                 scr = this.scrCoords,
161                 f;
162 
163             if (coord_type === Const.COORDS_BY_USER) {
164                 c = coordinates.usrCoords;
165                 f = ucr[0] - c[0];
166                 sum = f * f;
167 
168                 if (sum > Mat.eps) {
169                     return Number.POSITIVE_INFINITY;
170                 }
171                 f = ucr[1] - c[1];
172                 sum += f * f;
173                 f = ucr[2] - c[2];
174                 sum += f * f;
175             } else {
176                 c = coordinates.scrCoords;
177                 //f = scr[0]-c[0];
178                 //sum = f*f;
179                 f = scr[1] - c[1];
180                 sum += f * f;
181                 f = scr[2] - c[2];
182                 sum += f * f;
183             }
184 
185             return Math.sqrt(sum);
186         },
187 
188         /**
189          * Set coordinates by either user coordinates or screen coordinates and recalculate the other one.
190          * @param {Number} coord_type The type of coordinates used here. Possible values are <b>COORDS_BY_USER</b> and <b>COORDS_BY_SCREEN</b>.
191          * @param {Array} coordinates An array of affine coordinates the Coords object is set to.
192          * @param {Boolean} [doRound=true] flag If true or null round the coordinates in usr2screen. This is used in smooth curve plotting.
193          * The IE needs rounded coordinates. Id doRound==false we have to round in updatePathString.
194          * @param {Boolean} [noevent=false]
195          * @returns {JXG.Coords} Reference to the coords object.
196          */
197         setCoordinates: function (coord_type, coordinates, doRound, noevent) {
198             var uc = this.usrCoords,
199                 sc = this.scrCoords,
200                 ou = [uc[0], uc[1], uc[2]],
201                 os = [sc[0], sc[1], sc[2]];
202 
203             if (coord_type === Const.COORDS_BY_USER) {
204                 if (coordinates.length === 2) { // Euclidean coordinates
205                     uc[0] = 1.0;
206                     uc[1] = coordinates[0];
207                     uc[2] = coordinates[1];
208                 } else { // Homogeneous coordinates (normalized)
209                     uc[0] = coordinates[0];
210                     uc[1] = coordinates[1];
211                     uc[2] = coordinates[2];
212                     this.normalizeUsrCoords();
213                 }
214                 this.usr2screen(doRound);
215             } else {
216                 sc[1] = coordinates[0];
217                 sc[2] = coordinates[1];
218                 this.screen2usr();
219             }
220 
221             if (this.emitter && !noevent && (os[1] !== sc[1] || os[2] !== sc[2])) {
222                 this.triggerEventHandlers(['update'], [ou, os]);
223             }
224 
225             return this;
226         },
227 
228         /**
229          * Triggered whenever the coordinates change.
230          * @name JXG.Coords#update
231          * @param {Array} ou Old user coordinates
232          * @param {Array} os Old screen coordinates
233          * @event
234          */
235         __evt__update: function (ou, os) { },
236 
237         /**
238          * @ignore
239          */
240         __evt: function () {}
241     });
242 
243     return JXG.Coords;
244 });
245