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*/ 34 /*jslint nomen: true, plusplus: true*/ 35 36 /*depends: 37 jxg 38 base/constants 39 math/math 40 utils/type 41 */ 42 43 /** 44 * @fileoverview This file contains code for transformations of geometrical objects. 45 * @author graphjs 46 * @version 0.1 47 */ 48 49 define([ 50 'jxg', 'base/constants', 'math/math', 'utils/type' 51 ], function (JXG, Const, Mat, Type) { 52 53 "use strict"; 54 55 /** 56 * Possible types: 57 * - translate 58 * - scale 59 * - reflect 60 * - rotate 61 * - shear 62 * - generic 63 * 64 * Rotation matrix: 65 * ( 1 0 0 ) 66 * ( 0 cos(a) -sin(a)) 67 * ( 0 sin(a) cos(a) ) 68 * 69 * Translation matrix: 70 * ( 1 0 0) 71 * ( a 1 0) 72 * ( b 0 1) 73 */ 74 JXG.Transformation = function (board, type, params) { 75 this.elementClass = Const.OBJECT_CLASS_OTHER; 76 this.matrix = [ 77 [1, 0, 0], 78 [0, 1, 0], 79 [0, 0, 1] 80 ]; 81 this.board = board; 82 this.isNumericMatrix = false; 83 this.setMatrix(board, type, params); 84 85 this.methodMap = { 86 apply: 'apply', 87 applyOnce: 'applyOnce', 88 bindTo: 'bindTo', 89 bind: 'bind', 90 melt: 'melt' 91 }; 92 }; 93 94 JXG.Transformation.prototype = {}; 95 96 JXG.extend(JXG.Transformation.prototype, /** @lends JXG.Transformation.prototype */ { 97 update: function () { 98 return this; 99 }, 100 101 /** 102 * Set the transformation matrix for different 103 * types of standard transforms 104 */ 105 setMatrix: function (board, type, params) { 106 var i; 107 108 this.isNumericMatrix = true; 109 110 for (i = 0; i < params.length; i++) { 111 if (typeof params[i] !== 'number') { 112 this.isNumericMatrix = false; 113 break; 114 } 115 } 116 117 if (type === 'translate') { 118 if (params.length !== 2) { 119 throw new Error("JSXGraph: translate transformation needs 2 parameters."); 120 } 121 this.evalParam = Type.createEvalFunction(board, params, 2); 122 this.update = function () { 123 this.matrix[1][0] = this.evalParam(0); 124 this.matrix[2][0] = this.evalParam(1); 125 }; 126 } else if (type === 'scale') { 127 if (params.length !== 2) { 128 throw new Error("JSXGraph: scale transformation needs 2 parameters."); 129 } 130 this.evalParam = Type.createEvalFunction(board, params, 2); 131 this.update = function () { 132 this.matrix[1][1] = this.evalParam(0); // x 133 this.matrix[2][2] = this.evalParam(1); // y 134 }; 135 // Input: line or two points 136 } else if (type === 'reflect') { 137 // line or two points 138 if (params.length < 4) { 139 params[0] = board.select(params[0]); 140 } 141 142 // two points 143 if (params.length === 2) { 144 params[1] = board.select(params[1]); 145 } 146 147 // 4 coordinates [px,py,qx,qy] 148 if (params.length === 4) { 149 this.evalParam = Type.createEvalFunction(board, params, 4); 150 } 151 152 this.update = function () { 153 var x, y, z, xoff, yoff, d, 154 v, p; 155 // Determine homogeneous coordinates of reflections axis 156 // line 157 if (params.length === 1) { 158 v = params[0].stdform; 159 // two points 160 } else if (params.length === 2) { 161 v = Mat.crossProduct(params[1].coords.usrCoords, params[0].coords.usrCoords); 162 // two points coordinates [px,py,qx,qy] 163 } else if (params.length === 4) { 164 v = Mat.crossProduct( 165 [1, this.evalParam(2), this.evalParam(3)], 166 [1, this.evalParam(0), this.evalParam(1)] 167 ); 168 } 169 170 // Project origin to the line. This gives a finite point p 171 x = v[1]; 172 y = v[2]; 173 z = v[0]; 174 p = [-z * x, -z * y, x * x + y * y]; 175 d = p[2]; 176 177 // Normalize p 178 xoff = p[0] / p[2]; 179 yoff = p[1] / p[2]; 180 181 // x, y is the direction of the line 182 x = -v[2]; 183 y = v[1]; 184 185 this.matrix[1][1] = (x * x - y * y) / d; 186 this.matrix[1][2] = 2 * x * y / d; 187 this.matrix[2][1] = this.matrix[1][2]; 188 this.matrix[2][2] = -this.matrix[1][1]; 189 this.matrix[1][0] = xoff * (1 - this.matrix[1][1]) - yoff * this.matrix[1][2]; 190 this.matrix[2][0] = yoff * (1 - this.matrix[2][2]) - xoff * this.matrix[2][1]; 191 }; 192 } else if (type === 'rotate') { 193 // angle, x, y 194 if (params.length === 3) { 195 this.evalParam = Type.createEvalFunction(board, params, 3); 196 // angle, p or angle 197 } else if (params.length > 0 && params.length <= 2) { 198 this.evalParam = Type.createEvalFunction(board, params, 1); 199 200 if (params.length === 2) { 201 params[1] = board.select(params[1]); 202 } 203 } 204 205 this.update = function () { 206 var x, y, 207 beta = this.evalParam(0), 208 co = Math.cos(beta), 209 si = Math.sin(beta); 210 211 this.matrix[1][1] = co; 212 this.matrix[1][2] = -si; 213 this.matrix[2][1] = si; 214 this.matrix[2][2] = co; 215 216 // rotate around [x,y] otherwise rotate around [0,0] 217 if (params.length > 1) { 218 if (params.length === 3) { 219 x = this.evalParam(1); 220 y = this.evalParam(2); 221 } else { 222 x = params[1].X(); 223 y = params[1].Y(); 224 } 225 this.matrix[1][0] = x * (1 - co) + y * si; 226 this.matrix[2][0] = y * (1 - co) - x * si; 227 } 228 }; 229 } else if (type === 'shear') { 230 if (params.length !== 2) { 231 throw new Error("JSXGraph: shear transformation needs 2 parameters."); 232 } 233 234 this.evalParam = Type.createEvalFunction(board, params, 2); 235 this.update = function () { 236 this.matrix[1][2] = this.evalParam(0); 237 this.matrix[2][1] = this.evalParam(1); 238 }; 239 } else if (type === 'generic') { 240 if (params.length !== 9) { 241 throw new Error("JSXGraph: generic transformation needs 9 parameters."); 242 } 243 244 this.evalParam = Type.createEvalFunction(board, params, 9); 245 246 this.update = function () { 247 this.matrix[0][0] = this.evalParam(0); 248 this.matrix[0][1] = this.evalParam(1); 249 this.matrix[0][2] = this.evalParam(2); 250 this.matrix[1][0] = this.evalParam(3); 251 this.matrix[1][1] = this.evalParam(4); 252 this.matrix[1][2] = this.evalParam(5); 253 this.matrix[2][0] = this.evalParam(6); 254 this.matrix[2][1] = this.evalParam(7); 255 this.matrix[2][2] = this.evalParam(8); 256 }; 257 } 258 }, 259 260 /** 261 * Transform a GeometryElement: 262 * Update the matrix first, then do the matrix-vector-multiplication 263 * @param {JXG.GeometryElement} p element which is transformed 264 * @param {String} self Apply the transformation to the initialCoords instead of the coords if this is set. 265 * @returns {Array} 266 */ 267 apply: function (p, self) { 268 this.update(); 269 270 if (Type.exists(self)) { 271 return Mat.matVecMult(this.matrix, p.initialCoords.usrCoords); 272 } 273 return Mat.matVecMult(this.matrix, p.coords.usrCoords); 274 }, 275 276 /** 277 * Apply a transformation once to a GeometryElement. 278 * If it is a free point, then it can be dragged around later 279 * and will overwrite the transformed coordinates. 280 * @param {JXG.Point,Array} p 281 */ 282 applyOnce: function (p) { 283 var c, len, i; 284 285 if (!Type.isArray(p)) { 286 p = [p]; 287 } 288 289 len = p.length; 290 291 for (i = 0; i < len; i++) { 292 this.update(); 293 c = Mat.matVecMult(this.matrix, p[i].coords.usrCoords); 294 p[i].coords.setCoordinates(Const.COORDS_BY_USER, c); 295 } 296 }, 297 298 /** 299 * Bind a transformation to a GeometryElement 300 */ 301 bindTo: function (p) { 302 var i, len; 303 if (Type.isArray(p)) { 304 len = p.length; 305 306 for (i = 0; i < len; i++) { 307 p[i].transformations.push(this); 308 } 309 } else { 310 p.transformations.push(this); 311 } 312 }, 313 314 /** 315 * @deprecated Use setAttribute 316 * @param term 317 */ 318 setProperty: function (term) { }, 319 320 setAttribute: function (term) { }, 321 322 /** 323 * Multiplication of a transformation t from the right. 324 * this = t join this 325 */ 326 melt: function (t) { 327 var res = [], i, len, len0, k, s, j; 328 329 len = t.matrix.length; 330 len0 = this.matrix[0].length; 331 332 for (i = 0; i < len; i++) { 333 res[i] = []; 334 } 335 336 this.update(); 337 t.update(); 338 339 for (i = 0; i < len; i++) { 340 for (j = 0; j < len0; j++) { 341 s = 0; 342 for (k = 0; k < len; k++) { 343 s += t.matrix[i][k] * this.matrix[k][j]; 344 } 345 res[i][j] = s; 346 } 347 } 348 349 this.update = function () { 350 var len = this.matrix.length, 351 len0 = this.matrix[0].length; 352 353 for (i = 0; i < len; i++) { 354 for (j = 0; j < len0; j++) { 355 this.matrix[i][j] = res[i][j]; 356 } 357 } 358 }; 359 return this; 360 } 361 }); 362 363 JXG.createTransform = function (board, parents, attributes) { 364 return new JXG.Transformation(board, attributes.type, parents); 365 }; 366 367 JXG.registerElement('transform', JXG.createTransform); 368 369 return { 370 Transformation: JXG.Transformation, 371 createTransform: JXG.createTransform 372 }; 373 }); 374