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