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 this.evalParam = Type.createEvalFunction(board, params, 2); 119 this.update = function () { 120 this.matrix[1][0] = this.evalParam(0); 121 this.matrix[2][0] = this.evalParam(1); 122 }; 123 } else if (type === 'scale') { 124 this.evalParam = Type.createEvalFunction(board, params, 2); 125 this.update = function () { 126 this.matrix[1][1] = this.evalParam(0); // x 127 this.matrix[2][2] = this.evalParam(1); // y 128 }; 129 // Input: line or two points 130 } else if (type === 'reflect') { 131 // line or two points 132 if (params.length < 4) { 133 params[0] = board.select(params[0]); 134 } 135 136 // two points 137 if (params.length === 2) { 138 params[1] = board.select(params[1]); 139 } 140 141 // 4 coordinates [px,py,qx,qy] 142 if (params.length === 4) { 143 this.evalParam = Type.createEvalFunction(board, params, 4); 144 } 145 146 this.update = function () { 147 var x, y, z, xoff, yoff, d, 148 v, p; 149 // Determine homogeneous coordinates of reflections axis 150 // line 151 if (params.length === 1) { 152 v = params[0].stdform; 153 // two points 154 } else if (params.length === 2) { 155 v = Mat.crossProduct(params[1].coords.usrCoords, params[0].coords.usrCoords); 156 // two points coordinates [px,py,qx,qy] 157 } else if (params.length === 4) { 158 v = Mat.crossProduct( 159 [1, this.evalParam(2), this.evalParam(3)], 160 [1, this.evalParam(0), this.evalParam(1)] 161 ); 162 } 163 164 // Project origin to the line. This gives a finite point p 165 x = v[1]; 166 y = v[2]; 167 z = v[0]; 168 p = [-z * x, -z * y, x * x + y * y]; 169 d = p[2]; 170 171 // Normalize p 172 xoff = p[0] / p[2]; 173 yoff = p[1] / p[2]; 174 175 // x, y is the direction of the line 176 x = -v[2]; 177 y = v[1]; 178 179 this.matrix[1][1] = (x * x - y * y) / d; 180 this.matrix[1][2] = 2 * x * y / d; 181 this.matrix[2][1] = this.matrix[1][2]; 182 this.matrix[2][2] = -this.matrix[1][1]; 183 this.matrix[1][0] = xoff * (1 - this.matrix[1][1]) - yoff * this.matrix[1][2]; 184 this.matrix[2][0] = yoff * (1 - this.matrix[2][2]) - xoff * this.matrix[2][1]; 185 }; 186 } else if (type === 'rotate') { 187 // angle, x, y 188 if (params.length === 3) { 189 this.evalParam = Type.createEvalFunction(board, params, 3); 190 // angle, p or angle 191 } else if (params.length <= 2) { 192 this.evalParam = Type.createEvalFunction(board, params, 1); 193 194 if (params.length === 2) { 195 params[1] = board.select(params[1]); 196 } 197 } 198 199 this.update = function () { 200 var x, y, 201 beta = this.evalParam(0), 202 co = Math.cos(beta), 203 si = Math.sin(beta); 204 205 this.matrix[1][1] = co; 206 this.matrix[1][2] = -si; 207 this.matrix[2][1] = si; 208 this.matrix[2][2] = co; 209 210 // rotate around [x,y] otherwise rotate around [0,0] 211 if (params.length > 1) { 212 if (params.length === 3) { 213 x = this.evalParam(1); 214 y = this.evalParam(2); 215 } else { 216 x = params[1].X(); 217 y = params[1].Y(); 218 } 219 this.matrix[1][0] = x * (1 - co) + y * si; 220 this.matrix[2][0] = y * (1 - co) - x * si; 221 } 222 }; 223 } else if (type === 'shear') { 224 this.evalParam = Type.createEvalFunction(board, params, 2); 225 226 this.update = function () { 227 this.matrix[1][2] = this.evalParam(0); 228 this.matrix[2][1] = this.evalParam(1); 229 }; 230 } else if (type === 'generic') { 231 this.evalParam = Type.createEvalFunction(board, params, 9); 232 233 this.update = function () { 234 this.matrix[0][0] = this.evalParam(0); 235 this.matrix[0][1] = this.evalParam(1); 236 this.matrix[0][2] = this.evalParam(2); 237 this.matrix[1][0] = this.evalParam(3); 238 this.matrix[1][1] = this.evalParam(4); 239 this.matrix[1][2] = this.evalParam(5); 240 this.matrix[2][0] = this.evalParam(6); 241 this.matrix[2][1] = this.evalParam(7); 242 this.matrix[2][2] = this.evalParam(8); 243 }; 244 } 245 }, 246 247 /** 248 * Transform a GeometryElement: 249 * Update the matrix first, then do the matrix-vector-multiplication 250 * @param {JXG.GeometryElement} p element which is transformed 251 * @param {String} self Apply the transformation to the initialCoords instead of the coords if this is set. 252 * @returns {Array} 253 */ 254 apply: function (p, self) { 255 this.update(); 256 257 if (Type.exists(self)) { 258 return Mat.matVecMult(this.matrix, p.initialCoords.usrCoords); 259 } 260 return Mat.matVecMult(this.matrix, p.coords.usrCoords); 261 }, 262 263 /** 264 * Apply a transformation once to a GeometryElement. 265 * If it is a free point, then it can be dragged around later 266 * and will overwrite the transformed coordinates. 267 * @param {JXG.Point,Array} p 268 */ 269 applyOnce: function (p) { 270 var c, len, i; 271 272 if (!Type.isArray(p)) { 273 p = [p]; 274 } 275 276 len = p.length; 277 278 for (i = 0; i < len; i++) { 279 this.update(); 280 c = Mat.matVecMult(this.matrix, p[i].coords.usrCoords); 281 p[i].coords.setCoordinates(Const.COORDS_BY_USER, c); 282 } 283 }, 284 285 /** 286 * Bind a transformation to a GeometryElement 287 */ 288 bindTo: function (p) { 289 var i, len; 290 if (Type.isArray(p)) { 291 len = p.length; 292 293 for (i = 0; i < len; i++) { 294 p[i].transformations.push(this); 295 } 296 } else { 297 p.transformations.push(this); 298 } 299 }, 300 301 /** 302 * @deprecated Use setAttribute 303 * @param term 304 */ 305 setProperty: function (term) { }, 306 307 setAttribute: function (term) { }, 308 309 /** 310 * Multiplication of a transformation t from the right. 311 * this = t join this 312 */ 313 melt: function (t) { 314 var res = [], i, len, len0, k, s, j; 315 316 len = t.matrix.length; 317 len0 = this.matrix[0].length; 318 319 for (i = 0; i < len; i++) { 320 res[i] = []; 321 } 322 323 this.update(); 324 t.update(); 325 326 for (i = 0; i < len; i++) { 327 for (j = 0; j < len0; j++) { 328 s = 0; 329 for (k = 0; k < len; k++) { 330 s += t.matrix[i][k] * this.matrix[k][j]; 331 } 332 res[i][j] = s; 333 } 334 } 335 336 this.update = function () { 337 var len = this.matrix.length, 338 len0 = this.matrix[0].length; 339 340 for (i = 0; i < len; i++) { 341 for (j = 0; j < len0; j++) { 342 this.matrix[i][j] = res[i][j]; 343 } 344 } 345 }; 346 return this; 347 } 348 }); 349 350 JXG.createTransform = function (board, parents, attributes) { 351 return new JXG.Transformation(board, attributes.type, parents); 352 }; 353 354 JXG.registerElement('transform', JXG.createTransform); 355 356 return { 357 Transformation: JXG.Transformation, 358 createTransform: JXG.createTransform 359 }; 360 });