View Javadoc

1   /* File AffineTransform2D.java 
2    *
3    * Project : Java Geometry Library
4    *
5    * ===========================================
6    * 
7    * This library is free software; you can redistribute it and/or modify it 
8    * under the terms of the GNU Lesser General Public License as published by
9    * the Free Software Foundation, either version 2.1 of the License, or (at
10   * your option) any later version.
11   *
12   * This library is distributed in the hope that it will be useful, but 
13   * WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY
14   * or FITNESS FOR A PARTICULAR PURPOSE.
15   *
16   * See the GNU Lesser General Public License for more details.
17   *
18   * You should have received a copy of the GNU Lesser General Public License
19   * along with this library. if not, write to :
20   * The Free Software Foundation, Inc., 59 Temple Place, Suite 330,
21   * Boston, MA 02111-1307, USA.
22   */
23  
24  // package
25  
26  package math.geom2d;
27  
28  // Imports
29  import math.geom2d.Shape2D;
30  import math.geom2d.Angle2D;
31  import math.geom2d.Point2D;
32  import math.geom2d.Vector2D;
33  import math.geom2d.line.LinearShape2D;
34  import math.geom2d.transform.Bijection2D;
35  
36  /**
37   * Base class for generic affine transforms in the plane. They include
38   * rotations, translations, shears, homotheties, and combinations of these. Such
39   * transformations can be constructed by using coefficients specification, or by
40   * creating specialized instances, by using static methods.
41   * <p>
42   */
43  public class AffineTransform2D implements Bijection2D, Cloneable {
44  
45      // coefficients for x coordinate.
46      protected double m00, m01, m02;
47  
48      // coefficients for y coordinate.
49      protected double m10, m11, m12;
50  
51      // ===================================================================
52      // static methods
53  
54      /**
55       * @since 0.8.1
56       */
57      public final static AffineTransform2D createIdentity() {
58          return new AffineTransform2D(1, 0, 0, 0, 1, 0);
59      }
60  
61      /** 
62       * Creates a new affine transform by copying coefficients.
63       * @since 0.8.1
64       */
65      public final static AffineTransform2D create(AffineTransform2D trans) {
66          double[][] mat = trans.getAffineMatrix();
67          return new AffineTransform2D(
68          		mat[0][0], mat[0][1], mat[0][2], 
69          		mat[1][0], mat[1][1], mat[1][2]);
70      }
71  
72      /**
73       * @since 0.8.1
74       */
75      public final static AffineTransform2D create(double[] coefs) {
76          if (coefs.length==4) {
77              return new AffineTransform2D(
78              		coefs[0], coefs[1], 0, 
79              		coefs[2], coefs[3], 0);
80          } else if (coefs.length==6) {
81              return new AffineTransform2D(
82              		coefs[0], coefs[1], coefs[2], 
83              		coefs[3], coefs[4], coefs[5]);
84          } else {
85          	throw new IllegalArgumentException();
86          }
87      }
88  
89      /**
90       * @since 0.8.1
91       */
92      public final static AffineTransform2D create(
93      		double xx, double yx, double tx,
94      		double xy, double yy, double ty) {
95      	return new AffineTransform2D(xx, yx, tx, xy, yy, ty);
96      }
97  
98      public final static AffineTransform2D createGlideReflection(
99              LinearShape2D line, double distance) {
100     	// get origin and vector of line
101         Vector2D vector = line.getVector().getNormalizedVector();
102         Point2D origin = line.getOrigin();
103         
104         // extract origin and vector coordinates
105         double dx = vector.getX();
106         double dy = vector.getY();
107         double x0 = origin.getX();
108         double y0 = origin.getY();
109 
110         // compute translation parameters
111         double tx = dx*distance;
112         double ty = dy*distance;
113 
114         // some computation shortcuts
115         double delta = dx*dx+dy*dy;
116         double dx2 = dx*dx;
117         double dy2 = dy*dy;
118         double dxy = dx*dy;
119         double dxy0 = dx*y0;
120         double dyx0 = dy*x0;
121         
122         // create the affine transform with parameters of glide reflection
123         return new AffineTransform2D(
124         		(dx2-dy2)/delta, 2*dxy/delta, 2*dy*(dyx0-dxy0)/delta+tx,
125         		2*dxy/delta, (dy2-dx2)/delta, 2*dx*(dxy0-dyx0)/delta+ty);
126     }
127 
128     public final static AffineTransform2D createHomothecy(Point2D center,
129             double k) {
130         return createScaling(center, k, k);
131     }
132 
133     public final static AffineTransform2D createLineReflection(
134             LinearShape2D line) {
135         Vector2D vector = line.getVector();
136         Point2D origin = line.getOrigin();
137         double dx = vector.getX();
138         double dy = vector.getY();
139         double x0 = origin.getX();
140         double y0 = origin.getY();
141         double delta = dx*dx+dy*dy;
142 
143         return new AffineTransform2D((dx*dx-dy*dy)/delta, 2*dx*dy/delta, 2*dy
144                 *(dy*x0-dx*y0)/delta, 2*dx*dy/delta, (dy*dy-dx*dx)/delta, 2*dx
145                 *(dx*y0-dy*x0)/delta);
146     }
147 
148     /**
149      * Return a point reflection centered on a point.
150      * 
151      * @param center the center of the reflection
152      * @return an instance of AffineTransform2D representing a point reflection
153      */
154     public final static AffineTransform2D createPointReflection(Point2D center) {
155         return AffineTransform2D.createScaling(center, -1, -1);
156     }
157 
158     public final static AffineTransform2D createQuadrantRotation(int numQuadrant) {
159         int n = ((numQuadrant%4)+4)%4;
160         switch (n) {
161         case 0:
162             return new AffineTransform2D(1, 0, 0, 0, 1, 0);
163         case 1:
164             return new AffineTransform2D(0, -1, 0, 1, 0, 0);
165         case 2:
166             return new AffineTransform2D(-1, 0, 0, 0, -1, 0);
167         case 3:
168             return new AffineTransform2D(0, 1, 0, -1, 0, 0);
169         default:
170             return new AffineTransform2D(1, 0, 0, 0, 1, 0);
171         }
172     }
173 
174     /**
175      * Return a rotation around the origin, with angle in radians.
176      */
177     public final static AffineTransform2D createRotation(double angle) {
178         return AffineTransform2D.createRotation(0, 0, angle);
179     }
180 
181     /**
182      * Return a rotation around the specified point, with angle in radians.
183      */
184     public final static AffineTransform2D createRotation(Point2D center,
185             double angle) {
186         return AffineTransform2D.createRotation(center.getX(), center.getY(),
187                 angle);
188     }
189 
190     /**
191      * Return a rotation around the specified point, with angle in radians. If
192      * the angular distance of the angle with a multiple of PI/2 is lower than
193      * the threshold Shape2D.ACCURACY, the method assumes equality.
194      */
195     public final static AffineTransform2D createRotation(double cx, double cy,
196             double angle) {
197         angle = Angle2D.formatAngle(angle);
198 
199         // coefficients of parameters m00, m01, m10 and m11.
200         double cot = 1, sit = 0;
201 
202         // special processing to detect angle close to multiple of PI/2.
203         int k = (int) Math.round(angle*2/Math.PI);
204         if (Math.abs(k*Math.PI/2-angle)<Shape2D.ACCURACY) {
205             assert k>=0 : "k should be positive";
206             assert k<5 : "k should be between 0 and 4";
207             switch (k) {
208             case 0:
209                 cot = 1;
210                 sit = 0;
211                 break;
212             case 1:
213                 cot = 0;
214                 sit = 1;
215                 break;
216             case 2:
217                 cot = -1;
218                 sit = 0;
219                 break;
220             case 3:
221                 cot = 0;
222                 sit = -1;
223                 break;
224             case 4:
225                 cot = 1;
226                 sit = 0;
227                 break;
228             }
229         } else {
230             cot = Math.cos(angle);
231             sit = Math.sin(angle);
232         }
233 
234         // init coef of the new AffineTransform.
235         return new AffineTransform2D(
236         		cot, -sit, (1-cot)*cx+sit*cy, 
237         		sit, cot,  (1-cot)*cy-sit*cx);
238     }
239 
240     /**
241      * Return a scaling by the given coefficients, centered on the origin.
242      */
243     public final static AffineTransform2D createScaling(double sx, double sy) {
244         return AffineTransform2D.createScaling(new Point2D(0, 0), sx, sy);
245     }
246 
247     /**
248      * Return a scaling by the given coefficients, centered on the given point.
249      */
250     public final static AffineTransform2D createScaling(Point2D center,
251             double sx, double sy) {
252         return new AffineTransform2D(sx, 0, (1-sx)*center.getX(), 0, sy, (1-sy)
253                 *center.getY());
254     }
255 
256     /**
257      * Creates a Shear transform, using the classical Java notation.
258      * 
259      * @param shx shear in x-axis
260      * @param shy shear in y-axis
261      * @return a shear transform
262      */
263     public final static AffineTransform2D createShear(double shx, double shy) {
264         return new AffineTransform2D(1, shx, 0, shy, 1, 0);
265     }
266 
267     /**
268      * Return a translation by the given vector.
269      */
270     public final static AffineTransform2D createTranslation(Vector2D vect) {
271         return new AffineTransform2D(1, 0, vect.getX(), 0, 1, vect.getY());
272     }
273 
274     /**
275      * Return a translation by the given vector.
276      */
277     public final static AffineTransform2D createTranslation(double dx, double dy) {
278         return new AffineTransform2D(1, 0, dx, 0, 1, dy);
279     }
280 
281     // ===================================================================
282     // methods to identify transforms
283 
284     /**
285      * Checks if the given transform is the identity transform.
286      */
287     public final static boolean isIdentity(AffineTransform2D trans) {
288         double[] coefs = trans.getCoefficients();
289         if (Math.abs(coefs[0]-1)>Shape2D.ACCURACY)
290             return false;
291         if (Math.abs(coefs[1])>Shape2D.ACCURACY)
292             return false;
293         if (Math.abs(coefs[2])>Shape2D.ACCURACY)
294             return false;
295         if (Math.abs(coefs[3])>Shape2D.ACCURACY)
296             return false;
297         if (Math.abs(coefs[4]-1)>Shape2D.ACCURACY)
298             return false;
299         if (Math.abs(coefs[5])>Shape2D.ACCURACY)
300             return false;
301         return true;
302     }
303 
304     /**
305      * Checks if the transform is direct, i.e. it preserves the orientation of
306      * transformed shapes.
307      * 
308      * @return true if transform is direct.
309      */
310     public final static boolean isDirect(AffineTransform2D trans) {
311         double[][] mat = trans.getAffineMatrix();
312         return mat[0][0]*mat[1][1]-mat[0][1]*mat[1][0]>0;
313     }
314 
315     /**
316      * Checks if the transform is an isometry, i.e. a compound of translation,
317      * rotation and reflection. Isometry keeps area of shapes unchanged, but can
318      * change orientation (directed or undirected).
319      * 
320      * @return true in case of isometry.
321      */
322     public final static boolean isIsometry(AffineTransform2D trans) {
323         // extract matrix coefficients
324         double[][] mat = trans.getAffineMatrix();
325         double a = mat[0][0];
326         double b = mat[0][1];
327         double d = mat[1][0];
328         double e = mat[1][1];
329 
330         // peforms some tests
331         if (Math.abs(a*a+d*d-1)>Shape2D.ACCURACY)
332             return false;
333         if (Math.abs(b*b+e*e-1)>Shape2D.ACCURACY)
334             return false;
335         if (Math.abs(a*b+d*e)>Shape2D.ACCURACY)
336             return false;
337 
338         // if all tests passed, return true;
339         return true;
340     }
341 
342     /**
343      * Checks if the transform is a motion, i.e. a compound of translations and
344      * rotation. Motion remains area and orientation (directed or undirected) of
345      * shapes unchanged.
346      * 
347      * @return true in case of motion.
348      */
349     public final static boolean isMotion(AffineTransform2D trans) {
350         double[][] mat = trans.getAffineMatrix();
351         double det = mat[0][0]*mat[1][1]-mat[0][1]*mat[1][0];
352         return Math.abs(det-1)<Shape2D.ACCURACY;
353     }
354 
355     /**
356      * Checks if the transform is an similarity, i.e. transformation which keeps
357      * unchanged the global shape, up to a scaling factor.
358      * 
359      * @return true in case of similarity.
360      */
361     public final static boolean isSimilarity(AffineTransform2D trans) {
362         double[][] mat = trans.getAffineMatrix();
363         // isolate linear part of the transform
364         double a = mat[0][0];
365         double b = mat[1][0];
366         double c = mat[0][1];
367         double d = mat[1][1];
368 
369         // determinant
370         double k2 = Math.abs(a*d-b*c);
371 
372         // test each condition
373         if (Math.abs(a*a+b*b-k2)>Shape2D.ACCURACY)
374             return false;
375         if (Math.abs(c*c+d*d-k2)>Shape2D.ACCURACY)
376             return false;
377         if (Math.abs(a*a+c*c-k2)>Shape2D.ACCURACY)
378             return false;
379         if (Math.abs(b*b+d*d-k2)>Shape2D.ACCURACY)
380             return false;
381 
382         // if each test passed, return true
383         return true;
384     }
385 
386     // ===================================================================
387     // Constructors
388 
389     /** Main constructor */
390     public AffineTransform2D() {
391         // init to identity matrix
392         m00 = m11 = 1;
393         m01 = m10 = 0;
394         m02 = m12 = 0;
395     }
396 
397     /** constructor by copy of an existing transform */
398     public AffineTransform2D(AffineTransform2D trans) {
399         double[][] mat = trans.getAffineMatrix();
400         this.m00 = mat[0][0];
401         this.m01 = mat[0][1];
402         this.m02 = mat[0][2];
403         this.m10 = mat[1][0];
404         this.m11 = mat[1][1];
405         this.m12 = mat[1][2];
406     }
407 
408     public AffineTransform2D(double[] coefs) {
409         if (coefs.length==4) {
410             m00 = coefs[0];
411             m01 = coefs[1];
412             m10 = coefs[2];
413             m11 = coefs[3];
414         } else {
415             m00 = coefs[0];
416             m01 = coefs[1];
417             m02 = coefs[2];
418             m10 = coefs[3];
419             m11 = coefs[4];
420             m12 = coefs[5];
421         }
422     }
423 
424     public AffineTransform2D(double xx, double yx, double tx, double xy,
425             double yy, double ty) {
426         m00 = xx;
427         m01 = yx;
428         m02 = tx;
429         m10 = xy;
430         m11 = yy;
431         m12 = ty;
432     }
433 
434     // ===================================================================
435     // methods specific to AffineTransform2D class
436 
437     /**
438      * Returns coefficients of the transform in a linear array of 6 double.
439      */
440     public double[] getCoefficients() {
441         double[] tab = { m00, m01, m02, m10, m11, m12 };
442         return tab;
443     }
444 
445     /**
446      * Returns the 3x3 square matrix representing the transform.
447      * 
448      * @return the 3x3 affine transform representing the matrix
449      */
450     public double[][] getAffineMatrix() {
451         double[][] tab = new double[][] { 
452         		new double[] { m00, m01, m02 },
453                 new double[] { m10, m11, m12 }, 
454                 new double[] { 0, 0, 1 } };
455         return tab;
456     }
457 
458     /**
459      * Return the affine transform created by applying first the affine
460      * transform given by <code>that</code>, then this affine transform.
461      * 
462      * @deprecated replaced by concatenate() method (0.6.3)
463      * @param that the transform to apply first
464      * @return the composition this * that
465      */
466     @Deprecated
467     public AffineTransform2D compose(AffineTransform2D that) {
468         return this.concatenate(that);
469     }
470 
471     /**
472      * Return the affine transform created by applying first the affine
473      * transform given by <code>that</code>, then this affine transform. This
474      * the equivalent method of the 'concatenate' method in
475      * java.awt.geom.AffineTransform.
476      * 
477      * @param that the transform to apply first
478      * @return the composition this * that
479      * @since 0.6.3
480      */
481     public AffineTransform2D concatenate(AffineTransform2D that) {
482         double[][] m2 = that.getAffineMatrix();
483         double n00 = this.m00*m2[0][0]+this.m01*m2[1][0];
484         double n01 = this.m00*m2[0][1]+this.m01*m2[1][1];
485         double n02 = this.m00*m2[0][2]+this.m01*m2[1][2]+this.m02;
486         double n10 = this.m10*m2[0][0]+this.m11*m2[1][0];
487         double n11 = this.m10*m2[0][1]+this.m11*m2[1][1];
488         double n12 = this.m10*m2[0][2]+this.m11*m2[1][2]+this.m12;
489         return new AffineTransform2D(n00, n01, n02, n10, n11, n12);
490     }
491 
492     /**
493      * Return the affine transform created by applying first this affine
494      * transform, then the affine transform given by <code>that</code>. This
495      * the equivalent method of the 'preConcatenate' method in
496      * java.awt.geom.AffineTransform. <code><pre>
497      * shape = shape.transform(T1.chain(T2).chain(T3));
498      * </pre></code> is equivalent to the sequence: <code><pre>
499      * shape = shape.transform(T1);
500      * shape = shape.transform(T2);
501      * shape = shape.transform(T3);
502      * </pre></code>
503      * 
504      * @param that the transform to apply in a second step
505      * @return the composition that * this
506      * @since 0.6.3
507      */
508     public AffineTransform2D chain(AffineTransform2D that) {
509         double[][] m2 = that.getAffineMatrix();
510         return new AffineTransform2D(
511                 m2[0][0]*this.m00+m2[0][1]*this.m10,
512                 m2[0][0]*this.m01+m2[0][1]*this.m11,
513                 m2[0][0]*this.m02+m2[0][1]*this.m12+m2[0][2],
514                 m2[1][0]*this.m00+m2[1][1]*this.m10, 
515                 m2[1][0]*this.m01+m2[1][1]*this.m11,
516                 m2[1][0]*this.m02+m2[1][1]*this.m12+m2[1][2]);
517     }
518 
519     /**
520      * Return the affine transform created by applying first this affine
521      * transform, then the affine transform given by <code>that</code>. This
522      * the equivalent method of the 'preConcatenate' method in
523      * java.awt.geom.AffineTransform.
524      * 
525      * @param that the transform to apply in a second step
526      * @return the composition that * this
527      * @since 0.6.3
528      */
529     public AffineTransform2D preConcatenate(AffineTransform2D that) {
530         return this.chain(that);
531     }
532 
533     // ===================================================================
534     // methods testing type of transform
535 
536     public boolean isSimilarity() {
537         return AffineTransform2D.isSimilarity(this);
538     }
539 
540     public boolean isMotion() {
541         return AffineTransform2D.isMotion(this);
542     }
543 
544     public boolean isIsometry() {
545         return AffineTransform2D.isIsometry(this);
546     }
547 
548     public boolean isDirect() {
549         return AffineTransform2D.isDirect(this);
550     }
551 
552     public boolean isIdentity() {
553         return AffineTransform2D.isIdentity(this);
554     }
555 
556     // ===================================================================
557     // implementations of Bijection2D methods
558 
559     /**
560      * Return the inverse transform. If the transform is not invertible, throws
561      * a new NonInvertibleTransformException.
562      * 
563      * @since 0.6.3
564      */
565     public AffineTransform2D invert() {
566         double det = m00*m11-m10*m01;
567 
568         if (Math.abs(det)<Shape2D.ACCURACY)
569             throw new NonInvertibleTransformException();
570 
571         return new AffineTransform2D(
572                 m11/det, -m01/det, (m01*m12-m02*m11)/det,
573                 -m10/det, m00/det, (m02*m10-m00*m12)/det);
574     }
575 
576     /**
577      * Return the inverse transform. If the transform is not invertible, throws
578      * a new NonInvertibleTransformException.
579      * 
580      * @deprecated use invert() method instead (0.6.3)
581      */
582     @Deprecated
583     public AffineTransform2D getInverseTransform() {
584         return this.invert();
585     }
586 
587     // ===================================================================
588     // implementations of Transform2D methods
589 
590     public Point2D[] transform(java.awt.geom.Point2D[] src, Point2D[] dst) {
591         if (dst==null)
592             dst = new Point2D[src.length];
593         if (dst[0]==null)
594             for (int i = 0; i<src.length; i++)
595                 dst[i] = new Point2D();
596 
597         double coef[] = getCoefficients();
598 
599         for (int i = 0; i<src.length; i++)
600             dst[i].setLocation(new Point2D(
601                     src[i].getX()*coef[0]+src[i].getY()*coef[1]+coef[2],
602                     src[i].getX()*coef[3]+src[i].getY()*coef[4]+coef[5]));
603         return dst;
604     }
605 
606     public Point2D transform(java.awt.geom.Point2D src) {
607         double coef[] = this.getCoefficients();
608         Point2D dst = new Point2D(
609                 src.getX()*coef[0]+src.getY()*coef[1]+coef[2], 
610                 src.getX()*coef[3]+src.getY()*coef[4]+coef[5]);
611         return dst;
612     }
613 
614     /**
615      * @deprecated use point.transform() instead. (0.7.0)
616      */
617     @Deprecated
618     public Point2D transform(java.awt.geom.Point2D src, Point2D dst) {
619         double coef[] = getCoefficients();
620         if (dst==null)
621             dst = new Point2D();
622         dst.setLocation(
623                 src.getX()*coef[0]+src.getY()*coef[1]+coef[2], 
624                 src.getX()*coef[3]+src.getY()*coef[4]+coef[5]);
625         return dst;
626     }
627 
628     // ===================================================================
629     // Override the Object methods
630 
631     /**
632      * Displays the coefficients of the transform, row by row.
633      */
634     @Override
635     public String toString() {
636         return new String("AffineTransform2D(" +
637         		m00 + "," + m01 + "," + m02 + "," + 
638         		m10 + "," + m11 + "," + m12 + "," );
639     }
640     
641     @Override
642     public boolean equals(Object obj) {
643         if (!(obj instanceof AffineTransform2D))
644             return false;
645 
646         double[] tab1 = this.getCoefficients();
647         double[] tab2 = ((AffineTransform2D) obj).getCoefficients();
648 
649         for (int i = 0; i<6; i++)
650             if (Math.abs(tab1[i]-tab2[i])>Shape2D.ACCURACY)
651                 return false;
652 
653         return true;
654     }
655     
656     @Override
657     public AffineTransform2D clone() {
658         return new AffineTransform2D(m00, m01, m02, m10, m11, m12);
659     }
660 }