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