1 /* 2 Copyright 2008,2009 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: you can redistribute it and/or modify 13 it under the terms of the GNU Lesser General Public License as published by 14 the Free Software Foundation, either version 3 of the License, or 15 (at your option) any later version. 16 17 JSXGraph is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 GNU Lesser General Public License for more details. 21 22 You should have received a copy of the GNU Lesser General Public License 23 along with JSXGraph. If not, see <http://www.gnu.org/licenses/>. 24 */ 25 26 /** 27 * @fileoverview This file contains code for transformations of geometrical objects. 28 * @author graphjs 29 * @version 0.1 30 * 31 * Possible types: 32 * - translate 33 * - scale 34 * - reflect 35 * - rotate 36 * - shear 37 * - generic 38 * 39 * Rotation matrix: 40 * ( 1 0 0 ) 41 * ( 0 cos(a) -sin(a)) 42 * ( 0 sin(a) cos(a) ) 43 * 44 * Translation matrix: 45 * ( 1 0 0) 46 * ( a 1 0) 47 * ( b 0 1) 48 49 */ 50 JXG.Transformation = function(board,type, params) { 51 this.elementClass = JXG.OBJECT_CLASS_OTHER; 52 this.matrix = [[1,0,0],[0,1,0],[0,0,1]]; 53 this.board = board; 54 this.isNumericMatrix = false; 55 this.setMatrix(board,type,params); 56 }; 57 JXG.Transformation.prototype = {}; 58 59 JXG.extend(JXG.Transformation.prototype, /** @lends JXG.Transformation.prototype */ { 60 update: function(){ return this;}, 61 62 /** 63 * Set the transformation matrix for different 64 * types of standard transforms 65 */ 66 setMatrix: function(board,type,params) { 67 var i; 68 69 this.isNumericMatrix = true; 70 for (i=0;i<params.length;i++) { 71 if (typeof params[i]!='number') { 72 this.isNumericMatrix = false; 73 break; 74 } 75 } 76 77 if (type=='translate') { 78 this.evalParam = JXG.createEvalFunction(board,params,2); 79 this.update = function() { 80 this.matrix[1][0] = this.evalParam(0); 81 this.matrix[2][0] = this.evalParam(1); 82 }; 83 } else if (type=='scale') { 84 this.evalParam = JXG.createEvalFunction(board,params,2); 85 this.update = function() { 86 this.matrix[1][1] = this.evalParam(0); // x 87 this.matrix[2][2] = this.evalParam(1); // y 88 }; 89 } else if (type=='reflect') { // Input: line or two points 90 if (params.length<4) { // line or two points 91 params[0] = JXG.getReference(board,params[0]); 92 } 93 if (params.length==2) { // two points 94 params[1] = JXG.getReference(board,params[1]); 95 } 96 if (params.length==4) { // 4 coordinates [px,py,qx,qy] 97 this.evalParam = JXG.createEvalFunction(board,params,4); 98 } 99 this.update = function() { 100 var x, y, z, xoff, yoff, d, 101 v, p; 102 // Determine homogeneous coordinates of reflections axis 103 if (params.length==1) { // line 104 v = params[0].stdform; 105 } else if (params.length==2){ // two points 106 v = JXG.Math.crossProduct(params[1].coords.usrCoords, params[0].coords.usrCoords); 107 } else if (params.length==4){ // two points coordinates [px,py,qx,qy] 108 v = JXG.Math.crossProduct( 109 [1, this.evalParam(2), this.evalParam(3)], 110 [1, this.evalParam(0), this.evalParam(1)]); 111 } 112 // Project origin to the line. This gives a finite point p 113 x = v[1]; 114 y = v[2]; 115 z = v[0]; 116 p = [-z*x, -z*y, x*x+y*y]; 117 d = p[2]; 118 // Normalize p 119 xoff = p[0]/p[2]; 120 yoff = p[1]/p[2]; 121 // x, y is the direction of the line 122 x = -v[2]; 123 y = v[1]; 124 125 this.matrix[1][1] = (x*x-y*y)/d; 126 this.matrix[1][2] = 2*x*y/d; 127 this.matrix[2][1] = this.matrix[1][2]; 128 this.matrix[2][2] = -this.matrix[1][1]; 129 this.matrix[1][0] = xoff*(1-this.matrix[1][1])-yoff*this.matrix[1][2]; 130 this.matrix[2][0] = yoff*(1-this.matrix[2][2])-xoff*this.matrix[2][1]; 131 }; 132 } else if (type=='rotate') { 133 if (params.length==3) { // angle, x, y 134 this.evalParam = JXG.createEvalFunction(board,params,3); 135 } else if (params.length<=2) { // angle, p or angle 136 this.evalParam = JXG.createEvalFunction(board,params,1); 137 if (params.length==2) { 138 params[1] = JXG.getReference(board,params[1]); 139 } 140 } 141 this.update = function() { 142 var beta = this.evalParam(0), x, y, co = Math.cos(beta), si = Math.sin(beta); 143 this.matrix[1][1] = co; 144 this.matrix[1][2] = -si; 145 this.matrix[2][1] = si; 146 this.matrix[2][2] = co; 147 if (params.length>1) { // rotate around [x,y] otherwise rotate around [0,0] 148 if (params.length==3) { 149 x = this.evalParam(1); 150 y = this.evalParam(2); 151 } else { 152 x = params[1].X(); 153 y = params[1].Y(); 154 } 155 this.matrix[1][0] = x*(1-co)+y*si; 156 this.matrix[2][0] = y*(1-co)-x*si; 157 } 158 }; 159 } else if (type=='shear') { 160 this.evalParam = JXG.createEvalFunction(board,params,1); 161 this.update = function() { 162 var beta = this.evalParam(0); 163 this.matrix[1][1] = Math.tan(beta); 164 }; 165 } else if (type=='generic') { 166 this.evalParam = JXG.createEvalFunction(board,params,9); 167 this.update = function() { 168 this.matrix[0][0] = this.evalParam(0); 169 this.matrix[0][1] = this.evalParam(1); 170 this.matrix[0][2] = this.evalParam(2); 171 this.matrix[1][0] = this.evalParam(3); 172 this.matrix[1][1] = this.evalParam(4); 173 this.matrix[1][2] = this.evalParam(5); 174 this.matrix[2][0] = this.evalParam(6); 175 this.matrix[2][1] = this.evalParam(7); 176 this.matrix[2][2] = this.evalParam(8); 177 }; 178 } 179 }, 180 181 /** 182 * Transform a GeometryElement: 183 * First, update the matrix 184 * Second, do the matrix-vector-multiplication 185 * 186 * @param {JXG.GeometryElement} element, which is transformed 187 */ 188 apply: function(p){ 189 this.update(); 190 if (arguments[1] != null) { 191 return JXG.Math.matVecMult(this.matrix,p.initialCoords.usrCoords); 192 } else { 193 return JXG.Math.matVecMult(this.matrix,p.coords.usrCoords); 194 } 195 }, 196 197 /** 198 * Apply a transformation once to a GeometryElement. 199 * If it is a free point, then it can be dragged around later 200 * and will overwrite the transformed coordinates. 201 * @param {JXG.Point|Array} p 202 */ 203 applyOnce: function(p){ 204 var c, len, i; 205 206 if (!JXG.isArray(p)) { 207 p = [p]; 208 } 209 210 len = p.length; 211 for (i = 0; i < len; i++) { 212 this.update(); 213 c = JXG.Math.matVecMult(this.matrix, p[i].coords.usrCoords); 214 p[i].coords.setCoordinates(JXG.COORDS_BY_USER, c); 215 } 216 217 }, 218 219 /** 220 * Bind a transformation to a GeometryElement 221 */ 222 bindTo: function(p){ 223 var i, len; 224 if (JXG.isArray(p)) { 225 len = p.length; 226 for (i=0; i<len; i++) { 227 p[i].transformations.push(this); 228 } 229 } else { 230 p.transformations.push(this); 231 } 232 }, 233 234 setProperty: function(term) { 235 }, 236 237 /** 238 * Multiplication of a transformation t from the right. 239 * this = t join this 240 */ 241 melt: function(t){ 242 var res = [], i, len, len0, k, s, j; 243 244 len = t.matrix.length; 245 len0 = this.matrix[0].length; 246 247 for (i=0;i<len;i++) { 248 res[i] = []; 249 } 250 this.update(); 251 t.update(); 252 for (i=0;i<len;i++) { 253 for (j=0;j<len0;j++) { 254 s = 0; 255 for (k=0;k<len;k++) { 256 s += t.matrix[i][k]*this.matrix[k][j]; 257 } 258 res[i][j] = s; 259 } 260 } 261 this.update = function() { 262 var len = this.matrix.length, 263 len0 = this.matrix[0].length; 264 for (i=0;i<len;i++) { 265 for (j=0;j<len0;j++) { 266 this.matrix[i][j] = res[i][j]; 267 } 268 } 269 }; 270 return this; 271 } 272 }); 273 274 JXG.createTransform = function(board, parents, attributes) { 275 return new JXG.Transformation(board, attributes['type'], parents); 276 }; 277 278 JXG.JSXGraph.registerElement('transform', JXG.createTransform); 279