1 /* 2 * Copyright (c) 2011, 2015, 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 java.util.Iterator; 29 30 import com.sun.javafx.geometry.BoundsUtils; 31 import javafx.event.EventDispatchChain; 32 33 import javafx.scene.Node; 34 35 import com.sun.javafx.util.WeakReferenceQueue; 36 import com.sun.javafx.binding.ExpressionHelper; 37 import com.sun.javafx.event.EventHandlerManager; 38 import com.sun.javafx.geom.transform.Affine2D; 39 import com.sun.javafx.geom.transform.Affine3D; 40 import com.sun.javafx.geom.transform.AffineBase; 41 import com.sun.javafx.geom.transform.BaseTransform; 42 import com.sun.javafx.scene.transform.TransformUtils; 43 import java.lang.ref.SoftReference; 44 import javafx.beans.InvalidationListener; 45 import javafx.beans.property.ObjectProperty; 46 import javafx.beans.property.ReadOnlyBooleanProperty; 47 import javafx.beans.property.SimpleObjectProperty; 48 import javafx.beans.value.ChangeListener; 49 import javafx.event.Event; 50 import javafx.event.EventHandler; 51 import javafx.event.EventTarget; 52 import javafx.event.EventType; 53 import javafx.geometry.BoundingBox; 54 import javafx.geometry.Bounds; 55 import javafx.geometry.Point2D; 56 import javafx.geometry.Point3D; 57 58 // PENDING_DOC_REVIEW of this whole class 59 /** 60 * This class is a base class for different affine transformations. 61 * It provides factory methods for the simple transformations - rotating, 62 * scaling, shearing, and translation. It allows to get the transformation 63 * matrix elements for any transform. 64 * 65 * <p>Example:</p> 66 * 67 * <pre><code> 68 * Rectangle rect = new Rectangle(50,50, Color.RED); 69 * rect.getTransforms().add(new Rotate(45,0,0)); //rotate by 45 degrees 70 * </code></pre> 71 * @since JavaFX 2.0 72 */ 73 public abstract class Transform implements Cloneable, EventTarget { 74 75 /* ************************************************************************* 76 * * 77 * Factories * 78 * * 79 **************************************************************************/ 80 81 /** 82 * Returns a new {@code Affine} object from 12 number 83 * values representing the 6 specifiable entries of the 3x4 84 * Affine transformation matrix. 85 * 86 * @param mxx the X coordinate scaling element of the 3x4 matrix 87 * @param myx the Y coordinate shearing element of the 3x4 matrix 88 * @param mxy the X coordinate shearing element of the 3x4 matrix 89 * @param myy the Y coordinate scaling element of the 3x4 matrix 90 * @param tx the X coordinate translation element of the 3x4 matrix 91 * @param ty the Y coordinate translation element of the 3x4 matrix 92 * @return a new {@code Affine} object derived from specified parameters 93 */ 94 public static Affine affine( 95 double mxx, double myx, double mxy, double myy, double tx, double ty) { 96 final Affine affine = new Affine(); 97 affine.setMxx(mxx); 98 affine.setMxy(mxy); 99 affine.setTx(tx); 100 affine.setMyx(myx); 101 affine.setMyy(myy); 102 affine.setTy(ty); 103 return affine; 104 } 105 106 107 /** 108 * Returns a new {@code Affine} object from 12 number 109 * values representing the 12 specifiable entries of the 3x4 110 * Affine transformation matrix. 111 * 112 * @param mxx the X coordinate scaling element of the 3x4 matrix 113 * @param mxy the XY element of the 3x4 matrix 114 * @param mxz the XZ element of the 3x4 matrix 115 * @param tx the X coordinate translation element of the 3x4 matrix 116 * @param myx the YX element of the 3x4 matrix 117 * @param myy the Y coordinate scaling element of the 3x4 matrix 118 * @param myz the YZ element of the 3x4 matrix 119 * @param ty the Y coordinate translation element of the 3x4 matrix 120 * @param mzx the ZX element of the 3x4 matrix 121 * @param mzy the ZY element of the 3x4 matrix 122 * @param mzz the Z coordinate scaling element of the 3x4 matrix 123 * @param tz the Z coordinate translation element of the 3x4 matrix 124 * @return a new {@code Affine} object derived from specified parameters 125 */ 126 public static Affine affine( 127 double mxx, double mxy, double mxz, double tx, 128 double myx, double myy, double myz, double ty, 129 double mzx, double mzy, double mzz, double tz) { 130 final Affine affine = new Affine(); 131 affine.setMxx(mxx); 132 affine.setMxy(mxy); 133 affine.setMxz(mxz); 134 affine.setTx(tx); 135 affine.setMyx(myx); 136 affine.setMyy(myy); 137 affine.setMyz(myz); 138 affine.setTy(ty); 139 affine.setMzx(mzx); 140 affine.setMzy(mzy); 141 affine.setMzz(mzz); 142 affine.setTz(tz); 143 return affine; 144 } 145 146 147 /** 148 * Returns a {@code Translate} object representing a translation transformation. 149 * <p> 150 * This is equivalent to: 151 * <pre> 152 * new Translate(x, y); 153 * </pre> 154 */ 155 public static Translate translate(double x, double y) { 156 final Translate translate = new Translate(); 157 translate.setX(x); 158 translate.setY(y); 159 return translate; 160 } 161 162 163 /** 164 * Returns a {@code Rotate} object that rotates coordinates around a pivot 165 * point. 166 * <p> 167 * This is equivalent to: 168 * <pre> 169 * new Rotate(angle, pivotX, pivotY); 170 * </pre> 171 */ 172 public static Rotate rotate(double angle, double pivotX, double pivotY) { 173 final Rotate rotate = new Rotate(); 174 rotate.setAngle(angle); 175 rotate.setPivotX(pivotX); 176 rotate.setPivotY(pivotY); 177 return rotate; 178 } 179 180 181 /** 182 * Returns a {@code Scale} object representing a scaling transformation. 183 * <p> 184 * This is equivalent to: 185 * <pre> 186 * new Scale(x, y); 187 * </pre> 188 */ 189 public static Scale scale(double x, double y) { 190 final Scale scale = new Scale(); 191 scale.setX(x); 192 scale.setY(y); 193 return scale; 194 } 195 196 197 /** 198 * Returns a {@code Scale} object representing a scaling transformation. 199 * The returned scale operation will be about the given pivot point. 200 * <p> 201 * This is equivalent to: 202 * <pre> 203 * new Scale(x, y, pivotX, pivotY); 204 * </pre> 205 */ 206 public static Scale scale(double x, double y, double pivotX, double pivotY) { 207 final Scale scale = new Scale(); 208 scale.setX(x); 209 scale.setY(y); 210 scale.setPivotX(pivotX); 211 scale.setPivotY(pivotY); 212 return scale; 213 } 214 215 216 /** 217 * Returns a {@code Shear} object representing a shearing transformation. 218 * <p> 219 * This is equivalent to: 220 * <pre> 221 * new Shear(x, y); 222 * </pre> 223 */ 224 public static Shear shear(double x, double y) { 225 final Shear shear = new Shear(); 226 shear.setX(x); 227 shear.setY(y); 228 return shear; 229 } 230 231 /** 232 * Returns a {@code Shear} object representing a shearing transformation. 233 * <p> 234 * This is equivalent to: 235 * <pre> 236 * new Shear(x, y, pivotX, pivotY); 237 * </pre> 238 */ 239 public static Shear shear(double x, double y, double pivotX, double pivotY) { 240 final Shear shear = new Shear(); 241 shear.setX(x); 242 shear.setY(y); 243 shear.setPivotX(pivotX); 244 shear.setPivotY(pivotY); 245 return shear; 246 } 247 248 /** 249 * For transforms with expensive inversion we cache the inverted matrix 250 * once it is needed and computed for some operation. 251 */ 252 private SoftReference<Transform> inverseCache = null; 253 254 private WeakReferenceQueue impl_nodes = new WeakReferenceQueue(); 255 256 /* ************************************************************************* 257 * * 258 * Element getters * 259 * * 260 **************************************************************************/ 261 262 /** 263 * Gets the X coordinate scaling element of the 3x4 matrix. 264 * 265 * @since JavaFX 2.2 266 */ 267 public double getMxx() { 268 return 1.0; 269 } 270 271 /** 272 * Gets the XY coordinate element of the 3x4 matrix. 273 * 274 * @since JavaFX 2.2 275 */ 276 public double getMxy() { 277 return 0.0; 278 } 279 280 /** 281 * Gets the XZ coordinate element of the 3x4 matrix. 282 * 283 * @since JavaFX 2.2 284 */ 285 public double getMxz() { 286 return 0.0; 287 } 288 289 /** 290 * Gets the X coordinate translation element of the 3x4 matrix. 291 * 292 * @since JavaFX 2.2 293 */ 294 public double getTx() { 295 return 0.0; 296 } 297 298 /** 299 * Gets the YX coordinate element of the 3x4 matrix. 300 * 301 * @since JavaFX 2.2 302 */ 303 public double getMyx() { 304 return 0.0; 305 } 306 307 /** 308 * Gets the Y coordinate scaling element of the 3x4 matrix. 309 * 310 * @since JavaFX 2.2 311 */ 312 public double getMyy() { 313 return 1.0; 314 } 315 316 /** 317 * Gets the YZ coordinate element of the 3x4 matrix. 318 * 319 * @since JavaFX 2.2 320 */ 321 public double getMyz() { 322 return 0.0; 323 } 324 325 /** 326 * Gets the Y coordinate translation element of the 3x4 matrix. 327 * 328 * @since JavaFX 2.2 329 */ 330 public double getTy() { 331 return 0.0; 332 } 333 334 /** 335 * Gets the ZX coordinate element of the 3x4 matrix. 336 * 337 * @since JavaFX 2.2 338 */ 339 public double getMzx() { 340 return 0.0; 341 } 342 343 /** 344 * Gets the ZY coordinate element of the 3x4 matrix. 345 * 346 * @since JavaFX 2.2 347 */ 348 public double getMzy() { 349 return 0.0; 350 } 351 352 /** 353 * Gets the Z coordinate scaling element of the 3x4 matrix. 354 * 355 * @since JavaFX 2.2 356 */ 357 public double getMzz() { 358 return 1.0; 359 } 360 361 /** 362 * Gets the Z coordinate translation element of the 3x4 matrix. 363 * 364 * @since JavaFX 2.2 365 */ 366 public double getTz() { 367 return 0.0; 368 } 369 370 /** 371 * Gets the specified element of the transformation matrix. 372 * @param type type of matrix to get the value from 373 * @param row zero-based row number 374 * @param column zero-based column number 375 * @return value of the specified transformation matrix element 376 * @throws IllegalArgumentException if a 2D matrix type is requested for 377 * a 3D transform 378 * @throws IndexOutOfBoundsException if the indices are not within 379 * the specified matrix type 380 * @throws NullPointerException if the specified {@code type} is null 381 * @since JavaFX 8.0 382 */ 383 public double getElement(MatrixType type, int row, int column) { 384 if (row < 0 || row >= type.rows() || column < 0 || column >= type.columns()) { 385 throw new IndexOutOfBoundsException("Index outside of affine " 386 + "matrix " + type + ": [" + row + ", " + column + "]"); 387 } 388 switch(type) { 389 case MT_2D_2x3: 390 // fall-through 391 case MT_2D_3x3: 392 if (!isType2D()) { 393 throw new IllegalArgumentException("Cannot access 2D matrix " 394 + "of a 3D transform"); 395 } 396 switch(row) { 397 case 0: 398 switch(column) { 399 case 0: return getMxx(); 400 case 1: return getMxy(); 401 case 2: return getTx(); 402 } 403 case 1: 404 switch(column) { 405 case 0: return getMyx(); 406 case 1: return getMyy(); 407 case 2: return getTy(); 408 } 409 case 2: 410 switch(column) { 411 case 0: return 0.0; 412 case 1: return 0.0; 413 case 2: return 1.0; 414 } 415 } 416 break; 417 case MT_3D_3x4: 418 // fall-through 419 case MT_3D_4x4: 420 switch(row) { 421 case 0: 422 switch(column) { 423 case 0: return getMxx(); 424 case 1: return getMxy(); 425 case 2: return getMxz(); 426 case 3: return getTx(); 427 } 428 case 1: 429 switch(column) { 430 case 0: return getMyx(); 431 case 1: return getMyy(); 432 case 2: return getMyz(); 433 case 3: return getTy(); 434 } 435 case 2: 436 switch(column) { 437 case 0: return getMzx(); 438 case 1: return getMzy(); 439 case 2: return getMzz(); 440 case 3: return getTz(); 441 } 442 case 3: 443 switch(column) { 444 case 0: return 0.0; 445 case 1: return 0.0; 446 case 2: return 0.0; 447 case 3: return 1.0; 448 } 449 } 450 break; 451 } 452 // cannot reach here 453 throw new InternalError("Unsupported matrix type " + type); 454 } 455 456 /* ************************************************************************* 457 * * 458 * State getters * 459 * * 460 **************************************************************************/ 461 462 /** 463 * Computes if this transform is currently a 2D transform (has no effect 464 * in the direction of Z axis). 465 * Used by the subclasses to effectively provide value of the type2D 466 * property. 467 * @return true if this transform is currently 2D-only 468 */ 469 boolean computeIs2D() { 470 return getMxz() == 0.0 && getMzx() == 0.0 && getMzy() == 0.0 && 471 getMzz() == 1.0 && getTz() == 0.0; 472 } 473 474 /** 475 * Computes if this transform is currently an identity (has 476 * no effect in any direction). 477 * Used by the subclasses to effectively provide value of the identity 478 * property. 479 * @return true if this transform is currently an identity transform 480 */ 481 boolean computeIsIdentity() { 482 return 483 getMxx() == 1.0 && getMxy() == 0.0 && getMxz() == 0.0 && getTx() == 0.0 && 484 getMyx() == 0.0 && getMyy() == 1.0 && getMyz() == 0.0 && getTy() == 0.0 && 485 getMzx() == 0.0 && getMzy() == 0.0 && getMzz() == 1.0 && getTz() == 0.0; 486 } 487 488 /** 489 * Computes determinant of the transformation matrix. 490 * Among other things, determinant can be used for testing this transform's 491 * invertibility - it is invertible if determinant is not equal to zero. 492 * @return Determinant of the transformation matrix 493 * @since JavaFX 8.0 494 */ 495 public double determinant() { 496 final double myx = getMyx(); 497 final double myy = getMyy(); 498 final double myz = getMyz(); 499 final double mzx = getMzx(); 500 final double mzy = getMzy(); 501 final double mzz = getMzz(); 502 503 return (getMxx() * (myy * mzz - mzy * myz) + 504 getMxy() * (myz * mzx - mzz * myx) + 505 getMxz() * (myx * mzy - mzx * myy)); 506 } 507 508 /** 509 * Determines if this is currently a 2D transform. 510 * Transform is 2D if it has no effect along the Z axis. 511 * @since JavaFX 8.0 512 */ 513 private LazyBooleanProperty type2D; 514 515 public final boolean isType2D() { 516 return type2D == null ? computeIs2D() : type2D.get(); 517 } 518 519 public final ReadOnlyBooleanProperty type2DProperty() { 520 if (type2D == null) { 521 type2D = new LazyBooleanProperty() { 522 523 @Override 524 protected boolean computeValue() { 525 return computeIs2D(); 526 } 527 528 @Override 529 public Object getBean() { 530 return Transform.this; 531 } 532 533 @Override 534 public String getName() { 535 return "type2D"; 536 } 537 }; 538 } 539 return type2D; 540 } 541 542 /** 543 * Determines if this is currently an identity transform. 544 * Identity transform has no effect on the transformed nodes. 545 * @since JavaFX 8.0 546 */ 547 private LazyBooleanProperty identity; 548 549 public final boolean isIdentity() { 550 return identity == null ? computeIsIdentity() : identity.get(); 551 } 552 553 public final ReadOnlyBooleanProperty identityProperty() { 554 if (identity == null) { 555 identity = new LazyBooleanProperty() { 556 557 @Override 558 protected boolean computeValue() { 559 return computeIsIdentity(); 560 } 561 562 @Override 563 public Object getBean() { 564 return Transform.this; 565 } 566 567 @Override 568 public String getName() { 569 return "identity"; 570 } 571 }; 572 } 573 return identity; 574 } 575 576 /** 577 * Lazily computed read-only boolean property implementation. 578 * Used for type2D and identity properties. 579 */ 580 private static abstract class LazyBooleanProperty 581 extends ReadOnlyBooleanProperty { 582 583 private ExpressionHelper<Boolean> helper; 584 private boolean valid; 585 private boolean value; 586 587 @Override 588 public void addListener(InvalidationListener listener) { 589 helper = ExpressionHelper.addListener(helper, this, listener); 590 } 591 592 @Override 593 public void removeListener(InvalidationListener listener) { 594 helper = ExpressionHelper.removeListener(helper, listener); 595 } 596 597 @Override 598 public void addListener(ChangeListener<? super Boolean> listener) { 599 helper = ExpressionHelper.addListener(helper, this, listener); 600 } 601 602 @Override 603 public void removeListener(ChangeListener<? super Boolean> listener) { 604 helper = ExpressionHelper.removeListener(helper, listener); 605 } 606 607 @Override 608 public boolean get() { 609 if (!valid) { 610 value = computeValue(); 611 valid = true; 612 } 613 614 return value; 615 } 616 617 public void invalidate() { 618 if (valid) { 619 valid = false; 620 ExpressionHelper.fireValueChangedEvent(helper); 621 } 622 } 623 624 protected abstract boolean computeValue(); 625 } 626 627 /** 628 * Transforms the specified point by this transform and by the specified 629 * transform and returns distance of the result points. Used for similarTo 630 * method. Has to be used only for 2D transforms (otherwise throws an 631 * exception). 632 * @param t the other transform 633 * @param x point's X coordinate 634 * @param y point's Y coordinate 635 * @return distance of the transformed points 636 */ 637 private double transformDiff(Transform t, double x, double y) { 638 final Point2D byThis = transform(x, y); 639 final Point2D byOther = t.transform(x, y); 640 return byThis.distance(byOther); 641 } 642 643 /** 644 * Transforms the specified point by this transform and by the specified 645 * transform and returns distance of the result points. Used for similarTo 646 * method. 647 * @param t the other transform 648 * @param x point's X coordinate 649 * @param y point's Y coordinate 650 * @param z point's Z coordinate 651 * @return distance of the transformed points 652 */ 653 private double transformDiff(Transform t, double x, double y, double z) { 654 final Point3D byThis = transform(x, y, z); 655 final Point3D byOther = t.transform(x, y, z); 656 return byThis.distance(byOther); 657 } 658 659 /** 660 * Checks if this transform is similar to the specified transform. 661 * The two transforms are considered similar if any point from 662 * {@code range} is transformed by them to points that are no farther 663 * than {@code maxDelta} from each other. 664 * @param transform transform to be compared to this transform 665 * @param range region of interest on which the two transforms are compared 666 * @param maxDelta maximum allowed distance for the results of transforming 667 * any single point from {@code range} by the two transforms 668 * @return true if the transforms are similar according to the specified 669 * criteria 670 * @throws NullPointerException if the specified {@code transform} 671 * or {@code range} is null 672 * @since JavaFX 8.0 673 */ 674 public boolean similarTo(Transform transform, Bounds range, double maxDelta) { 675 676 double cornerX, cornerY, cornerZ; 677 678 if (isType2D() && transform.isType2D()) { 679 cornerX = range.getMinX(); 680 cornerY = range.getMinY(); 681 682 if (transformDiff(transform, cornerX, cornerY) > maxDelta) { 683 return false; 684 } 685 686 cornerY = range.getMaxY(); 687 if (transformDiff(transform, cornerX, cornerY) > maxDelta) { 688 return false; 689 } 690 691 cornerX = range.getMaxX(); 692 cornerY = range.getMinY(); 693 if (transformDiff(transform, cornerX, cornerY) > maxDelta) { 694 return false; 695 } 696 697 cornerY = range.getMaxY(); 698 if (transformDiff(transform, cornerX, cornerY) > maxDelta) { 699 return false; 700 } 701 702 return true; 703 } 704 705 cornerX = range.getMinX(); 706 cornerY = range.getMinY(); 707 cornerZ = range.getMinZ(); 708 if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) { 709 return false; 710 } 711 712 cornerY = range.getMaxY(); 713 if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) { 714 return false; 715 } 716 717 cornerX = range.getMaxX(); 718 cornerY = range.getMinY(); 719 if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) { 720 return false; 721 } 722 723 cornerY = range.getMaxY(); 724 if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) { 725 return false; 726 } 727 728 if (range.getDepth() != 0.0) { 729 cornerX = range.getMinX(); 730 cornerY = range.getMinY(); 731 cornerZ = range.getMaxZ(); 732 if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) { 733 return false; 734 } 735 736 cornerY = range.getMaxY(); 737 if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) { 738 return false; 739 } 740 741 cornerX = range.getMaxX(); 742 cornerY = range.getMinY(); 743 if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) { 744 return false; 745 } 746 747 cornerY = range.getMaxY(); 748 if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) { 749 return false; 750 } 751 } 752 753 return true; 754 } 755 756 /* ************************************************************************* 757 * * 758 * Array getters * 759 * * 760 **************************************************************************/ 761 762 /** 763 * Core of the toArray implementation for the 2D case. 764 * All of the checks has been made by the enclosing method as well as 765 * the constant elements filled, this method only fills the varying 766 * elements to the array. Used by subclasses to fill 767 * the elements efficiently. 768 * @param array array to be filled with the 6 2D elements 769 */ 770 void fill2DArray(double[] array) { 771 array[0] = getMxx(); 772 array[1] = getMxy(); 773 array[2] = getTx(); 774 array[3] = getMyx(); 775 array[4] = getMyy(); 776 array[5] = getTy(); 777 } 778 779 /** 780 * Core of the toArray implementation for the 3D case. 781 * All of the checks has been made by the enclosing method as well as 782 * the constant elements filled, this method only fills the varying 783 * elements to the array. Used by subclasses to fill 784 * the elements efficiently. 785 * @param array array to be filled with the 12 3D elements 786 */ 787 void fill3DArray(double[] array) { 788 array[0] = getMxx(); 789 array[1] = getMxy(); 790 array[2] = getMxz(); 791 array[3] = getTx(); 792 array[4] = getMyx(); 793 array[5] = getMyy(); 794 array[6] = getMyz(); 795 array[7] = getTy(); 796 array[8] = getMzx(); 797 array[9] = getMzy(); 798 array[10] = getMzz(); 799 array[11] = getTz(); 800 } 801 802 /** 803 * Returns an array containing the flattened transformation matrix. 804 * If the requested matrix type fits in the specified array, it is returned 805 * therein. Otherwise, a new array is created. 806 * @param type matrix type to be filled in the array 807 * @param array array into which the elements of the matrix are to be 808 * stored, if it is non-null and big enough; otherwise, 809 * a new array is created for this purpose. 810 * @return an array containing the elements of the requested matrix type 811 * representing this transform 812 * @throws IllegalArgumentException if a 2D matrix type is requested for 813 * a 3D transform 814 * @throws NullPointerException if the specified {@code type} is null 815 * @since JavaFX 8.0 816 */ 817 public double[] toArray(MatrixType type, double[] array) { 818 checkRequestedMAT(type); 819 820 if (array == null || array.length < type.elements()) { 821 array = new double[type.elements()]; 822 } 823 824 switch (type) { 825 case MT_2D_3x3: 826 array[6] = 0.0; 827 array[7] = 0.0; 828 array[8] = 1.0; 829 // fall-through 830 case MT_2D_2x3: 831 fill2DArray(array); 832 break; 833 case MT_3D_4x4: 834 array[12] = 0.0; 835 array[13] = 0.0; 836 array[14] = 0.0; 837 array[15] = 1.0; 838 // fall-through 839 case MT_3D_3x4: 840 fill3DArray(array); 841 break; 842 default: 843 throw new InternalError("Unsupported matrix type " + type); 844 } 845 846 return array; 847 } 848 849 /** 850 * Returns an array containing the flattened transformation matrix. 851 * @param type matrix type to be filled in the array 852 * @return an array containing the elements of the requested matrix type 853 * representing this transform 854 * @throws IllegalArgumentException if a 2D matrix type is requested for 855 * a 3D transform 856 * @throws NullPointerException if the specified {@code type} is null 857 * @since JavaFX 8.0 858 */ 859 public double[] toArray(MatrixType type) { 860 return toArray(type, null); 861 } 862 863 /** 864 * Returns an array containing a row of the transformation matrix. 865 * If the row of the requested matrix type fits in the specified array, 866 * it is returned therein. Otherwise, a new array is created. 867 * @param type matrix type whose row is to be filled in the array 868 * @param row zero-based index of the row 869 * @param array array into which the elements of the row are to be 870 * stored, if it is non-null and big enough; otherwise, 871 * a new array is created for this purpose. 872 * @return an array containing the requested row of the requested matrix 873 * type representing this transform 874 * @throws IllegalArgumentException if a 2D matrix type is requested for 875 * a 3D transform 876 * @throws IndexOutOfBoundsException if the {@code row} index is not within 877 * the number of rows of the specified matrix type 878 * @throws NullPointerException if the specified {@code type} is null 879 * @since JavaFX 8.0 880 */ 881 public double[] row(MatrixType type, int row, double[] array) { 882 883 checkRequestedMAT(type); 884 885 if (row < 0 || row >= type.rows()) { 886 throw new IndexOutOfBoundsException( 887 "Cannot get row " + row + " from " + type); 888 } 889 890 if (array == null || array.length < type.columns()) { 891 array = new double[type.columns()]; 892 } 893 894 switch(type) { 895 case MT_2D_2x3: 896 case MT_2D_3x3: 897 switch (row) { 898 case 0: 899 array[0] = getMxx(); 900 array[1] = getMxy(); 901 array[2] = getTx(); 902 break; 903 case 1: 904 array[0] = getMyx(); 905 array[1] = getMyy(); 906 array[2] = getTy(); 907 break; 908 case 2: 909 array[0] = 0.0; 910 array[1] = 0.0; 911 array[2] = 1.0; 912 break; 913 } 914 break; 915 case MT_3D_3x4: 916 case MT_3D_4x4: 917 switch (row) { 918 case 0: 919 array[0] = getMxx(); 920 array[1] = getMxy(); 921 array[2] = getMxz(); 922 array[3] = getTx(); 923 break; 924 case 1: 925 array[0] = getMyx(); 926 array[1] = getMyy(); 927 array[2] = getMyz(); 928 array[3] = getTy(); 929 break; 930 case 2: 931 array[0] = getMzx(); 932 array[1] = getMzy(); 933 array[2] = getMzz(); 934 array[3] = getTz(); 935 break; 936 case 3: 937 array[0] = 0.0; 938 array[1] = 0.0; 939 array[2] = 0.0; 940 array[3] = 1.0; 941 break; 942 } 943 break; 944 default: 945 throw new InternalError("Unsupported row " + row + " of " + type); 946 } 947 return array; 948 } 949 950 /** 951 * Returns an array containing a row of the transformation matrix. 952 * @param type matrix type whose row is to be filled in the array 953 * @param row zero-based index of the row 954 * @return an array containing the requested row of the requested matrix 955 * type representing this transform 956 * @throws IllegalArgumentException if a 2D matrix type is requested for 957 * a 3D transform 958 * @throws IndexOutOfBoundsException if the {@code row} index is not within 959 * the number of rows of the specified matrix type 960 * @throws NullPointerException if the specified {@code type} is null 961 * @since JavaFX 8.0 962 */ 963 public double[] row(MatrixType type, int row) { 964 return row(type, row, null); 965 } 966 967 /** 968 * Returns an array containing a column of the transformation matrix. 969 * If the column of the requested matrix type fits in the specified array, 970 * it is returned therein. Otherwise, a new array is created. 971 * @param type matrix type whose column is to be filled in the array 972 * @param column zero-based index of the column 973 * @param array array into which the elements of the column are to be 974 * stored, if it is non-null and big enough; otherwise, 975 * a new array is created for this purpose. 976 * @return an array containing the requested column of the requested matrix 977 * type representing this transform 978 * @throws IllegalArgumentException if a 2D matrix type is requested for 979 * a 3D transform 980 * @throws IndexOutOfBoundsException if the {@code column} index 981 * is not within the number of columns of the specified matrix type 982 * @throws NullPointerException if the specified {@code type} is null 983 * @since JavaFX 8.0 984 */ 985 public double[] column(MatrixType type, int column, double[] array) { 986 987 checkRequestedMAT(type); 988 989 if (column < 0 || column >= type.columns()) { 990 throw new IndexOutOfBoundsException( 991 "Cannot get row " + column + " from " + type); 992 } 993 994 if (array == null || array.length < type.rows()) { 995 array = new double[type.rows()]; 996 } 997 998 switch(type) { 999 case MT_2D_2x3: 1000 switch (column) { 1001 case 0: 1002 array[0] = getMxx(); 1003 array[1] = getMyx(); 1004 break; 1005 case 1: 1006 array[0] = getMxy(); 1007 array[1] = getMyy(); 1008 break; 1009 case 2: 1010 array[0] = getTx(); 1011 array[1] = getTy(); 1012 break; 1013 } 1014 break; 1015 case MT_2D_3x3: 1016 switch (column) { 1017 case 0: 1018 array[0] = getMxx(); 1019 array[1] = getMyx(); 1020 array[2] = 0.0; 1021 break; 1022 case 1: 1023 array[0] = getMxy(); 1024 array[1] = getMyy(); 1025 array[2] = 0.0; 1026 break; 1027 case 2: 1028 array[0] = getTx(); 1029 array[1] = getTy(); 1030 array[2] = 1.0; 1031 break; 1032 } 1033 break; 1034 case MT_3D_3x4: 1035 switch (column) { 1036 case 0: 1037 array[0] = getMxx(); 1038 array[1] = getMyx(); 1039 array[2] = getMzx(); 1040 break; 1041 case 1: 1042 array[0] = getMxy(); 1043 array[1] = getMyy(); 1044 array[2] = getMzy(); 1045 break; 1046 case 2: 1047 array[0] = getMxz(); 1048 array[1] = getMyz(); 1049 array[2] = getMzz(); 1050 break; 1051 case 3: 1052 array[0] = getTx(); 1053 array[1] = getTy(); 1054 array[2] = getTz(); 1055 break; 1056 } 1057 break; 1058 case MT_3D_4x4: 1059 switch (column) { 1060 case 0: 1061 array[0] = getMxx(); 1062 array[1] = getMyx(); 1063 array[2] = getMzx(); 1064 array[3] = 0.0; 1065 break; 1066 case 1: 1067 array[0] = getMxy(); 1068 array[1] = getMyy(); 1069 array[2] = getMzy(); 1070 array[3] = 0.0; 1071 break; 1072 case 2: 1073 array[0] = getMxz(); 1074 array[1] = getMyz(); 1075 array[2] = getMzz(); 1076 array[3] = 0.0; 1077 break; 1078 case 3: 1079 array[0] = getTx(); 1080 array[1] = getTy(); 1081 array[2] = getTz(); 1082 array[3] = 1.0; 1083 break; 1084 } 1085 break; 1086 default: 1087 throw new InternalError("Unsupported column " + column + " of " 1088 + type); 1089 } 1090 return array; 1091 } 1092 1093 /** 1094 * Returns an array containing a column of the transformation matrix. 1095 * @param type matrix type whose column is to be filled in the array 1096 * @param column zero-based index of the column 1097 * @return an array containing the requested column of the requested matrix 1098 * type representing this transform 1099 * @throws IllegalArgumentException if a 2D matrix type is requested for 1100 * a 3D transform 1101 * @throws IndexOutOfBoundsException if the {@code column} index 1102 * is not within the number of columns of the specified matrix type 1103 * @throws NullPointerException if the specified {@code type} is null 1104 * @since JavaFX 8.0 1105 */ 1106 public double[] column(MatrixType type, int column) { 1107 return column(type, column, null); 1108 } 1109 1110 /* ************************************************************************* 1111 * * 1112 * Transform creators * 1113 * * 1114 **************************************************************************/ 1115 1116 /** 1117 * Returns the concatenation of this transform and the specified transform. 1118 * Applying the resulting transform to a node has the same effect as 1119 * adding the two transforms to its {@code getTransforms()} list, 1120 * {@code this} transform first and the specified {@code transform} second. 1121 * @param transform transform to be concatenated with this transform 1122 * @return The concatenated transform 1123 * @throws NullPointerException if the specified {@code transform} is null 1124 * @since JavaFX 8.0 1125 */ 1126 public Transform createConcatenation(Transform transform) { 1127 final double txx = transform.getMxx(); 1128 final double txy = transform.getMxy(); 1129 final double txz = transform.getMxz(); 1130 final double ttx = transform.getTx(); 1131 final double tyx = transform.getMyx(); 1132 final double tyy = transform.getMyy(); 1133 final double tyz = transform.getMyz(); 1134 final double tty = transform.getTy(); 1135 final double tzx = transform.getMzx(); 1136 final double tzy = transform.getMzy(); 1137 final double tzz = transform.getMzz(); 1138 final double ttz = transform.getTz(); 1139 return new Affine( 1140 (getMxx() * txx + getMxy() * tyx + getMxz() * tzx), 1141 (getMxx() * txy + getMxy() * tyy + getMxz() * tzy), 1142 (getMxx() * txz + getMxy() * tyz + getMxz() * tzz), 1143 (getMxx() * ttx + getMxy() * tty + getMxz() * ttz + getTx()), 1144 (getMyx() * txx + getMyy() * tyx + getMyz() * tzx), 1145 (getMyx() * txy + getMyy() * tyy + getMyz() * tzy), 1146 (getMyx() * txz + getMyy() * tyz + getMyz() * tzz), 1147 (getMyx() * ttx + getMyy() * tty + getMyz() * ttz + getTy()), 1148 (getMzx() * txx + getMzy() * tyx + getMzz() * tzx), 1149 (getMzx() * txy + getMzy() * tyy + getMzz() * tzy), 1150 (getMzx() * txz + getMzy() * tyz + getMzz() * tzz), 1151 (getMzx() * ttx + getMzy() * tty + getMzz() * ttz + getTz())); 1152 } 1153 1154 /** 1155 * Returns the inverse transform of this transform. 1156 * @return the inverse transform 1157 * @throws NonInvertibleTransformException if this transform 1158 * cannot be inverted 1159 * @since JavaFX 8.0 1160 */ 1161 public Transform createInverse() throws NonInvertibleTransformException { 1162 return getInverseCache().clone(); 1163 } 1164 1165 /** 1166 * Returns a deep copy of this transform. 1167 * @return a copy of this transform 1168 * @since JavaFX 8.0 1169 */ 1170 @Override 1171 public Transform clone() { 1172 return TransformUtils.immutableTransform(this); 1173 } 1174 1175 /* ************************************************************************* 1176 * * 1177 * Transform, Inverse Transform * 1178 * * 1179 **************************************************************************/ 1180 1181 /** 1182 * Transforms the specified point by this transform. 1183 * This method can be used only for 2D transforms. 1184 * @param x the X coordinate of the point 1185 * @param y the Y coordinate of the point 1186 * @return the transformed point 1187 * @throws IllegalStateException if this is a 3D transform 1188 * @since JavaFX 8.0 1189 */ 1190 public Point2D transform(double x, double y) { 1191 ensureCanTransform2DPoint(); 1192 1193 return new Point2D( 1194 getMxx() * x + getMxy() * y + getTx(), 1195 getMyx() * x + getMyy() * y + getTy()); 1196 } 1197 1198 /** 1199 * Transforms the specified point by this transform. 1200 * This method can be used only for 2D transforms. 1201 * @param point the point to be transformed 1202 * @return the transformed point 1203 * @throws IllegalStateException if this is a 3D transform 1204 * @throws NullPointerException if the specified {@code point} is null 1205 * @since JavaFX 8.0 1206 */ 1207 public Point2D transform(Point2D point) { 1208 return transform(point.getX(), point.getY()); 1209 } 1210 1211 /** 1212 * Transforms the specified point by this transform. 1213 * @param x the X coordinate of the point 1214 * @param y the Y coordinate of the point 1215 * @param z the Z coordinate of the point 1216 * @return the transformed point 1217 * @since JavaFX 8.0 1218 */ 1219 public Point3D transform(double x, double y, double z) { 1220 return new Point3D( 1221 getMxx() * x + getMxy() * y + getMxz() * z + getTx(), 1222 getMyx() * x + getMyy() * y + getMyz() * z + getTy(), 1223 getMzx() * x + getMzy() * y + getMzz() * z + getTz()); 1224 } 1225 1226 /** 1227 * Transforms the specified point by this transform. 1228 * @param point the point to be transformed 1229 * @return the transformed point 1230 * @throws NullPointerException if the specified {@code point} is null 1231 * @since JavaFX 8.0 1232 */ 1233 public Point3D transform(Point3D point) { 1234 return transform(point.getX(), point.getY(), point.getZ()); 1235 } 1236 1237 /** 1238 * Transforms the specified bounds by this transform. 1239 * @param bounds the bounds to be transformed 1240 * @return the transformed bounds 1241 * @since JavaFX 8.0 1242 */ 1243 public Bounds transform(Bounds bounds) { 1244 if (isType2D() && (bounds.getMinZ() == 0) && (bounds.getMaxZ() == 0)) { 1245 Point2D p1 = transform(bounds.getMinX(), bounds.getMinY()); 1246 Point2D p2 = transform(bounds.getMaxX(), bounds.getMinY()); 1247 Point2D p3 = transform(bounds.getMaxX(), bounds.getMaxY()); 1248 Point2D p4 = transform(bounds.getMinX(), bounds.getMaxY()); 1249 1250 return BoundsUtils.createBoundingBox(p1, p2, p3, p4); 1251 } 1252 Point3D p1 = transform(bounds.getMinX(), bounds.getMinY(), bounds.getMinZ()); 1253 Point3D p2 = transform(bounds.getMinX(), bounds.getMinY(), bounds.getMaxZ()); 1254 Point3D p3 = transform(bounds.getMinX(), bounds.getMaxY(), bounds.getMinZ()); 1255 Point3D p4 = transform(bounds.getMinX(), bounds.getMaxY(), bounds.getMaxZ()); 1256 Point3D p5 = transform(bounds.getMaxX(), bounds.getMaxY(), bounds.getMinZ()); 1257 Point3D p6 = transform(bounds.getMaxX(), bounds.getMaxY(), bounds.getMaxZ()); 1258 Point3D p7 = transform(bounds.getMaxX(), bounds.getMinY(), bounds.getMinZ()); 1259 Point3D p8 = transform(bounds.getMaxX(), bounds.getMinY(), bounds.getMaxZ()); 1260 1261 return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8); 1262 } 1263 1264 /** 1265 * Core of the transform2DPoints method. 1266 * All the checks has been performed and the care of the overlaps has been 1267 * taken by the enclosing method, this method only transforms the points 1268 * and fills them to the array. Used by the subclasses to perform 1269 * the transform efficiently. 1270 */ 1271 void transform2DPointsImpl(double[] srcPts, int srcOff, 1272 double[] dstPts, int dstOff, int numPts) { 1273 final double xx = getMxx(); 1274 final double xy = getMxy(); 1275 final double tx = getTx(); 1276 final double yx = getMyx(); 1277 final double yy = getMyy(); 1278 final double ty = getTy(); 1279 1280 while (--numPts >= 0) { 1281 final double x = srcPts[srcOff++]; 1282 final double y = srcPts[srcOff++]; 1283 1284 dstPts[dstOff++] = xx * x + xy * y + tx; 1285 dstPts[dstOff++] = yx * x + yy * y + ty; 1286 } 1287 } 1288 1289 /** 1290 * Core of the transform3DPoints method. 1291 * All the checks has been performed and the care of the overlaps has been 1292 * taken by the enclosing method, this method only transforms the points 1293 * and fills them to the array. Used by the subclasses to perform 1294 * the transform efficiently. 1295 */ 1296 void transform3DPointsImpl(double[] srcPts, int srcOff, 1297 double[] dstPts, int dstOff, int numPts) { 1298 1299 final double xx = getMxx(); 1300 final double xy = getMxy(); 1301 final double xz = getMxz(); 1302 final double tx = getTx(); 1303 final double yx = getMyx(); 1304 final double yy = getMyy(); 1305 final double yz = getMyz(); 1306 final double ty = getTy(); 1307 final double zx = getMzx(); 1308 final double zy = getMzy(); 1309 final double zz = getMzz(); 1310 final double tz = getTz(); 1311 1312 while (--numPts >= 0) { 1313 final double x = srcPts[srcOff++]; 1314 final double y = srcPts[srcOff++]; 1315 final double z = srcPts[srcOff++]; 1316 1317 dstPts[dstOff++] = xx * x + xy * y + xz * z + tx; 1318 dstPts[dstOff++] = yx * x + yy * y + yz * z + ty; 1319 dstPts[dstOff++] = zx * x + zy * y + zz * z + tz; 1320 } 1321 } 1322 1323 /** 1324 * Transforms an array of coordinates by this transform. 1325 * The two coordinate array sections can be exactly the same or 1326 * can be overlapping sections of the same array without affecting the 1327 * validity of the results. 1328 * This method ensures that no source coordinates are overwritten by a 1329 * previous operation before they can be transformed. 1330 * The coordinates are stored in the arrays starting at the specified 1331 * offset in the order <code>[x0, y0, x1, y1, ..., xn, yn]</code>. 1332 * This method can be used only for 2D transforms. 1333 * @param srcPts the array containing the source point coordinates. 1334 * Each point is stored as a pair of x, y coordinates. 1335 * @param srcOff the offset to the first point to be transformed 1336 * in the source array 1337 * @param dstPts the array into which the transformed point coordinates 1338 * are returned. Each point is stored as a pair of x, y 1339 * coordinates. 1340 * @param dstOff the offset to the location of the first 1341 * transformed point that is stored in the destination array 1342 * @param numPts the number of points to be transformed 1343 * @throws IllegalStateException if this is a 3D transform 1344 * @throws NullPointerException if {@code srcPts} or (@code dstPts} is null 1345 * @since JavaFX 8.0 1346 */ 1347 public void transform2DPoints(double[] srcPts, int srcOff, 1348 double[] dstPts, int dstOff, 1349 int numPts) { 1350 1351 if (srcPts == null || dstPts == null) { 1352 throw new NullPointerException(); 1353 } 1354 1355 if (!isType2D()) { 1356 throw new IllegalStateException("Cannot transform 2D points " 1357 + "with a 3D transform"); 1358 } 1359 1360 // deal with overlapping arrays 1361 srcOff = getFixedSrcOffset(srcPts, srcOff, dstPts, dstOff, numPts, 2); 1362 1363 // do the transformations 1364 transform2DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts); 1365 } 1366 1367 /** 1368 * Transforms an array of floating point coordinates by this transform. 1369 * The three coordinate array sections can be exactly the same or 1370 * can be overlapping sections of the same array without affecting the 1371 * validity of the results. 1372 * This method ensures that no source coordinates are overwritten by a 1373 * previous operation before they can be transformed. 1374 * The coordinates are stored in the arrays starting at the specified 1375 * offset in the order <code>[x0, y0, z0, x1, y1, z1, ..., xn, yn, zn]</code>. 1376 * @param srcPts the array containing the source point coordinates. 1377 * Each point is stored as a tiplet of x, y, z coordinates. 1378 * @param srcOff the offset to the first point to be transformed 1379 * in the source array 1380 * @param dstPts the array into which the transformed point coordinates 1381 * are returned. Each point is stored as a triplet of x, y, z 1382 * coordinates. 1383 * @param dstOff the offset to the location of the first 1384 * transformed point that is stored in the destination array 1385 * @param numPts the number of points to be transformed 1386 * @throws NullPointerException if {@code srcPts} or (@code dstPts} is null 1387 * @since JavaFX 8.0 1388 */ 1389 public void transform3DPoints(double[] srcPts, int srcOff, 1390 double[] dstPts, int dstOff, 1391 int numPts) { 1392 1393 if (srcPts == null || dstPts == null) { 1394 throw new NullPointerException(); 1395 } 1396 1397 // deal with overlapping arrays 1398 srcOff = getFixedSrcOffset(srcPts, srcOff, dstPts, dstOff, numPts, 3); 1399 1400 // do the transformations 1401 transform3DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts); 1402 } 1403 1404 /** 1405 * Transforms the relative magnitude vector by this transform. 1406 * The vector is transformed without applying the translation components 1407 * of the affine transformation matrix. 1408 * This method can be used only for a 2D transform. 1409 * @param x vector magnitude in the direction of the X axis 1410 * @param y vector magnitude in the direction of the Y axis 1411 * @return the transformed relative magnitude vector represented 1412 * by a {@code Point2D} instance 1413 * @throws IllegalStateException if this is a 3D transform 1414 * @since JavaFX 8.0 1415 */ 1416 public Point2D deltaTransform(double x, double y) { 1417 ensureCanTransform2DPoint(); 1418 1419 return new Point2D( 1420 getMxx() * x + getMxy() * y, 1421 getMyx() * x + getMyy() * y); 1422 } 1423 1424 /** 1425 * Transforms the relative magnitude vector represented by the specified 1426 * {@code Point2D} instance by this transform. 1427 * The vector is transformed without applying the translation components 1428 * of the affine transformation matrix. 1429 * This method can be used only for a 2D transform. 1430 * @param point the relative magnitude vector 1431 * @return the transformed relative magnitude vector represented 1432 * by a {@code Point2D} instance 1433 * @throws IllegalStateException if this is a 3D transform 1434 * @throws NullPointerException if the specified {@code point} is null 1435 * @since JavaFX 8.0 1436 */ 1437 public Point2D deltaTransform(Point2D point) { 1438 return deltaTransform(point.getX(), point.getY()); 1439 } 1440 1441 /** 1442 * Transforms the relative magnitude vector by this transform. 1443 * The vector is transformed without applying the translation components 1444 * of the affine transformation matrix. 1445 * @param x vector magnitude in the direction of the X axis 1446 * @param y vector magnitude in the direction of the Y axis 1447 * @return the transformed relative magnitude vector represented 1448 * by a {@code Point3D} instance 1449 * @since JavaFX 8.0 1450 */ 1451 public Point3D deltaTransform(double x, double y, double z) { 1452 return new Point3D( 1453 getMxx() * x + getMxy() * y + getMxz() * z, 1454 getMyx() * x + getMyy() * y + getMyz() * z, 1455 getMzx() * x + getMzy() * y + getMzz() * z); 1456 } 1457 1458 /** 1459 * Transforms the relative magnitude vector represented by the specified 1460 * {@code Point3D} instance by this transform. 1461 * The vector is transformed without applying the translation components 1462 * of the affine transformation matrix. 1463 * @param point the relative magnitude vector 1464 * @return the transformed relative magnitude vector represented 1465 * by a {@code Point3D} instance 1466 * @throws NullPointerException if the specified {@code point} is null 1467 * @since JavaFX 8.0 1468 */ 1469 public Point3D deltaTransform(Point3D point) { 1470 return deltaTransform(point.getX(), point.getY(), point.getZ()); 1471 } 1472 1473 /** 1474 * Transforms the specified point by the inverse of this transform. 1475 * This method can be used only for 2D transforms. 1476 * @param x the X coordinate of the point 1477 * @param y the Y coordinate of the point 1478 * @return the inversely transformed point 1479 * @throws IllegalStateException if this is a 3D transform 1480 * @throws NonInvertibleTransformException if this transform 1481 * cannot be inverted 1482 * @since JavaFX 8.0 1483 */ 1484 public Point2D inverseTransform(double x, double y) 1485 throws NonInvertibleTransformException { 1486 1487 ensureCanTransform2DPoint(); 1488 1489 return getInverseCache().transform(x, y); 1490 } 1491 1492 /** 1493 * Transforms the specified point by the inverse of this transform. 1494 * This method can be used only for 2D transforms. 1495 * @param point the point to be transformed 1496 * @return the inversely transformed point 1497 * @throws IllegalStateException if this is a 3D transform 1498 * @throws NonInvertibleTransformException if this transform 1499 * cannot be inverted 1500 * @throws NullPointerException if the specified {@code point} is null 1501 * @since JavaFX 8.0 1502 */ 1503 public Point2D inverseTransform(Point2D point) 1504 throws NonInvertibleTransformException { 1505 return inverseTransform(point.getX(), point.getY()); 1506 } 1507 1508 /** 1509 * Transforms the specified point by the inverse of this transform. 1510 * @param x the X coordinate of the point 1511 * @param y the Y coordinate of the point 1512 * @param z the Z coordinate of the point 1513 * @return the inversely transformed point 1514 * @throws NonInvertibleTransformException if this transform 1515 * cannot be inverted 1516 * @since JavaFX 8.0 1517 */ 1518 public Point3D inverseTransform(double x, double y, double z) 1519 throws NonInvertibleTransformException { 1520 1521 return getInverseCache().transform(x, y, z); 1522 } 1523 1524 /** 1525 * Transforms the specified point by the inverse of this transform. 1526 * @param point the point to be transformed 1527 * @return the inversely transformed point 1528 * @throws NonInvertibleTransformException if this transform 1529 * cannot be inverted 1530 * @throws NullPointerException if the specified {@code point} is null 1531 * @since JavaFX 8.0 1532 */ 1533 public Point3D inverseTransform(Point3D point) 1534 throws NonInvertibleTransformException { 1535 return inverseTransform(point.getX(), point.getY(), point.getZ()); 1536 } 1537 1538 /** 1539 * Transforms the specified bounds by the inverse of this transform. 1540 * @param bounds the bounds to be transformed 1541 * @return the inversely transformed bounds 1542 * @throws NonInvertibleTransformException if this transform 1543 * cannot be inverted 1544 * @throws NullPointerException if the specified {@code bounds} is null 1545 * @since JavaFX 8.0 1546 */ 1547 public Bounds inverseTransform(Bounds bounds) 1548 throws NonInvertibleTransformException { 1549 if (isType2D() && (bounds.getMinZ() == 0) && (bounds.getMaxZ() == 0)) { 1550 Point2D p1 = inverseTransform(bounds.getMinX(), bounds.getMinY()); 1551 Point2D p2 = inverseTransform(bounds.getMaxX(), bounds.getMinY()); 1552 Point2D p3 = inverseTransform(bounds.getMaxX(), bounds.getMaxY()); 1553 Point2D p4 = inverseTransform(bounds.getMinX(), bounds.getMaxY()); 1554 1555 return BoundsUtils.createBoundingBox(p1, p2, p3, p4); 1556 } 1557 Point3D p1 = inverseTransform(bounds.getMinX(), bounds.getMinY(), bounds.getMinZ()); 1558 Point3D p2 = inverseTransform(bounds.getMinX(), bounds.getMinY(), bounds.getMaxZ()); 1559 Point3D p3 = inverseTransform(bounds.getMinX(), bounds.getMaxY(), bounds.getMinZ()); 1560 Point3D p4 = inverseTransform(bounds.getMinX(), bounds.getMaxY(), bounds.getMaxZ()); 1561 Point3D p5 = inverseTransform(bounds.getMaxX(), bounds.getMaxY(), bounds.getMinZ()); 1562 Point3D p6 = inverseTransform(bounds.getMaxX(), bounds.getMaxY(), bounds.getMaxZ()); 1563 Point3D p7 = inverseTransform(bounds.getMaxX(), bounds.getMinY(), bounds.getMinZ()); 1564 Point3D p8 = inverseTransform(bounds.getMaxX(), bounds.getMinY(), bounds.getMaxZ()); 1565 1566 return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8); 1567 1568 } 1569 1570 /** 1571 * Core of the inverseTransform2DPoints method. 1572 * All the checks has been performed and the care of the overlaps has been 1573 * taken by the enclosing method, this method only transforms the points 1574 * and fills them to the array. Used by the subclasses to perform 1575 * the transform efficiently. 1576 */ 1577 void inverseTransform2DPointsImpl(double[] srcPts, int srcOff, 1578 double[] dstPts, int dstOff, int numPts) 1579 throws NonInvertibleTransformException { 1580 1581 getInverseCache().transform2DPointsImpl(srcPts, srcOff, 1582 dstPts, dstOff, numPts); 1583 } 1584 1585 /** 1586 * Core of the inverseTransform3DPoints method. 1587 * All the checks has been performed and the care of the overlaps has been 1588 * taken by the enclosing method, this method only transforms the points 1589 * and fills them to the array. Used by the subclasses to perform 1590 * the transform efficiently. 1591 */ 1592 void inverseTransform3DPointsImpl(double[] srcPts, int srcOff, 1593 double[] dstPts, int dstOff, int numPts) 1594 throws NonInvertibleTransformException { 1595 1596 getInverseCache().transform3DPointsImpl(srcPts, srcOff, 1597 dstPts, dstOff, numPts); 1598 } 1599 1600 /** 1601 * Transforms an array of coordinates by the inverse of this transform. 1602 * The two coordinate array sections can be exactly the same or 1603 * can be overlapping sections of the same array without affecting the 1604 * validity of the results. 1605 * This method ensures that no source coordinates are overwritten by a 1606 * previous operation before they can be transformed. 1607 * The coordinates are stored in the arrays starting at the specified 1608 * offset in the order <code>[x0, y0, x1, y1, ..., xn, yn]</code>. 1609 * This method can be used only for 2D transforms. 1610 * @param srcPts the array containing the source point coordinates. 1611 * Each point is stored as a pair of x, y coordinates. 1612 * @param srcOff the offset to the first point to be transformed 1613 * in the source array 1614 * @param dstPts the array into which the transformed point coordinates 1615 * are returned. Each point is stored as a pair of x, y 1616 * coordinates. 1617 * @param dstOff the offset to the location of the first 1618 * transformed point that is stored in the destination array 1619 * @param numPts the number of points to be transformed 1620 * @throws IllegalStateException if this is a 3D transform 1621 * @throws NonInvertibleTransformException if this transform 1622 * cannot be inverted 1623 * @throws NullPointerException if {@code srcPts} or (@code dstPts} is null 1624 * @since JavaFX 8.0 1625 */ 1626 public void inverseTransform2DPoints(double[] srcPts, int srcOff, 1627 double[] dstPts, int dstOff, 1628 int numPts) throws NonInvertibleTransformException{ 1629 1630 if (srcPts == null || dstPts == null) { 1631 throw new NullPointerException(); 1632 } 1633 1634 if (!isType2D()) { 1635 throw new IllegalStateException("Cannot transform 2D points " 1636 + "with a 3D transform"); 1637 } 1638 1639 // deal with overlapping arrays 1640 srcOff = getFixedSrcOffset(srcPts, srcOff, dstPts, dstOff, numPts, 2); 1641 1642 // do the transformations 1643 inverseTransform2DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts); 1644 } 1645 1646 /** 1647 * Transforms an array of floating point coordinates by the inverse 1648 * of this transform. 1649 * The three coordinate array sections can be exactly the same or 1650 * can be overlapping sections of the same array without affecting the 1651 * validity of the results. 1652 * This method ensures that no source coordinates are overwritten by a 1653 * previous operation before they can be transformed. 1654 * The coordinates are stored in the arrays starting at the specified 1655 * offset in the order <code>[x0, y0, z0, x1, y1, z1, ..., xn, yn, zn]</code>. 1656 * @param srcPts the array containing the source point coordinates. 1657 * Each point is stored as a triplet of x, y, z coordinates. 1658 * @param srcOff the offset to the first point to be transformed 1659 * in the source array 1660 * @param dstPts the array into which the transformed point coordinates 1661 * are returned. Each point is stored as a triplet of x, y, z 1662 * coordinates. 1663 * @param dstOff the offset to the location of the first 1664 * transformed point that is stored in the destination array 1665 * @param numPts the number of points to be transformed 1666 * @throws NonInvertibleTransformException if this transform 1667 * cannot be inverted 1668 * @throws NullPointerException if {@code srcPts} or (@code dstPts} is null 1669 * @since JavaFX 8.0 1670 */ 1671 public void inverseTransform3DPoints(double[] srcPts, int srcOff, 1672 double[] dstPts, int dstOff, 1673 int numPts) throws NonInvertibleTransformException { 1674 1675 if (srcPts == null || dstPts == null) { 1676 throw new NullPointerException(); 1677 } 1678 1679 // deal with overlapping arrays 1680 srcOff = getFixedSrcOffset(srcPts, srcOff, dstPts, dstOff, numPts, 3); 1681 1682 // do the transformations 1683 inverseTransform3DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts); 1684 } 1685 1686 /** 1687 * Transforms the relative magnitude vector by the inverse of this transform. 1688 * The vector is transformed without applying the translation components 1689 * of the affine transformation matrix. 1690 * This method can be used only for a 2D transform. 1691 * @param x vector magnitude in the direction of the X axis 1692 * @param y vector magnitude in the direction of the Y axis 1693 * @return the inversely transformed relative magnitude vector represented 1694 * by a {@code Point2D} instance 1695 * @throws IllegalStateException if this is a 3D transform 1696 * @throws NonInvertibleTransformException if this transform 1697 * cannot be inverted 1698 * @since JavaFX 8.0 1699 */ 1700 public Point2D inverseDeltaTransform(double x, double y) 1701 throws NonInvertibleTransformException { 1702 1703 ensureCanTransform2DPoint(); 1704 1705 return getInverseCache().deltaTransform(x, y); 1706 } 1707 1708 /** 1709 * Transforms the relative magnitude vector represented by the specified 1710 * {@code Point2D} instance by the inverse of this transform. 1711 * The vector is transformed without applying the translation components 1712 * of the affine transformation matrix. 1713 * This method can be used only for a 2D transform. 1714 * @param point the relative magnitude vector 1715 * @return the inversely transformed relative magnitude vector represented 1716 * by a {@code Point2D} instance 1717 * @throws IllegalStateException if this is a 3D transform 1718 * @throws NonInvertibleTransformException if this transform 1719 * cannot be inverted 1720 * @throws NullPointerException if the specified {@code point} is null 1721 * @since JavaFX 8.0 1722 */ 1723 public Point2D inverseDeltaTransform(Point2D point) 1724 throws NonInvertibleTransformException { 1725 return inverseDeltaTransform(point.getX(), point.getY()); 1726 } 1727 1728 /** 1729 * Transforms the relative magnitude vector by the inverse of this transform. 1730 * The vector is transformed without applying the translation components 1731 * of the affine transformation matrix. 1732 * @param x vector magnitude in the direction of the X axis 1733 * @param y vector magnitude in the direction of the Y axis 1734 * @return the inversely transformed relative magnitude vector represented 1735 * by a {@code Point3D} instance 1736 * @throws NonInvertibleTransformException if this transform 1737 * cannot be inverted 1738 * @since JavaFX 8.0 1739 */ 1740 public Point3D inverseDeltaTransform(double x, double y, double z) 1741 throws NonInvertibleTransformException { 1742 1743 return getInverseCache().deltaTransform(x, y, z); 1744 } 1745 1746 /** 1747 * Transforms the relative magnitude vector represented by the specified 1748 * {@code Point3D} instance by the inverse of this transform. 1749 * The vector is transformed without applying the translation components 1750 * of the affine transformation matrix. 1751 * @param point the relative magnitude vector 1752 * @return the inversely transformed relative magnitude vector represented 1753 * by a {@code Point3D} instance 1754 * @throws NonInvertibleTransformException if this transform 1755 * cannot be inverted 1756 * @throws NullPointerException if the specified {@code point} is null 1757 * @since JavaFX 8.0 1758 */ 1759 public Point3D inverseDeltaTransform(Point3D point) 1760 throws NonInvertibleTransformException { 1761 return inverseDeltaTransform(point.getX(), point.getY(), point.getZ()); 1762 } 1763 1764 /** 1765 * Helper method for transforming arrays of points that deals with 1766 * overlapping arrays. 1767 * @return the (if necessary fixed) srcOff 1768 */ 1769 private int getFixedSrcOffset(double[] srcPts, int srcOff, 1770 double[] dstPts, int dstOff, 1771 int numPts, int dimensions) { 1772 1773 if (dstPts == srcPts && 1774 dstOff > srcOff && dstOff < srcOff + numPts * dimensions) 1775 { 1776 // If the arrays overlap partially with the destination higher 1777 // than the source and we transform the coordinates normally 1778 // we would overwrite some of the later source coordinates 1779 // with results of previous transformations. 1780 // To get around this we use arraycopy to copy the points 1781 // to their final destination with correct overwrite 1782 // handling and then transform them in place in the new 1783 // safer location. 1784 System.arraycopy(srcPts, srcOff, dstPts, dstOff, numPts * dimensions); 1785 return dstOff; 1786 } 1787 1788 return srcOff; 1789 } 1790 1791 /* ************************************************************************* 1792 * * 1793 * Event Dispatch * 1794 * * 1795 **************************************************************************/ 1796 1797 private EventHandlerManager internalEventDispatcher; 1798 private EventHandlerManager getInternalEventDispatcher() { 1799 if (internalEventDispatcher == null) { 1800 internalEventDispatcher = new EventHandlerManager(this); 1801 } 1802 return internalEventDispatcher; 1803 } 1804 private ObjectProperty<EventHandler<? super TransformChangedEvent>> 1805 onTransformChanged; 1806 1807 @Override 1808 public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { 1809 return internalEventDispatcher == null 1810 ? tail : tail.append(getInternalEventDispatcher()); 1811 } 1812 1813 /** 1814 * <p> 1815 * Registers an event handler to this transform. Any event filters are first 1816 * processed, then the specified onFoo event handlers, and finally any 1817 * event handlers registered by this method. 1818 * </p><p> 1819 * Currently the only event delivered to a {@code Transform} is the 1820 * {@code TransformChangedEvent} with it's single type 1821 * {@code TRANSFORM_CHANGED}. 1822 * </p> 1823 * 1824 * @param <T> the specific event class of the handler 1825 * @param eventType the type of the events to receive by the handler 1826 * @param eventHandler the handler to register 1827 * @throws NullPointerException if the event type or handler is null 1828 * @since JavaFX 8.0 1829 */ 1830 public final <T extends Event> void addEventHandler( 1831 final EventType<T> eventType, 1832 final EventHandler<? super T> eventHandler) { 1833 getInternalEventDispatcher() 1834 .addEventHandler(eventType, eventHandler); 1835 // need to validate all properties to get the change events 1836 validate(); 1837 } 1838 1839 /** 1840 * Unregisters a previously registered event handler from this transform. 1841 * One handler might have been registered for different event types, so the 1842 * caller needs to specify the particular event type from which to 1843 * unregister the handler. 1844 * 1845 * @param <T> the specific event class of the handler 1846 * @param eventType the event type from which to unregister 1847 * @param eventHandler the handler to unregister 1848 * @throws NullPointerException if the event type or handler is null 1849 * @since JavaFX 8.0 1850 */ 1851 public final <T extends Event> void removeEventHandler( 1852 final EventType<T> eventType, 1853 final EventHandler<? super T> eventHandler) { 1854 getInternalEventDispatcher() 1855 .removeEventHandler(eventType, eventHandler); 1856 } 1857 1858 /** 1859 * <p> 1860 * Registers an event filter to this transform. Registered event filters get 1861 * an event before any associated event handlers. 1862 * </p><p> 1863 * Currently the only event delivered to a {@code Transform} is the 1864 * {@code TransformChangedEvent} with it's single type 1865 * {@code TRANSFORM_CHANGED}. 1866 * <p> 1867 * 1868 * @param <T> the specific event class of the filter 1869 * @param eventType the type of the events to receive by the filter 1870 * @param eventFilter the filter to register 1871 * @throws NullPointerException if the event type or filter is null 1872 * @since JavaFX 8.0 1873 */ 1874 public final <T extends Event> void addEventFilter( 1875 final EventType<T> eventType, 1876 final EventHandler<? super T> eventFilter) { 1877 getInternalEventDispatcher() 1878 .addEventFilter(eventType, eventFilter); 1879 // need to validate all properties to get the change events 1880 validate(); 1881 } 1882 1883 /** 1884 * Unregisters a previously registered event filter from this transform. One 1885 * filter might have been registered for different event types, so the 1886 * caller needs to specify the particular event type from which to 1887 * unregister the filter. 1888 * 1889 * @param <T> the specific event class of the filter 1890 * @param eventType the event type from which to unregister 1891 * @param eventFilter the filter to unregister 1892 * @throws NullPointerException if the event type or filter is null 1893 * @since JavaFX 8.0 1894 */ 1895 public final <T extends Event> void removeEventFilter( 1896 final EventType<T> eventType, 1897 final EventHandler<? super T> eventFilter) { 1898 getInternalEventDispatcher() 1899 .removeEventFilter(eventType, eventFilter); 1900 } 1901 1902 /** 1903 * Sets the onTransformChanged event handler which is called whenever 1904 * the transform changes any of its parameters. 1905 * 1906 * @param value the event handler, can be null to clear it 1907 * @since JavaFX 8.0 1908 */ 1909 public final void setOnTransformChanged( 1910 EventHandler<? super TransformChangedEvent> value) { 1911 onTransformChangedProperty().set(value); 1912 // need to validate all properties to get the change events 1913 validate(); 1914 } 1915 1916 /** 1917 * Gets the onTransformChanged event handler. 1918 * @return the event handler previously set by {@code setOnTransformChanged} 1919 * method, null if the handler is not set. 1920 * @since JavaFX 8.0 1921 */ 1922 public final EventHandler<? super TransformChangedEvent> getOnTransformChanged() { 1923 return (onTransformChanged == null) ? null : onTransformChanged.get(); 1924 } 1925 1926 /** 1927 * The onTransformChanged event handler is called whenever the transform 1928 * changes any of its parameters. 1929 * @since JavaFX 8.0 1930 */ 1931 public final ObjectProperty<EventHandler<? super TransformChangedEvent>> 1932 onTransformChangedProperty() { 1933 if (onTransformChanged == null) { 1934 1935 onTransformChanged = new SimpleObjectProperty<EventHandler 1936 <? super TransformChangedEvent>>(this, "onTransformChanged") { 1937 1938 @Override protected void invalidated() { 1939 getInternalEventDispatcher().setEventHandler( 1940 TransformChangedEvent.TRANSFORM_CHANGED, get()); 1941 } 1942 }; 1943 } 1944 1945 return onTransformChanged; 1946 } 1947 1948 /* ************************************************************************* 1949 * * 1950 * Internal implementation stuff * 1951 * * 1952 **************************************************************************/ 1953 1954 /** 1955 * Makes sure the specified matrix type can be requested from this transform. 1956 * Is used for convenience in various methods that accept 1957 * the MatrixType argument. 1958 * @param type matrix type to check 1959 * @throws IllegalArgumentException if this is a 3D transform and 1960 * a 2D type is requested 1961 */ 1962 void checkRequestedMAT(MatrixType type) throws IllegalArgumentException{ 1963 if (type.is2D() && !isType2D()) { 1964 throw new IllegalArgumentException("Cannot access 2D matrix " 1965 + "for a 3D transform"); 1966 } 1967 } 1968 1969 /** 1970 * Makes sure this is a 2D transform. 1971 * Is used for convenience in various 2D point transformation methods. 1972 * @throws IllegalStateException if this is a 2D transform 1973 */ 1974 void ensureCanTransform2DPoint() throws IllegalStateException { 1975 if (!isType2D()) { 1976 throw new IllegalStateException("Cannot transform 2D point " 1977 + "with a 3D transform"); 1978 } 1979 } 1980 1981 /** 1982 * Needed for the proper delivery of the TransformChangedEvent. 1983 * If the members are invalid, the transformChanged() notification 1984 * is not called and the event is not delivered. To avoid that 1985 * we need to manually validate all properties. Subclasses validate 1986 * their specific properties. 1987 */ 1988 void validate() { 1989 getMxx(); getMxy(); getMxz(); getTx(); 1990 getMyx(); getMyy(); getMyz(); getTy(); 1991 getMzx(); getMzy(); getMzz(); getTz(); 1992 } 1993 1994 /** 1995 * @treatAsPrivate implementation detail 1996 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 1997 */ 1998 @Deprecated 1999 public abstract void impl_apply(Affine3D t); 2000 2001 /** 2002 * @treatAsPrivate implementation detail 2003 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 2004 */ 2005 @Deprecated 2006 public abstract BaseTransform impl_derive(BaseTransform t); 2007 2008 /** 2009 * @treatAsPrivate implementation detail 2010 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 2011 */ 2012 @Deprecated 2013 public void impl_add(final Node node) { 2014 impl_nodes.add(node); 2015 } 2016 2017 /** 2018 * @treatAsPrivate implementation detail 2019 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 2020 */ 2021 @Deprecated 2022 public void impl_remove(final Node node) { 2023 impl_nodes.remove(node); 2024 } 2025 2026 /** 2027 * This method must be called by all transforms whenever any of their 2028 * parameters changes. It is typically called when any of the transform's 2029 * properties is invalidated (it is OK to skip the call if an invalid 2030 * property is set). 2031 * @since JavaFX 8.0 2032 */ 2033 protected void transformChanged() { 2034 inverseCache = null; 2035 final Iterator iterator = impl_nodes.iterator(); 2036 while (iterator.hasNext()) { 2037 ((Node) iterator.next()).impl_transformsChanged(); 2038 } 2039 2040 if (type2D != null) { 2041 type2D.invalidate(); 2042 } 2043 2044 if (identity != null) { 2045 identity.invalidate(); 2046 } 2047 2048 if (internalEventDispatcher != null) { 2049 // need to validate all properties for the event to be fired next time 2050 validate(); 2051 Event.fireEvent(this, new TransformChangedEvent(this, this)); 2052 } 2053 } 2054 2055 /** 2056 * Visitor from {@code Affine} class which provides an efficient 2057 * {@code append} operation for the subclasses. 2058 * @param a {@code Affine} instance to append to 2059 */ 2060 void appendTo(Affine a) { 2061 a.append(getMxx(), getMxy(), getMxz(), getTx(), 2062 getMyx(), getMyy(), getMyz(), getTy(), 2063 getMzx(), getMzy(), getMzz(), getTz()); 2064 } 2065 2066 /** 2067 * Visitor from {@code Affine} class which provides an efficient 2068 * {@code prepend} operation for the subclasses. 2069 * @param a {@code Affine} instance to prepend to 2070 */ 2071 void prependTo(Affine a) { 2072 a.prepend(getMxx(), getMxy(), getMxz(), getTx(), 2073 getMyx(), getMyy(), getMyz(), getTy(), 2074 getMzx(), getMzy(), getMzz(), getTz()); 2075 } 2076 2077 /** 2078 * <p> 2079 * Gets the inverse transform cache. 2080 * </p><p> 2081 * Computing the inverse transform is generally an expensive operation, 2082 * so once it is needed we cache the result (throwing it away when the 2083 * transform changes). The subclasses may avoid using the cache if their 2084 * inverse can be computed quickly on the fly. 2085 * </p><p> 2086 * This method computes the inverse if the cache is not valid. 2087 * </p> 2088 * @return the cached inverse transformation 2089 * @throws NonInvertibleTransformException if this transform 2090 * cannot be inverted 2091 */ 2092 private Transform getInverseCache() throws NonInvertibleTransformException { 2093 if (inverseCache == null || inverseCache.get() == null) { 2094 Affine inv = new Affine( 2095 getMxx(), getMxy(), getMxz(), getTx(), 2096 getMyx(), getMyy(), getMyz(), getTy(), 2097 getMzx(), getMzy(), getMzz(), getTz()); 2098 inv.invert(); 2099 inverseCache = new SoftReference<Transform>(inv); 2100 return inv; 2101 } 2102 2103 return inverseCache.get(); 2104 } 2105 2106 /** 2107 * Used only by tests to emulate garbage collecting the soft references 2108 */ 2109 void clearInverseCache() { 2110 if (inverseCache != null) { 2111 inverseCache.clear(); 2112 } 2113 } 2114 }