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.Transformation.prototype.update = function(){}; 60 61 /** 62 * Set the transformation matrix for different 63 * types of standard transforms 64 */ 65 JXG.Transformation.prototype.setMatrix = function(board,type,params) { 66 var i; 67 68 this.isNumericMatrix = true; 69 for (i=0;i<params.length;i++) { 70 if (typeof params[i]!='number') { 71 this.isNumericMatrix = false; 72 break; 73 } 74 } 75 76 if (type=='translate') { 77 this.evalParam = JXG.createEvalFunction(board,params,2); 78 this.update = function() { 79 this.matrix[1][0] = this.evalParam(0); 80 this.matrix[2][0] = this.evalParam(1); 81 }; 82 } else if (type=='scale') { 83 this.evalParam = JXG.createEvalFunction(board,params,2); 84 this.update = function() { 85 this.matrix[1][1] = this.evalParam(0); // x 86 this.matrix[2][2] = this.evalParam(1); // y 87 }; 88 } else if (type=='reflect') { // Input: line or two points 89 if (params.length<4) { // line or two points 90 params[0] = JXG.getReference(board,params[0]); 91 } 92 if (params.length==2) { // two points 93 params[1] = JXG.getReference(board,params[1]); 94 } 95 if (params.length==4) { // 4 coordinates [px,py,qx,qy] 96 this.evalParam = JXG.createEvalFunction(board,params,4); 97 } 98 this.update = function() { 99 var x, y, xoff, yoff, d; 100 101 if (params.length==1) { // line 102 x = params[0].point2.X()-params[0].point1.X(); 103 y = params[0].point2.Y()-params[0].point1.Y(); 104 xoff = params[0].point1.X(); 105 yoff = params[0].point1.Y(); 106 } else if (params.length==2){ // two points 107 x = params[1].X()-params[0].X(); 108 y = params[1].Y()-params[0].Y(); 109 xoff = params[0].X(); 110 yoff = params[0].Y(); 111 } else if (params.length==4){ // two points coordinates [px,py,qx,qy] 112 x = this.evalParam(2)-this.evalParam(0); 113 y = this.evalParam(3)-this.evalParam(1); 114 xoff = this.evalParam(0); 115 yoff = this.evalParam(1); 116 } 117 d = x*x+y*y; 118 this.matrix[1][1] = (x*x-y*y)/d; 119 this.matrix[1][2] = 2*x*y/d; 120 this.matrix[2][1] = 2*x*y/d; 121 this.matrix[2][2] = (-x*x+y*y)/d; 122 this.matrix[1][0] = xoff*(1-this.matrix[1][1])-yoff*this.matrix[1][2]; 123 this.matrix[2][0] = yoff*(1-this.matrix[2][2])-xoff*this.matrix[2][1]; 124 }; 125 } else if (type=='rotate') { 126 if (params.length==3) { // angle, x, y 127 this.evalParam = JXG.createEvalFunction(board,params,3); 128 } else if (params.length<=2) { // angle, p or angle 129 this.evalParam = JXG.createEvalFunction(board,params,1); 130 if (params.length==2) { 131 params[1] = JXG.getReference(board,params[1]); 132 } 133 } 134 this.update = function() { 135 var beta = this.evalParam(0), x, y, co = Math.cos(beta), si = Math.sin(beta); 136 this.matrix[1][1] = co; 137 this.matrix[1][2] = -si; 138 this.matrix[2][1] = si; 139 this.matrix[2][2] = co; 140 if (params.length>1) { // rotate around [x,y] otherwise rotate around [0,0] 141 if (params.length==3) { 142 x = this.evalParam(1); 143 y = this.evalParam(2); 144 } else { 145 x = params[1].X(); 146 y = params[1].Y(); 147 } 148 this.matrix[1][0] = x*(1-co)+y*si; 149 this.matrix[2][0] = y*(1-co)-x*si; 150 } 151 }; 152 } else if (type=='shear') { 153 this.evalParam = JXG.createEvalFunction(board,params,1); 154 this.update = function() { 155 var beta = this.evalParam(0); 156 this.matrix[1][1] = Math.tan(beta); 157 }; 158 } else if (type=='generic') { 159 this.evalParam = JXG.createEvalFunction(board,params,9); 160 this.update = function() { 161 this.matrix[0][0] = this.evalParam(0); 162 this.matrix[0][1] = this.evalParam(1); 163 this.matrix[0][2] = this.evalParam(2); 164 this.matrix[1][0] = this.evalParam(3); 165 this.matrix[1][1] = this.evalParam(4); 166 this.matrix[1][2] = this.evalParam(5); 167 this.matrix[2][0] = this.evalParam(6); 168 this.matrix[2][1] = this.evalParam(7); 169 this.matrix[2][2] = this.evalParam(8); 170 }; 171 } 172 }; 173 174 /** 175 * Transform a GeometryElement: 176 * First, update the matrix 177 * Second, do the matrix-vector-multiplication 178 * 179 * @param {JXG.GeometryElement} element, which is transformed 180 */ 181 JXG.Transformation.prototype.apply = function(p){ 182 this.update(); 183 if (arguments[1]!=null) { 184 return JXG.Math.matVecMult(this.matrix,p.initialCoords.usrCoords); 185 } else { 186 return JXG.Math.matVecMult(this.matrix,p.coords.usrCoords); 187 } 188 }; 189 190 /** 191 * Apply a transformation once to a GeometryElement. 192 * If it is a free point, then it can be dragged around later 193 * and will overwrite the transformed coordinates. 194 */ 195 JXG.Transformation.prototype.applyOnce = function(p){ 196 var c, len, i; 197 if (!JXG.isArray(p)) { 198 this.update(); 199 c = JXG.Math.matVecMult(this.matrix,p.coords.usrCoords); 200 p.coords.setCoordinates(JXG.COORDS_BY_USER,[c[1],c[2]]); 201 } else { 202 len = p.length; 203 for (i=0; i<len; i++) { 204 this.update(); 205 c = JXG.Math.matVecMult(this.matrix,p[i].coords.usrCoords); 206 p[i].coords.setCoordinates(JXG.COORDS_BY_USER,[c[1],c[2]]); 207 } 208 } 209 }; 210 211 /** 212 * Bind a transformation to a GeometryElement 213 */ 214 JXG.Transformation.prototype.bindTo = function(p){ 215 var i, len; 216 if (JXG.isArray(p)) { 217 len = p.length; 218 for (i=0; i<len; i++) { 219 p[i].transformations.push(this); 220 } 221 } else { 222 p.transformations.push(this); 223 } 224 }; 225 226 JXG.Transformation.prototype.setProperty = function(term) {}; 227 228 /** 229 * Multiplication of a transformation t from the right. 230 * this = t join this 231 */ 232 JXG.Transformation.prototype.melt = function(t){ 233 var res = [], i, len, len0, k, s, j; 234 235 len = t.matrix.length; 236 len0 = this.matrix[0].length; 237 238 for (i=0;i<len;i++) { 239 res[i] = []; 240 } 241 this.update(); 242 t.update(); 243 for (i=0;i<len;i++) { 244 for (j=0;j<len0;j++) { 245 s = 0; 246 for (k=0;k<len;k++) { 247 s += t.matrix[i][k]*this.matrix[k][j]; 248 } 249 res[i][j] = s; 250 } 251 } 252 this.update = function() { 253 var len = this.matrix.length, 254 len0 = this.matrix[0].length; 255 for (i=0;i<len;i++) { 256 for (j=0;j<len0;j++) { 257 this.matrix[i][j] = res[i][j]; 258 } 259 } 260 }; 261 return true; 262 }; 263 264 JXG.createTransform = function(board, parentArr, atts) { 265 return new JXG.Transformation(board,atts['type'],parentArr); 266 }; 267 268 JXG.JSXGraph.registerElement('transform', JXG.createTransform); 269