1 /*
   2  * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.transform;
  27 
  28 import javafx.beans.property.DoubleProperty;
  29 import javafx.beans.property.DoublePropertyBase;
  30 import javafx.beans.property.ObjectProperty;
  31 import javafx.beans.property.ObjectPropertyBase;
  32 import javafx.geometry.Point3D;
  33 
  34 import com.sun.javafx.geom.transform.Affine3D;
  35 import com.sun.javafx.geom.transform.BaseTransform;
  36 import javafx.geometry.Point2D;
  37 
  38 
  39 /**
  40  * This class represents an {@code Affine} object that rotates coordinates
  41  * around an anchor point. This operation is equivalent to translating the
  42  * coordinates so that the anchor point is at the origin (S1), then rotating them
  43  * about the new origin (S2), and finally translating so that the
  44  * intermediate origin is restored to the coordinates of the original
  45  * anchor point (S3).
  46  * <p/>
  47  * For example, the matrix representing the returned transform of
  48  *    new Rotate (theta, x, y, z) around the Z-axis
  49  *
  50  * is :
  51  * <pre>
  52  *              [   cos(theta)    -sin(theta)   0    x-x*cos+y*sin  ]
  53  *              [   sin(theta)     cos(theta)   0    y-x*sin-y*cos  ]
  54  *              [      0               0        1          z        ]
  55  * </pre>
  56  * <p>
  57  * For example, to rotate a text 30 degrees around the Z-axis at
  58  * anchor point of (50,30):
  59  * <pre><code>
  60  * Text text = new Text("This is a test");
  61  * text.setX(10);
  62  * text.setY(50);
  63  * text.setFont(new Font(20));
  64  *
  65  * text.getTransforms().add(new Rotate(30, 50, 30));
  66  * </code></pre>
  67  * </p>
  68  * @since JavaFX 2.0
  69  */
  70 
  71 public class Rotate extends Transform {
  72 
  73     /**
  74      * Specifies the X-axis as the axis of rotation.
  75      */
  76     public static final Point3D X_AXIS = new Point3D(1,0,0);
  77 
  78     /**
  79      * Specifies the Y-axis as the axis of rotation.
  80      */
  81     public static final Point3D Y_AXIS = new Point3D(0,1,0);
  82 
  83     /**
  84      * Specifies the Z-axis as the axis of rotation.
  85      */
  86     public static final Point3D Z_AXIS = new Point3D(0,0,1);
  87 
  88     /**
  89      * Avoids lot of repeated computation.
  90      * @see #MatrixCache
  91      */
  92     private MatrixCache cache;
  93 
  94     /**
  95      * Avoids lot of repeated computation.
  96      * @see #MatrixCache
  97      */
  98     private MatrixCache inverseCache;
  99 
 100     /**
 101      * Creates a default Rotate transform (identity).
 102      */
 103     public Rotate() {
 104     }
 105 
 106     /**
 107      * Creates a two-dimensional Rotate transform.
 108      * The pivot point is set to (0,0)
 109      * @param angle the angle of rotation measured in degrees
 110      */
 111     public Rotate(double angle) {
 112         setAngle(angle);
 113     }
 114 
 115     /**
 116      * Creates a three-dimensional Rotate transform.
 117      * The pivot point is set to (0,0,0)
 118      * @param angle the angle of rotation measured in degrees
 119      * @param axis the axis of rotation
 120      */
 121     public Rotate(double angle, Point3D axis) {
 122         setAngle(angle);
 123         setAxis(axis);
 124     }
 125 
 126     /**
 127      * Creates a two-dimensional Rotate transform with pivot.
 128      * @param angle the angle of rotation measured in degrees
 129      * @param pivotX the X coordinate of the rotation pivot point
 130      * @param pivotY the Y coordinate of the rotation pivot point
 131      */
 132     public Rotate(double angle, double pivotX, double pivotY) {
 133         setAngle(angle);
 134         setPivotX(pivotX);
 135         setPivotY(pivotY);
 136     }
 137 
 138     /**
 139      * Creates a simple Rotate transform with three-dimensional pivot.
 140      * @param angle the angle of rotation measured in degrees
 141      * @param pivotX the X coordinate of the rotation pivot point
 142      * @param pivotY the Y coordinate of the rotation pivot point
 143      * @param pivotZ the Z coordinate of the rotation pivot point
 144      */
 145     public Rotate(double angle, double pivotX, double pivotY, double pivotZ) {
 146         this(angle, pivotX, pivotY);
 147         setPivotZ(pivotZ);
 148     }
 149 
 150     /**
 151      * Creates a three-dimensional Rotate transform with pivot.
 152      * @param angle the angle of rotation measured in degrees
 153      * @param pivotX the X coordinate of the rotation pivot point
 154      * @param pivotY the Y coordinate of the rotation pivot point
 155      * @param pivotZ the Z coordinate of the rotation pivot point
 156      * @param axis the axis of rotation
 157      */
 158     public Rotate(double angle, double pivotX, double pivotY, double pivotZ, Point3D axis) {
 159         this(angle, pivotX, pivotY);
 160         setPivotZ(pivotZ);
 161         setAxis(axis);
 162     }
 163 
 164     /**
 165      * Defines the angle of rotation measured in degrees.
 166      */
 167     private DoubleProperty angle;
 168 
 169 
 170     public final void setAngle(double value) {
 171         angleProperty().set(value);
 172     }
 173 
 174     public final double getAngle() {
 175         return angle == null ? 0.0 : angle.get();
 176     }
 177 
 178     public final DoubleProperty angleProperty() {
 179         if (angle == null) {
 180             angle = new DoublePropertyBase() {
 181 
 182                 @Override
 183                 public void invalidated() {
 184                     transformChanged();
 185                 }
 186 
 187                 @Override
 188                 public Object getBean() {
 189                     return Rotate.this;
 190                 }
 191 
 192                 @Override
 193                 public String getName() {
 194                     return "angle";
 195                 }
 196             };
 197         }
 198         return angle;
 199     }
 200 
 201     /**
 202      * Defines the X coordinate of the rotation pivot point.
 203      *
 204      * @defaultValue 0.0
 205      */
 206     private DoubleProperty pivotX;
 207 
 208 
 209     public final void setPivotX(double value) {
 210         pivotXProperty().set(value);
 211     }
 212 
 213     public final double getPivotX() {
 214         return pivotX == null ? 0.0 : pivotX.get();
 215     }
 216 
 217     public final DoubleProperty pivotXProperty() {
 218         if (pivotX == null) {
 219             pivotX = new DoublePropertyBase() {
 220 
 221                 @Override
 222                 public void invalidated() {
 223                     transformChanged();
 224                 }
 225 
 226                 @Override
 227                 public Object getBean() {
 228                     return Rotate.this;
 229                 }
 230 
 231                 @Override
 232                 public String getName() {
 233                     return "pivotX";
 234                 }
 235             };
 236         }
 237         return pivotX;
 238     }
 239 
 240     /**
 241      * Defines the Y coordinate of the rotation pivot point.
 242      *
 243      * @defaultValue 0.0
 244      */
 245     private DoubleProperty pivotY;
 246 
 247 
 248     public final void setPivotY(double value) {
 249         pivotYProperty().set(value);
 250     }
 251 
 252     public final double getPivotY() {
 253         return pivotY == null ? 0.0 : pivotY.get();
 254     }
 255 
 256     public final DoubleProperty pivotYProperty() {
 257         if (pivotY == null) {
 258             pivotY = new DoublePropertyBase() {
 259 
 260                 @Override
 261                 public void invalidated() {
 262                     transformChanged();
 263                 }
 264 
 265                 @Override
 266                 public Object getBean() {
 267                     return Rotate.this;
 268                 }
 269 
 270                 @Override
 271                 public String getName() {
 272                     return "pivotY";
 273                 }
 274             };
 275         }
 276         return pivotY;
 277     }
 278 
 279     /**
 280      * Defines the Z coordinate of the rotation pivot point.
 281      *
 282      * @defaultValue 0.0
 283      */
 284     private DoubleProperty pivotZ;
 285 
 286 
 287     public final void setPivotZ(double value) {
 288         pivotZProperty().set(value);
 289     }
 290 
 291     public final double getPivotZ() {
 292         return pivotZ == null ? 0.0 : pivotZ.get();
 293     }
 294 
 295     public final DoubleProperty pivotZProperty() {
 296         if (pivotZ == null) {
 297             pivotZ = new DoublePropertyBase() {
 298 
 299                 @Override
 300                 public void invalidated() {
 301                     transformChanged();
 302                 }
 303 
 304                 @Override
 305                 public Object getBean() {
 306                     return Rotate.this;
 307                 }
 308 
 309                 @Override
 310                 public String getName() {
 311                     return "pivotZ";
 312                 }
 313             };
 314         }
 315         return pivotZ;
 316     }
 317 
 318     /**
 319      * Defines the axis of rotation at the pivot point.
 320      */
 321     private ObjectProperty<Point3D> axis;
 322 
 323 
 324     public final void setAxis(Point3D value) {
 325         axisProperty().set(value);
 326     }
 327 
 328     public final Point3D getAxis() {
 329         return axis == null ? Z_AXIS : axis.get();
 330     }
 331 
 332     public final ObjectProperty<Point3D> axisProperty() {
 333         if (axis == null) {
 334             axis = new ObjectPropertyBase<Point3D>(Z_AXIS) {
 335 
 336                 @Override
 337                 public void invalidated() {
 338                     transformChanged();
 339                 }
 340 
 341                 @Override
 342                 public Object getBean() {
 343                     return Rotate.this;
 344                 }
 345 
 346                 @Override
 347                 public String getName() {
 348                     return "axis";
 349                 }
 350             };
 351         }
 352         return axis;
 353     }
 354 
 355     /* *************************************************************************
 356      *                                                                         *
 357      *                         Element getters                                 *
 358      *                                                                         *
 359      **************************************************************************/
 360 
 361     @Override
 362     public double getMxx() {
 363         updateCache();
 364         return cache.mxx;
 365     }
 366 
 367     @Override
 368     public double getMxy() {
 369         updateCache();
 370         return cache.mxy;
 371     }
 372 
 373     @Override
 374     public double getMxz() {
 375         updateCache();
 376         return cache.mxz;
 377     }
 378 
 379     @Override
 380     public double getTx() {
 381         updateCache();
 382         return cache.tx;
 383     }
 384 
 385     @Override
 386     public double getMyx() {
 387         updateCache();
 388         return cache.myx;
 389     }
 390 
 391     @Override
 392     public double getMyy() {
 393         updateCache();
 394         return cache.myy;
 395     }
 396 
 397     @Override
 398     public double getMyz() {
 399         updateCache();
 400         return cache.myz;
 401     }
 402 
 403     @Override
 404     public double getTy() {
 405         updateCache();
 406         return cache.ty;
 407     }
 408 
 409     @Override
 410     public double getMzx() {
 411         updateCache();
 412         return cache.mzx;
 413     }
 414 
 415     @Override
 416     public double getMzy() {
 417         updateCache();
 418         return cache.mzy;
 419     }
 420 
 421     @Override
 422     public double getMzz() {
 423         updateCache();
 424         return cache.mzz;
 425     }
 426 
 427     @Override
 428     public double getTz() {
 429         updateCache();
 430         return cache.tz;
 431     }
 432 
 433     /* *************************************************************************
 434      *                                                                         *
 435      *                           State getters                                 *
 436      *                                                                         *
 437      **************************************************************************/
 438 
 439     @Override
 440     boolean computeIs2D() {
 441         final Point3D a = getAxis();
 442         return (a.getX() == 0.0 && a.getY() == 0.0) || getAngle() == 0;
 443     }
 444 
 445     @Override
 446     boolean computeIsIdentity() {
 447         if (getAngle() == 0.0) {
 448             return true;
 449         }
 450 
 451         final Point3D a = getAxis();
 452         return a.getX() == 0 && a.getY() == 0 && a.getZ() == 0.0;
 453     }
 454 
 455     /* *************************************************************************
 456      *                                                                         *
 457      *                           Array getters                                 *
 458      *                                                                         *
 459      **************************************************************************/
 460 
 461     @Override
 462     void fill2DArray(double[] array) {
 463         updateCache();
 464         array[0] = cache.mxx;
 465         array[1] = cache.mxy;
 466         array[2] = cache.tx;
 467         array[3] = cache.myx;
 468         array[4] = cache.myy;
 469         array[5] = cache.ty;
 470     }
 471 
 472     @Override
 473     void fill3DArray(double[] array) {
 474         updateCache();
 475         array[0] = cache.mxx;
 476         array[1] = cache.mxy;
 477         array[2] = cache.mxz;
 478         array[3] = cache.tx;
 479         array[4] = cache.myx;
 480         array[5] = cache.myy;
 481         array[6] = cache.myz;
 482         array[7] = cache.ty;
 483         array[8] = cache.mzx;
 484         array[9] = cache.mzy;
 485         array[10] = cache.mzz;
 486         array[11] = cache.tz;
 487         return;
 488     }
 489 
 490     /* *************************************************************************
 491      *                                                                         *
 492      *                         Transform creators                              *
 493      *                                                                         *
 494      **************************************************************************/
 495 
 496     @Override
 497     public Transform createConcatenation(Transform transform) {
 498         if (transform instanceof Rotate) {
 499             Rotate r = (Rotate) transform;
 500             final double px = getPivotX();
 501             final double py = getPivotY();
 502             final double pz = getPivotZ();
 503 
 504             if ((r.getAxis() == getAxis() ||
 505                         r.getAxis().normalize().equals(getAxis().normalize())) &&
 506                     px == r.getPivotX() &&
 507                     py == r.getPivotY() &&
 508                     pz == r.getPivotZ()) {
 509                 return new Rotate(getAngle() + r.getAngle(), px, py, pz, getAxis());
 510             }
 511         }
 512 
 513         if (transform instanceof Affine) {
 514             Affine a = (Affine) transform.clone();
 515             a.prepend(this);
 516             return a;
 517         }
 518 
 519         return super.createConcatenation(transform);
 520     }
 521 
 522     @Override
 523     public Transform createInverse() throws NonInvertibleTransformException {
 524         return new Rotate(-getAngle(), getPivotX(), getPivotY(), getPivotZ(),
 525                 getAxis());
 526     }
 527 
 528     @Override
 529     public Rotate clone() {
 530         return new Rotate(getAngle(), getPivotX(), getPivotY(), getPivotZ(),
 531                 getAxis());
 532     }
 533 
 534     /* *************************************************************************
 535      *                                                                         *
 536      *                     Transform, Inverse Transform                        *
 537      *                                                                         *
 538      **************************************************************************/
 539 
 540     @Override
 541     public Point2D transform(double x, double y) {
 542         ensureCanTransform2DPoint();
 543 
 544         updateCache();
 545 
 546         return new Point2D(
 547             cache.mxx * x + cache.mxy * y + cache.tx,
 548             cache.myx * x + cache.myy * y + cache.ty);
 549     }
 550 
 551     @Override
 552     public Point3D transform(double x, double y, double z) {
 553         updateCache();
 554 
 555         return new Point3D(
 556             cache.mxx * x + cache.mxy * y + cache.mxz * z + cache.tx,
 557             cache.myx * x + cache.myy * y + cache.myz * z + cache.ty,
 558             cache.mzx * x + cache.mzy * y + cache.mzz * z + cache.tz);
 559     }
 560 
 561     @Override
 562     void transform2DPointsImpl(double[] srcPts, int srcOff,
 563             double[] dstPts, int dstOff, int numPts) {
 564         updateCache();
 565 
 566         while (--numPts >= 0) {
 567             final double x = srcPts[srcOff++];
 568             final double y = srcPts[srcOff++];
 569 
 570             dstPts[dstOff++] = cache.mxx * x + cache.mxy * y + cache.tx;
 571             dstPts[dstOff++] = cache.myx * x + cache.myy * y + cache.ty;
 572         }
 573     }
 574 
 575     @Override
 576     void transform3DPointsImpl(double[] srcPts, int srcOff,
 577             double[] dstPts, int dstOff, int numPts) {
 578 
 579         updateCache();
 580 
 581         while (--numPts >= 0) {
 582             final double x = srcPts[srcOff++];
 583             final double y = srcPts[srcOff++];
 584             final double z = srcPts[srcOff++];
 585 
 586             dstPts[dstOff++] = cache.mxx * x + cache.mxy * y + cache.mxz * z + cache.tx;
 587             dstPts[dstOff++] = cache.myx * x + cache.myy * y + cache.myz * z + cache.ty;
 588             dstPts[dstOff++] = cache.mzx * x + cache.mzy * y + cache.mzz * z + cache.tz;
 589         }
 590     }
 591 
 592     @Override
 593     public Point2D deltaTransform(double x, double y) {
 594         ensureCanTransform2DPoint();
 595 
 596         updateCache();
 597 
 598         return new Point2D(
 599             cache.mxx * x + cache.mxy * y,
 600             cache.myx * x + cache.myy * y);
 601     }
 602 
 603     @Override
 604     public Point3D deltaTransform(double x, double y, double z) {
 605         updateCache();
 606 
 607         return new Point3D(
 608             cache.mxx * x + cache.mxy * y + cache.mxz * z,
 609             cache.myx * x + cache.myy * y + cache.myz * z,
 610             cache.mzx * x + cache.mzy * y + cache.mzz * z);
 611     }
 612 
 613     @Override
 614     public Point2D inverseTransform(double x, double y) {
 615         ensureCanTransform2DPoint();
 616 
 617         updateInverseCache();
 618 
 619         return new Point2D(
 620             inverseCache.mxx * x + inverseCache.mxy * y + inverseCache.tx,
 621             inverseCache.myx * x + inverseCache.myy * y + inverseCache.ty);
 622     }
 623 
 624     @Override
 625     public Point3D inverseTransform(double x, double y, double z) {
 626         updateInverseCache();
 627 
 628         return new Point3D(
 629             inverseCache.mxx * x + inverseCache.mxy * y + inverseCache.mxz * z
 630                 + inverseCache.tx,
 631             inverseCache.myx * x + inverseCache.myy * y + inverseCache.myz * z
 632                 + inverseCache.ty,
 633             inverseCache.mzx * x + inverseCache.mzy * y + inverseCache.mzz * z
 634                 + inverseCache.tz);
 635     }
 636 
 637     @Override
 638     void inverseTransform2DPointsImpl(double[] srcPts, int srcOff,
 639             double[] dstPts, int dstOff, int numPts) {
 640         updateInverseCache();
 641 
 642         while (--numPts >= 0) {
 643             final double x = srcPts[srcOff++];
 644             final double y = srcPts[srcOff++];
 645 
 646             dstPts[dstOff++] = inverseCache.mxx * x + inverseCache.mxy * y
 647                     + inverseCache.tx;
 648             dstPts[dstOff++] = inverseCache.myx * x + inverseCache.myy * y
 649                     + inverseCache.ty;
 650         }
 651     }
 652 
 653     @Override
 654     void inverseTransform3DPointsImpl(double[] srcPts, int srcOff,
 655             double[] dstPts, int dstOff, int numPts) {
 656 
 657         updateInverseCache();
 658 
 659         while (--numPts >= 0) {
 660             final double x = srcPts[srcOff++];
 661             final double y = srcPts[srcOff++];
 662             final double z = srcPts[srcOff++];
 663 
 664             dstPts[dstOff++] = inverseCache.mxx * x + inverseCache.mxy * y
 665                     + inverseCache.mxz * z + inverseCache.tx;
 666             dstPts[dstOff++] = inverseCache.myx * x + inverseCache.myy * y
 667                     + inverseCache.myz * z + inverseCache.ty;
 668             dstPts[dstOff++] = inverseCache.mzx * x + inverseCache.mzy * y
 669                     + inverseCache.mzz * z + inverseCache.tz;
 670         }
 671     }
 672 
 673     @Override
 674     public Point2D inverseDeltaTransform(double x, double y) {
 675         ensureCanTransform2DPoint();
 676 
 677         updateInverseCache();
 678 
 679         return new Point2D(
 680             inverseCache.mxx * x + inverseCache.mxy * y,
 681             inverseCache.myx * x + inverseCache.myy * y);
 682     }
 683 
 684     @Override
 685     public Point3D inverseDeltaTransform(double x, double y, double z) {
 686         updateInverseCache();
 687 
 688         return new Point3D(
 689             inverseCache.mxx * x + inverseCache.mxy * y + inverseCache.mxz * z,
 690             inverseCache.myx * x + inverseCache.myy * y + inverseCache.myz * z,
 691             inverseCache.mzx * x + inverseCache.mzy * y + inverseCache.mzz * z);
 692     }
 693 
 694     /* *************************************************************************
 695      *                                                                         *
 696      *                               Other API                                 *
 697      *                                                                         *
 698      **************************************************************************/
 699 
 700     /**
 701      * Returns a string representation of this {@code Rotate} object.
 702      * @return a string representation of this {@code Rotate} object.
 703      */
 704     @Override
 705     public String toString() {
 706         final StringBuilder sb = new StringBuilder("Rotate [");
 707 
 708         sb.append("angle=").append(getAngle());
 709         sb.append(", pivotX=").append(getPivotX());
 710         sb.append(", pivotY=").append(getPivotY());
 711         sb.append(", pivotZ=").append(getPivotZ());
 712         sb.append(", axis=").append(getAxis());
 713 
 714         return sb.append("]").toString();
 715     }
 716 
 717     /* *************************************************************************
 718      *                                                                         *
 719      *                    Internal implementation stuff                        *
 720      *                                                                         *
 721      **************************************************************************/
 722 
 723     @Override
 724     void apply(final Affine3D trans) {
 725         double localPivotX = getPivotX();
 726         double localPivotY = getPivotY();
 727         double localPivotZ = getPivotZ();
 728         double localAngle = getAngle();
 729 
 730         if (localPivotX != 0 || localPivotY != 0 || localPivotZ != 0) {
 731             trans.translate(localPivotX, localPivotY, localPivotZ);
 732             trans.rotate(Math.toRadians(localAngle),
 733                          getAxis().getX(),getAxis().getY(), getAxis().getZ());
 734             trans.translate(-localPivotX, -localPivotY, -localPivotZ);
 735         } else {
 736             trans.rotate(Math.toRadians(localAngle),
 737                          getAxis().getX(), getAxis().getY(), getAxis().getZ());
 738         }
 739     }
 740 
 741     @Override
 742     BaseTransform derive(BaseTransform trans) {
 743         if (isIdentity()) {
 744             return trans;
 745         }
 746 
 747         double localPivotX = getPivotX();
 748         double localPivotY = getPivotY();
 749         double localPivotZ = getPivotZ();
 750         double localAngle = getAngle();
 751 
 752         if (localPivotX != 0 || localPivotY != 0 || localPivotZ != 0) {
 753             trans = trans.deriveWithTranslation(localPivotX, localPivotY, localPivotZ);
 754             trans = trans.deriveWithRotation(Math.toRadians(localAngle),
 755                          getAxis().getX(),getAxis().getY(), getAxis().getZ());
 756             return trans.deriveWithTranslation(-localPivotX, -localPivotY, -localPivotZ);
 757         } else {
 758             return trans.deriveWithRotation(Math.toRadians(localAngle),
 759                          getAxis().getX(), getAxis().getY(), getAxis().getZ());
 760         }
 761     }
 762 
 763     @Override
 764     void validate() {
 765         getAxis();
 766         getAngle();
 767         getPivotX();
 768         getPivotY();
 769         getPivotZ();
 770     }
 771 
 772     @Override
 773     protected void transformChanged() {
 774         if (cache != null) {
 775             cache.invalidate();
 776         }
 777         super.transformChanged();
 778     }
 779 
 780     @Override
 781     void appendTo(Affine a) {
 782         a.appendRotation(getAngle(), getPivotX(), getPivotY(), getPivotZ(),
 783                 getAxis());
 784     }
 785 
 786     @Override
 787     void prependTo(Affine a) {
 788         a.prependRotation(getAngle(), getPivotX(), getPivotY(), getPivotZ(),
 789                 getAxis());
 790     }
 791 
 792     /**
 793      * Updates the matrix cache
 794      */
 795     private void updateCache() {
 796         if (cache == null) {
 797             cache = new MatrixCache();
 798         }
 799 
 800         if (!cache.valid) {
 801             cache.update(getAngle(), getAxis(),
 802                     getPivotX(), getPivotY(), getPivotZ());
 803         }
 804     }
 805 
 806     /**
 807      * Updates the inverse matrix cache
 808      */
 809     private void updateInverseCache() {
 810         if (inverseCache == null) {
 811             inverseCache = new MatrixCache();
 812         }
 813 
 814         if (!inverseCache.valid) {
 815             inverseCache.update(-getAngle(), getAxis(),
 816                     getPivotX(), getPivotY(), getPivotZ());
 817         }
 818     }
 819 
 820     /**
 821      * Matrix cache. Computing single transformation matrix elements for
 822      * a general rotation is quite expensive. Also each of those partial
 823      * computations need some common operations to be made (compute sin
 824      * and cos, normalize axis). Therefore with the direct element computations
 825      * if all the getters for the elements are called to get the matrix,
 826      * the result is slow.
 827      *
 828      * If a matrix element is asked for, we can reasonably anticipate that
 829      * some other elements will be asked for as well. So when any element
 830      * needs to be computed, we compute the entire matrix, cache it,
 831      * and use the stored values until the transform changes.
 832      */
 833     private static class MatrixCache {
 834         boolean valid = false;
 835         boolean is3D = false;
 836 
 837         double mxx, mxy, mxz, tx,
 838                myx, myy, myz, ty,
 839                mzx, mzy, mzz, tz;
 840 
 841         public MatrixCache() {
 842             // to have the 3D part right when using 2D-only
 843             mzz = 1.0;
 844         }
 845 
 846         public void update(double angle, Point3D axis,
 847                 double px, double py, double pz) {
 848 
 849             final double rads = Math.toRadians(angle);
 850             final double sin = Math.sin(rads);
 851             final double cos = Math.cos(rads);
 852 
 853             if (axis == Z_AXIS ||
 854                     (axis.getX() == 0.0 &&
 855                      axis.getY() == 0.0 &&
 856                      axis.getZ() > 0.0)) {
 857                 // 2D case
 858                 mxx = cos;
 859                 mxy = -sin;
 860                 tx = px * (1 - cos) + py * sin;
 861                 myx = sin;
 862                 myy = cos;
 863                 ty = py * (1 - cos) - px * sin;
 864 
 865                 if (is3D) {
 866                     // Was 3D, needs to set the 3D values
 867                     mxz = 0.0;
 868                     myz = 0.0;
 869                     mzx = 0.0;
 870                     mzy = 0.0;
 871                     mzz = 1.0;
 872                     tz = 0.0;
 873                     is3D = false;
 874                 }
 875                 valid = true;
 876                 return;
 877             }
 878             // 3D case
 879             is3D = true;
 880 
 881             double axisX, axisY, axisZ;
 882 
 883             if (axis == X_AXIS || axis == Y_AXIS || axis == Z_AXIS) {
 884                 axisX = axis.getX();
 885                 axisY = axis.getY();
 886                 axisZ = axis.getZ();
 887             } else {
 888                 // normalize
 889                 final double mag = Math.sqrt(axis.getX() * axis.getX() +
 890                         axis.getY() * axis.getY() + axis.getZ() * axis.getZ());
 891 
 892                 if (mag == 0.0) {
 893                     mxx = 1; mxy = 0; mxz = 0; tx = 0;
 894                     myx = 0; myy = 1; myz = 0; ty = 0;
 895                     mzx = 0; mzy = 0; mzz = 1; tz = 0;
 896                     valid = true;
 897                     return;
 898                 } else {
 899                     axisX = axis.getX() / mag;
 900                     axisY = axis.getY() / mag;
 901                     axisZ = axis.getZ() / mag;
 902                 }
 903             }
 904 
 905             mxx = cos + axisX * axisX * (1 - cos);
 906             mxy = axisX * axisY * (1 - cos) - axisZ * sin;
 907             mxz = axisX * axisZ * (1 - cos) + axisY * sin;
 908             tx = px * (1 - mxx) - py * mxy - pz * mxz;
 909 
 910             myx = axisY * axisX * (1 - cos) + axisZ * sin;
 911             myy = cos + axisY * axisY * (1 - cos);
 912             myz = axisY * axisZ * (1 - cos) - axisX * sin;
 913             ty = py * (1 - myy) - px * myx - pz * myz;
 914 
 915             mzx = axisZ * axisX * (1 - cos) - axisY * sin;
 916             mzy = axisZ * axisY * (1 - cos) + axisX * sin;
 917             mzz = cos + axisZ * axisZ * (1 - cos);
 918             tz = pz * (1 - mzz) - px * mzx - py * mzy;
 919 
 920             valid = true;
 921         }
 922 
 923         public void invalidate() {
 924             valid = false;
 925         }
 926     }
 927 }