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