1 /* 2 * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.transform; 27 28 import 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.Affine3D; 39 import com.sun.javafx.geom.transform.BaseTransform; 40 import com.sun.javafx.scene.NodeHelper; 41 import com.sun.javafx.scene.transform.TransformHelper; 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.Bounds; 54 import javafx.geometry.Point2D; 55 import javafx.geometry.Point3D; 56 57 // PENDING_DOC_REVIEW of this whole class 58 /** 59 * This class is a base class for different affine transformations. 60 * It provides factory methods for the simple transformations - rotating, 61 * scaling, shearing, and translation. It allows to get the transformation 62 * matrix elements for any transform. 63 * 64 * <p>Example:</p> 65 * 66 * <pre><code> 67 * Rectangle rect = new Rectangle(50,50, Color.RED); 68 * rect.getTransforms().add(new Rotate(45,0,0)); //rotate by 45 degrees 69 * </code></pre> 70 * @since JavaFX 2.0 71 */ 72 public abstract class Transform implements Cloneable, EventTarget { 73 74 static { 75 // This is used by classes in different packages to get access to 76 // private and package private methods. 77 TransformHelper.setTransformAccessor(new TransformHelper.TransformAccessor() { 78 79 @Override 80 public void add(Transform transform, Node node) { 81 transform.add(node); 82 } 83 84 @Override 85 public void remove(Transform transform, Node node) { 86 transform.remove(node); 87 } 88 89 @Override 90 public void apply(Transform transform, Affine3D affine3D) { 91 transform.apply(affine3D); 92 } 93 94 @Override 95 public BaseTransform derive(Transform transform, BaseTransform baseTransform) { 96 return transform.derive(baseTransform); 97 } 98 99 @Override 100 public Transform createImmutableTransform() { 101 return Transform.createImmutableTransform(); 102 } 103 104 @Override 105 public Transform createImmutableTransform( 106 double mxx, double mxy, double mxz, double tx, 107 double myx, double myy, double myz, double ty, 108 double mzx, double mzy, double mzz, double tz) { 109 return Transform.createImmutableTransform(mxx, mxy, mxz, tx, 110 myx, myy, myz, ty, mzx, mzy, mzz, tz); 111 } 112 113 @Override 114 public Transform createImmutableTransform(Transform transform, 115 double mxx, double mxy, double mxz, double tx, 116 double myx, double myy, double myz, double ty, 117 double mzx, double mzy, double mzz, double tz) { 118 return Transform.createImmutableTransform(transform, 119 mxx, mxy, mxz, tx, myx, myy, myz, ty, mzx, mzy, mzz, tz); 120 } 121 122 @Override 123 public Transform createImmutableTransform(Transform transform, 124 Transform left, Transform right) { 125 return Transform.createImmutableTransform(transform, left, right); 126 } 127 }); 128 } 129 130 /* ************************************************************************* 131 * * 132 * Factories * 133 * * 134 **************************************************************************/ 135 136 /** 137 * Returns a new {@code Affine} object from 12 number 138 * values representing the 6 specifiable entries of the 3x4 139 * Affine transformation matrix. 140 * 141 * @param mxx the X coordinate scaling element of the 3x4 matrix 142 * @param myx the Y coordinate shearing element of the 3x4 matrix 143 * @param mxy the X coordinate shearing element of the 3x4 matrix 144 * @param myy the Y coordinate scaling element of the 3x4 matrix 145 * @param tx the X coordinate translation element of the 3x4 matrix 146 * @param ty the Y coordinate translation element of the 3x4 matrix 147 * @return a new {@code Affine} object derived from specified parameters 148 */ 149 public static Affine affine( 150 double mxx, double myx, double mxy, double myy, double tx, double ty) { 151 final Affine affine = new Affine(); 152 affine.setMxx(mxx); 153 affine.setMxy(mxy); 154 affine.setTx(tx); 155 affine.setMyx(myx); 156 affine.setMyy(myy); 157 affine.setTy(ty); 158 return affine; 159 } 160 161 162 /** 163 * Returns a new {@code Affine} object from 12 number 164 * values representing the 12 specifiable entries of the 3x4 165 * Affine transformation matrix. 166 * 167 * @param mxx the X coordinate scaling element of the 3x4 matrix 168 * @param mxy the XY element of the 3x4 matrix 169 * @param mxz the XZ element of the 3x4 matrix 170 * @param tx the X coordinate translation element of the 3x4 matrix 171 * @param myx the YX element of the 3x4 matrix 172 * @param myy the Y coordinate scaling element of the 3x4 matrix 173 * @param myz the YZ element of the 3x4 matrix 174 * @param ty the Y coordinate translation element of the 3x4 matrix 175 * @param mzx the ZX element of the 3x4 matrix 176 * @param mzy the ZY element of the 3x4 matrix 177 * @param mzz the Z coordinate scaling element of the 3x4 matrix 178 * @param tz the Z coordinate translation element of the 3x4 matrix 179 * @return a new {@code Affine} object derived from specified parameters 180 */ 181 public static Affine affine( 182 double mxx, double mxy, double mxz, double tx, 183 double myx, double myy, double myz, double ty, 184 double mzx, double mzy, double mzz, double tz) { 185 final Affine affine = new Affine(); 186 affine.setMxx(mxx); 187 affine.setMxy(mxy); 188 affine.setMxz(mxz); 189 affine.setTx(tx); 190 affine.setMyx(myx); 191 affine.setMyy(myy); 192 affine.setMyz(myz); 193 affine.setTy(ty); 194 affine.setMzx(mzx); 195 affine.setMzy(mzy); 196 affine.setMzz(mzz); 197 affine.setTz(tz); 198 return affine; 199 } 200 201 202 /** 203 * Returns a {@code Translate} object representing a translation transformation. 204 * <p> 205 * This is equivalent to: 206 * <pre> 207 * new Translate(x, y); 208 * </pre> 209 */ 210 public static Translate translate(double x, double y) { 211 final Translate translate = new Translate(); 212 translate.setX(x); 213 translate.setY(y); 214 return translate; 215 } 216 217 218 /** 219 * Returns a {@code Rotate} object that rotates coordinates around a pivot 220 * point. 221 * <p> 222 * This is equivalent to: 223 * <pre> 224 * new Rotate(angle, pivotX, pivotY); 225 * </pre> 226 */ 227 public static Rotate rotate(double angle, double pivotX, double pivotY) { 228 final Rotate rotate = new Rotate(); 229 rotate.setAngle(angle); 230 rotate.setPivotX(pivotX); 231 rotate.setPivotY(pivotY); 232 return rotate; 233 } 234 235 236 /** 237 * Returns a {@code Scale} object representing a scaling transformation. 238 * <p> 239 * This is equivalent to: 240 * <pre> 241 * new Scale(x, y); 242 * </pre> 243 */ 244 public static Scale scale(double x, double y) { 245 final Scale scale = new Scale(); 246 scale.setX(x); 247 scale.setY(y); 248 return scale; 249 } 250 251 252 /** 253 * Returns a {@code Scale} object representing a scaling transformation. 254 * The returned scale operation will be about the given pivot point. 255 * <p> 256 * This is equivalent to: 257 * <pre> 258 * new Scale(x, y, pivotX, pivotY); 259 * </pre> 260 */ 261 public static Scale scale(double x, double y, double pivotX, double pivotY) { 262 final Scale scale = new Scale(); 263 scale.setX(x); 264 scale.setY(y); 265 scale.setPivotX(pivotX); 266 scale.setPivotY(pivotY); 267 return scale; 268 } 269 270 271 /** 272 * Returns a {@code Shear} object representing a shearing transformation. 273 * <p> 274 * This is equivalent to: 275 * <pre> 276 * new Shear(x, y); 277 * </pre> 278 */ 279 public static Shear shear(double x, double y) { 280 final Shear shear = new Shear(); 281 shear.setX(x); 282 shear.setY(y); 283 return shear; 284 } 285 286 /** 287 * Returns a {@code Shear} object representing a shearing transformation. 288 * <p> 289 * This is equivalent to: 290 * <pre> 291 * new Shear(x, y, pivotX, pivotY); 292 * </pre> 293 */ 294 public static Shear shear(double x, double y, double pivotX, double pivotY) { 295 final Shear shear = new Shear(); 296 shear.setX(x); 297 shear.setY(y); 298 shear.setPivotX(pivotX); 299 shear.setPivotY(pivotY); 300 return shear; 301 } 302 303 /** 304 * For transforms with expensive inversion we cache the inverted matrix 305 * once it is needed and computed for some operation. 306 */ 307 private SoftReference<Transform> inverseCache = null; 308 309 private WeakReferenceQueue nodes = new WeakReferenceQueue(); 310 311 /* ************************************************************************* 312 * * 313 * Element getters * 314 * * 315 **************************************************************************/ 316 317 /** 318 * Gets the X coordinate scaling element of the 3x4 matrix. 319 * 320 * @since JavaFX 2.2 321 */ 322 public double getMxx() { 323 return 1.0; 324 } 325 326 /** 327 * Gets the XY coordinate element of the 3x4 matrix. 328 * 329 * @since JavaFX 2.2 330 */ 331 public double getMxy() { 332 return 0.0; 333 } 334 335 /** 336 * Gets the XZ coordinate element of the 3x4 matrix. 337 * 338 * @since JavaFX 2.2 339 */ 340 public double getMxz() { 341 return 0.0; 342 } 343 344 /** 345 * Gets the X coordinate translation element of the 3x4 matrix. 346 * 347 * @since JavaFX 2.2 348 */ 349 public double getTx() { 350 return 0.0; 351 } 352 353 /** 354 * Gets the YX coordinate element of the 3x4 matrix. 355 * 356 * @since JavaFX 2.2 357 */ 358 public double getMyx() { 359 return 0.0; 360 } 361 362 /** 363 * Gets the Y coordinate scaling element of the 3x4 matrix. 364 * 365 * @since JavaFX 2.2 366 */ 367 public double getMyy() { 368 return 1.0; 369 } 370 371 /** 372 * Gets the YZ coordinate element of the 3x4 matrix. 373 * 374 * @since JavaFX 2.2 375 */ 376 public double getMyz() { 377 return 0.0; 378 } 379 380 /** 381 * Gets the Y coordinate translation element of the 3x4 matrix. 382 * 383 * @since JavaFX 2.2 384 */ 385 public double getTy() { 386 return 0.0; 387 } 388 389 /** 390 * Gets the ZX coordinate element of the 3x4 matrix. 391 * 392 * @since JavaFX 2.2 393 */ 394 public double getMzx() { 395 return 0.0; 396 } 397 398 /** 399 * Gets the ZY coordinate element of the 3x4 matrix. 400 * 401 * @since JavaFX 2.2 402 */ 403 public double getMzy() { 404 return 0.0; 405 } 406 407 /** 408 * Gets the Z coordinate scaling element of the 3x4 matrix. 409 * 410 * @since JavaFX 2.2 411 */ 412 public double getMzz() { 413 return 1.0; 414 } 415 416 /** 417 * Gets the Z coordinate translation element of the 3x4 matrix. 418 * 419 * @since JavaFX 2.2 420 */ 421 public double getTz() { 422 return 0.0; 423 } 424 425 /** 426 * Gets the specified element of the transformation matrix. 427 * @param type type of matrix to get the value from 428 * @param row zero-based row number 429 * @param column zero-based column number 430 * @return value of the specified transformation matrix element 431 * @throws IllegalArgumentException if a 2D matrix type is requested for 432 * a 3D transform 433 * @throws IndexOutOfBoundsException if the indices are not within 434 * the specified matrix type 435 * @throws NullPointerException if the specified {@code type} is null 436 * @since JavaFX 8.0 437 */ 438 public double getElement(MatrixType type, int row, int column) { 439 if (row < 0 || row >= type.rows() || column < 0 || column >= type.columns()) { 440 throw new IndexOutOfBoundsException("Index outside of affine " 441 + "matrix " + type + ": [" + row + ", " + column + "]"); 442 } 443 switch(type) { 444 case MT_2D_2x3: 445 // fall-through 446 case MT_2D_3x3: 447 if (!isType2D()) { 448 throw new IllegalArgumentException("Cannot access 2D matrix " 449 + "of a 3D transform"); 450 } 451 switch(row) { 452 case 0: 453 switch(column) { 454 case 0: return getMxx(); 455 case 1: return getMxy(); 456 case 2: return getTx(); 457 } 458 case 1: 459 switch(column) { 460 case 0: return getMyx(); 461 case 1: return getMyy(); 462 case 2: return getTy(); 463 } 464 case 2: 465 switch(column) { 466 case 0: return 0.0; 467 case 1: return 0.0; 468 case 2: return 1.0; 469 } 470 } 471 break; 472 case MT_3D_3x4: 473 // fall-through 474 case MT_3D_4x4: 475 switch(row) { 476 case 0: 477 switch(column) { 478 case 0: return getMxx(); 479 case 1: return getMxy(); 480 case 2: return getMxz(); 481 case 3: return getTx(); 482 } 483 case 1: 484 switch(column) { 485 case 0: return getMyx(); 486 case 1: return getMyy(); 487 case 2: return getMyz(); 488 case 3: return getTy(); 489 } 490 case 2: 491 switch(column) { 492 case 0: return getMzx(); 493 case 1: return getMzy(); 494 case 2: return getMzz(); 495 case 3: return getTz(); 496 } 497 case 3: 498 switch(column) { 499 case 0: return 0.0; 500 case 1: return 0.0; 501 case 2: return 0.0; 502 case 3: return 1.0; 503 } 504 } 505 break; 506 } 507 // cannot reach here 508 throw new InternalError("Unsupported matrix type " + type); 509 } 510 511 /* ************************************************************************* 512 * * 513 * State getters * 514 * * 515 **************************************************************************/ 516 517 /** 518 * Computes if this transform is currently a 2D transform (has no effect 519 * in the direction of Z axis). 520 * Used by the subclasses to effectively provide value of the type2D 521 * property. 522 * @return true if this transform is currently 2D-only 523 */ 524 boolean computeIs2D() { 525 return getMxz() == 0.0 && getMzx() == 0.0 && getMzy() == 0.0 && 526 getMzz() == 1.0 && getTz() == 0.0; 527 } 528 529 /** 530 * Computes if this transform is currently an identity (has 531 * no effect in any direction). 532 * Used by the subclasses to effectively provide value of the identity 533 * property. 534 * @return true if this transform is currently an identity transform 535 */ 536 boolean computeIsIdentity() { 537 return 538 getMxx() == 1.0 && getMxy() == 0.0 && getMxz() == 0.0 && getTx() == 0.0 && 539 getMyx() == 0.0 && getMyy() == 1.0 && getMyz() == 0.0 && getTy() == 0.0 && 540 getMzx() == 0.0 && getMzy() == 0.0 && getMzz() == 1.0 && getTz() == 0.0; 541 } 542 543 /** 544 * Computes determinant of the transformation matrix. 545 * Among other things, determinant can be used for testing this transform's 546 * invertibility - it is invertible if determinant is not equal to zero. 547 * @return Determinant of the transformation matrix 548 * @since JavaFX 8.0 549 */ 550 public double determinant() { 551 final double myx = getMyx(); 552 final double myy = getMyy(); 553 final double myz = getMyz(); 554 final double mzx = getMzx(); 555 final double mzy = getMzy(); 556 final double mzz = getMzz(); 557 558 return (getMxx() * (myy * mzz - mzy * myz) + 559 getMxy() * (myz * mzx - mzz * myx) + 560 getMxz() * (myx * mzy - mzx * myy)); 561 } 562 563 /** 564 * Determines if this is currently a 2D transform. 565 * Transform is 2D if it has no effect along the Z axis. 566 * @since JavaFX 8.0 567 */ 568 private LazyBooleanProperty type2D; 569 570 public final boolean isType2D() { 571 return type2D == null ? computeIs2D() : type2D.get(); 572 } 573 574 public final ReadOnlyBooleanProperty type2DProperty() { 575 if (type2D == null) { 576 type2D = new LazyBooleanProperty() { 577 578 @Override 579 protected boolean computeValue() { 580 return computeIs2D(); 581 } 582 583 @Override 584 public Object getBean() { 585 return Transform.this; 586 } 587 588 @Override 589 public String getName() { 590 return "type2D"; 591 } 592 }; 593 } 594 return type2D; 595 } 596 597 /** 598 * Determines if this is currently an identity transform. 599 * Identity transform has no effect on the transformed nodes. 600 * @since JavaFX 8.0 601 */ 602 private LazyBooleanProperty identity; 603 604 public final boolean isIdentity() { 605 return identity == null ? computeIsIdentity() : identity.get(); 606 } 607 608 public final ReadOnlyBooleanProperty identityProperty() { 609 if (identity == null) { 610 identity = new LazyBooleanProperty() { 611 612 @Override 613 protected boolean computeValue() { 614 return computeIsIdentity(); 615 } 616 617 @Override 618 public Object getBean() { 619 return Transform.this; 620 } 621 622 @Override 623 public String getName() { 624 return "identity"; 625 } 626 }; 627 } 628 return identity; 629 } 630 631 /** 632 * Lazily computed read-only boolean property implementation. 633 * Used for type2D and identity properties. 634 */ 635 private static abstract class LazyBooleanProperty 636 extends ReadOnlyBooleanProperty { 637 638 private ExpressionHelper<Boolean> helper; 639 private boolean valid; 640 private boolean value; 641 642 @Override 643 public void addListener(InvalidationListener listener) { 644 helper = ExpressionHelper.addListener(helper, this, listener); 645 } 646 647 @Override 648 public void removeListener(InvalidationListener listener) { 649 helper = ExpressionHelper.removeListener(helper, listener); 650 } 651 652 @Override 653 public void addListener(ChangeListener<? super Boolean> listener) { 654 helper = ExpressionHelper.addListener(helper, this, listener); 655 } 656 657 @Override 658 public void removeListener(ChangeListener<? super Boolean> listener) { 659 helper = ExpressionHelper.removeListener(helper, listener); 660 } 661 662 @Override 663 public boolean get() { 664 if (!valid) { 665 value = computeValue(); 666 valid = true; 667 } 668 669 return value; 670 } 671 672 public void invalidate() { 673 if (valid) { 674 valid = false; 675 ExpressionHelper.fireValueChangedEvent(helper); 676 } 677 } 678 679 protected abstract boolean computeValue(); 680 } 681 682 /** 683 * Transforms the specified point by this transform and by the specified 684 * transform and returns distance of the result points. Used for similarTo 685 * method. Has to be used only for 2D transforms (otherwise throws an 686 * exception). 687 * @param t the other transform 688 * @param x point's X coordinate 689 * @param y point's Y coordinate 690 * @return distance of the transformed points 691 */ 692 private double transformDiff(Transform t, double x, double y) { 693 final Point2D byThis = transform(x, y); 694 final Point2D byOther = t.transform(x, y); 695 return byThis.distance(byOther); 696 } 697 698 /** 699 * Transforms the specified point by this transform and by the specified 700 * transform and returns distance of the result points. Used for similarTo 701 * method. 702 * @param t the other transform 703 * @param x point's X coordinate 704 * @param y point's Y coordinate 705 * @param z point's Z coordinate 706 * @return distance of the transformed points 707 */ 708 private double transformDiff(Transform t, double x, double y, double z) { 709 final Point3D byThis = transform(x, y, z); 710 final Point3D byOther = t.transform(x, y, z); 711 return byThis.distance(byOther); 712 } 713 714 /** 715 * Checks if this transform is similar to the specified transform. 716 * The two transforms are considered similar if any point from 717 * {@code range} is transformed by them to points that are no farther 718 * than {@code maxDelta} from each other. 719 * @param transform transform to be compared to this transform 720 * @param range region of interest on which the two transforms are compared 721 * @param maxDelta maximum allowed distance for the results of transforming 722 * any single point from {@code range} by the two transforms 723 * @return true if the transforms are similar according to the specified 724 * criteria 725 * @throws NullPointerException if the specified {@code transform} 726 * or {@code range} is null 727 * @since JavaFX 8.0 728 */ 729 public boolean similarTo(Transform transform, Bounds range, double maxDelta) { 730 731 double cornerX, cornerY, cornerZ; 732 733 if (isType2D() && transform.isType2D()) { 734 cornerX = range.getMinX(); 735 cornerY = range.getMinY(); 736 737 if (transformDiff(transform, cornerX, cornerY) > maxDelta) { 738 return false; 739 } 740 741 cornerY = range.getMaxY(); 742 if (transformDiff(transform, cornerX, cornerY) > maxDelta) { 743 return false; 744 } 745 746 cornerX = range.getMaxX(); 747 cornerY = range.getMinY(); 748 if (transformDiff(transform, cornerX, cornerY) > maxDelta) { 749 return false; 750 } 751 752 cornerY = range.getMaxY(); 753 if (transformDiff(transform, cornerX, cornerY) > maxDelta) { 754 return false; 755 } 756 757 return true; 758 } 759 760 cornerX = range.getMinX(); 761 cornerY = range.getMinY(); 762 cornerZ = range.getMinZ(); 763 if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) { 764 return false; 765 } 766 767 cornerY = range.getMaxY(); 768 if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) { 769 return false; 770 } 771 772 cornerX = range.getMaxX(); 773 cornerY = range.getMinY(); 774 if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) { 775 return false; 776 } 777 778 cornerY = range.getMaxY(); 779 if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) { 780 return false; 781 } 782 783 if (range.getDepth() != 0.0) { 784 cornerX = range.getMinX(); 785 cornerY = range.getMinY(); 786 cornerZ = range.getMaxZ(); 787 if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) { 788 return false; 789 } 790 791 cornerY = range.getMaxY(); 792 if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) { 793 return false; 794 } 795 796 cornerX = range.getMaxX(); 797 cornerY = range.getMinY(); 798 if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) { 799 return false; 800 } 801 802 cornerY = range.getMaxY(); 803 if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) { 804 return false; 805 } 806 } 807 808 return true; 809 } 810 811 /* ************************************************************************* 812 * * 813 * Array getters * 814 * * 815 **************************************************************************/ 816 817 /** 818 * Core of the toArray implementation for the 2D case. 819 * All of the checks has been made by the enclosing method as well as 820 * the constant elements filled, this method only fills the varying 821 * elements to the array. Used by subclasses to fill 822 * the elements efficiently. 823 * @param array array to be filled with the 6 2D elements 824 */ 825 void fill2DArray(double[] array) { 826 array[0] = getMxx(); 827 array[1] = getMxy(); 828 array[2] = getTx(); 829 array[3] = getMyx(); 830 array[4] = getMyy(); 831 array[5] = getTy(); 832 } 833 834 /** 835 * Core of the toArray implementation for the 3D case. 836 * All of the checks has been made by the enclosing method as well as 837 * the constant elements filled, this method only fills the varying 838 * elements to the array. Used by subclasses to fill 839 * the elements efficiently. 840 * @param array array to be filled with the 12 3D elements 841 */ 842 void fill3DArray(double[] array) { 843 array[0] = getMxx(); 844 array[1] = getMxy(); 845 array[2] = getMxz(); 846 array[3] = getTx(); 847 array[4] = getMyx(); 848 array[5] = getMyy(); 849 array[6] = getMyz(); 850 array[7] = getTy(); 851 array[8] = getMzx(); 852 array[9] = getMzy(); 853 array[10] = getMzz(); 854 array[11] = getTz(); 855 } 856 857 /** 858 * Returns an array containing the flattened transformation matrix. 859 * If the requested matrix type fits in the specified array, it is returned 860 * therein. Otherwise, a new array is created. 861 * @param type matrix type to be filled in the array 862 * @param array array into which the elements of the matrix are to be 863 * stored, if it is non-null and big enough; otherwise, 864 * a new array is created for this purpose. 865 * @return an array containing the elements of the requested matrix type 866 * representing this transform 867 * @throws IllegalArgumentException if a 2D matrix type is requested for 868 * a 3D transform 869 * @throws NullPointerException if the specified {@code type} is null 870 * @since JavaFX 8.0 871 */ 872 public double[] toArray(MatrixType type, double[] array) { 873 checkRequestedMAT(type); 874 875 if (array == null || array.length < type.elements()) { 876 array = new double[type.elements()]; 877 } 878 879 switch (type) { 880 case MT_2D_3x3: 881 array[6] = 0.0; 882 array[7] = 0.0; 883 array[8] = 1.0; 884 // fall-through 885 case MT_2D_2x3: 886 fill2DArray(array); 887 break; 888 case MT_3D_4x4: 889 array[12] = 0.0; 890 array[13] = 0.0; 891 array[14] = 0.0; 892 array[15] = 1.0; 893 // fall-through 894 case MT_3D_3x4: 895 fill3DArray(array); 896 break; 897 default: 898 throw new InternalError("Unsupported matrix type " + type); 899 } 900 901 return array; 902 } 903 904 /** 905 * Returns an array containing the flattened transformation matrix. 906 * @param type matrix type to be filled in the array 907 * @return an array containing the elements of the requested matrix type 908 * representing this transform 909 * @throws IllegalArgumentException if a 2D matrix type is requested for 910 * a 3D transform 911 * @throws NullPointerException if the specified {@code type} is null 912 * @since JavaFX 8.0 913 */ 914 public double[] toArray(MatrixType type) { 915 return toArray(type, null); 916 } 917 918 /** 919 * Returns an array containing a row of the transformation matrix. 920 * If the row of the requested matrix type fits in the specified array, 921 * it is returned therein. Otherwise, a new array is created. 922 * @param type matrix type whose row is to be filled in the array 923 * @param row zero-based index of the row 924 * @param array array into which the elements of the row are to be 925 * stored, if it is non-null and big enough; otherwise, 926 * a new array is created for this purpose. 927 * @return an array containing the requested row of the requested matrix 928 * type representing this transform 929 * @throws IllegalArgumentException if a 2D matrix type is requested for 930 * a 3D transform 931 * @throws IndexOutOfBoundsException if the {@code row} index is not within 932 * the number of rows of the specified matrix type 933 * @throws NullPointerException if the specified {@code type} is null 934 * @since JavaFX 8.0 935 */ 936 public double[] row(MatrixType type, int row, double[] array) { 937 938 checkRequestedMAT(type); 939 940 if (row < 0 || row >= type.rows()) { 941 throw new IndexOutOfBoundsException( 942 "Cannot get row " + row + " from " + type); 943 } 944 945 if (array == null || array.length < type.columns()) { 946 array = new double[type.columns()]; 947 } 948 949 switch(type) { 950 case MT_2D_2x3: 951 case MT_2D_3x3: 952 switch (row) { 953 case 0: 954 array[0] = getMxx(); 955 array[1] = getMxy(); 956 array[2] = getTx(); 957 break; 958 case 1: 959 array[0] = getMyx(); 960 array[1] = getMyy(); 961 array[2] = getTy(); 962 break; 963 case 2: 964 array[0] = 0.0; 965 array[1] = 0.0; 966 array[2] = 1.0; 967 break; 968 } 969 break; 970 case MT_3D_3x4: 971 case MT_3D_4x4: 972 switch (row) { 973 case 0: 974 array[0] = getMxx(); 975 array[1] = getMxy(); 976 array[2] = getMxz(); 977 array[3] = getTx(); 978 break; 979 case 1: 980 array[0] = getMyx(); 981 array[1] = getMyy(); 982 array[2] = getMyz(); 983 array[3] = getTy(); 984 break; 985 case 2: 986 array[0] = getMzx(); 987 array[1] = getMzy(); 988 array[2] = getMzz(); 989 array[3] = getTz(); 990 break; 991 case 3: 992 array[0] = 0.0; 993 array[1] = 0.0; 994 array[2] = 0.0; 995 array[3] = 1.0; 996 break; 997 } 998 break; 999 default: 1000 throw new InternalError("Unsupported row " + row + " of " + type); 1001 } 1002 return array; 1003 } 1004 1005 /** 1006 * Returns an array containing a row of the transformation matrix. 1007 * @param type matrix type whose row is to be filled in the array 1008 * @param row zero-based index of the row 1009 * @return an array containing the requested row of the requested matrix 1010 * type representing this transform 1011 * @throws IllegalArgumentException if a 2D matrix type is requested for 1012 * a 3D transform 1013 * @throws IndexOutOfBoundsException if the {@code row} index is not within 1014 * the number of rows of the specified matrix type 1015 * @throws NullPointerException if the specified {@code type} is null 1016 * @since JavaFX 8.0 1017 */ 1018 public double[] row(MatrixType type, int row) { 1019 return row(type, row, null); 1020 } 1021 1022 /** 1023 * Returns an array containing a column of the transformation matrix. 1024 * If the column of the requested matrix type fits in the specified array, 1025 * it is returned therein. Otherwise, a new array is created. 1026 * @param type matrix type whose column is to be filled in the array 1027 * @param column zero-based index of the column 1028 * @param array array into which the elements of the column are to be 1029 * stored, if it is non-null and big enough; otherwise, 1030 * a new array is created for this purpose. 1031 * @return an array containing the requested column of the requested matrix 1032 * type representing this transform 1033 * @throws IllegalArgumentException if a 2D matrix type is requested for 1034 * a 3D transform 1035 * @throws IndexOutOfBoundsException if the {@code column} index 1036 * is not within the number of columns of the specified matrix type 1037 * @throws NullPointerException if the specified {@code type} is null 1038 * @since JavaFX 8.0 1039 */ 1040 public double[] column(MatrixType type, int column, double[] array) { 1041 1042 checkRequestedMAT(type); 1043 1044 if (column < 0 || column >= type.columns()) { 1045 throw new IndexOutOfBoundsException( 1046 "Cannot get row " + column + " from " + type); 1047 } 1048 1049 if (array == null || array.length < type.rows()) { 1050 array = new double[type.rows()]; 1051 } 1052 1053 switch(type) { 1054 case MT_2D_2x3: 1055 switch (column) { 1056 case 0: 1057 array[0] = getMxx(); 1058 array[1] = getMyx(); 1059 break; 1060 case 1: 1061 array[0] = getMxy(); 1062 array[1] = getMyy(); 1063 break; 1064 case 2: 1065 array[0] = getTx(); 1066 array[1] = getTy(); 1067 break; 1068 } 1069 break; 1070 case MT_2D_3x3: 1071 switch (column) { 1072 case 0: 1073 array[0] = getMxx(); 1074 array[1] = getMyx(); 1075 array[2] = 0.0; 1076 break; 1077 case 1: 1078 array[0] = getMxy(); 1079 array[1] = getMyy(); 1080 array[2] = 0.0; 1081 break; 1082 case 2: 1083 array[0] = getTx(); 1084 array[1] = getTy(); 1085 array[2] = 1.0; 1086 break; 1087 } 1088 break; 1089 case MT_3D_3x4: 1090 switch (column) { 1091 case 0: 1092 array[0] = getMxx(); 1093 array[1] = getMyx(); 1094 array[2] = getMzx(); 1095 break; 1096 case 1: 1097 array[0] = getMxy(); 1098 array[1] = getMyy(); 1099 array[2] = getMzy(); 1100 break; 1101 case 2: 1102 array[0] = getMxz(); 1103 array[1] = getMyz(); 1104 array[2] = getMzz(); 1105 break; 1106 case 3: 1107 array[0] = getTx(); 1108 array[1] = getTy(); 1109 array[2] = getTz(); 1110 break; 1111 } 1112 break; 1113 case MT_3D_4x4: 1114 switch (column) { 1115 case 0: 1116 array[0] = getMxx(); 1117 array[1] = getMyx(); 1118 array[2] = getMzx(); 1119 array[3] = 0.0; 1120 break; 1121 case 1: 1122 array[0] = getMxy(); 1123 array[1] = getMyy(); 1124 array[2] = getMzy(); 1125 array[3] = 0.0; 1126 break; 1127 case 2: 1128 array[0] = getMxz(); 1129 array[1] = getMyz(); 1130 array[2] = getMzz(); 1131 array[3] = 0.0; 1132 break; 1133 case 3: 1134 array[0] = getTx(); 1135 array[1] = getTy(); 1136 array[2] = getTz(); 1137 array[3] = 1.0; 1138 break; 1139 } 1140 break; 1141 default: 1142 throw new InternalError("Unsupported column " + column + " of " 1143 + type); 1144 } 1145 return array; 1146 } 1147 1148 /** 1149 * Returns an array containing a column of the transformation matrix. 1150 * @param type matrix type whose column is to be filled in the array 1151 * @param column zero-based index of the column 1152 * @return an array containing the requested column of the requested matrix 1153 * type representing this transform 1154 * @throws IllegalArgumentException if a 2D matrix type is requested for 1155 * a 3D transform 1156 * @throws IndexOutOfBoundsException if the {@code column} index 1157 * is not within the number of columns of the specified matrix type 1158 * @throws NullPointerException if the specified {@code type} is null 1159 * @since JavaFX 8.0 1160 */ 1161 public double[] column(MatrixType type, int column) { 1162 return column(type, column, null); 1163 } 1164 1165 /* ************************************************************************* 1166 * * 1167 * Transform creators * 1168 * * 1169 **************************************************************************/ 1170 1171 /** 1172 * Returns the concatenation of this transform and the specified transform. 1173 * Applying the resulting transform to a node has the same effect as 1174 * adding the two transforms to its {@code getTransforms()} list, 1175 * {@code this} transform first and the specified {@code transform} second. 1176 * @param transform transform to be concatenated with this transform 1177 * @return The concatenated transform 1178 * @throws NullPointerException if the specified {@code transform} is null 1179 * @since JavaFX 8.0 1180 */ 1181 public Transform createConcatenation(Transform transform) { 1182 final double txx = transform.getMxx(); 1183 final double txy = transform.getMxy(); 1184 final double txz = transform.getMxz(); 1185 final double ttx = transform.getTx(); 1186 final double tyx = transform.getMyx(); 1187 final double tyy = transform.getMyy(); 1188 final double tyz = transform.getMyz(); 1189 final double tty = transform.getTy(); 1190 final double tzx = transform.getMzx(); 1191 final double tzy = transform.getMzy(); 1192 final double tzz = transform.getMzz(); 1193 final double ttz = transform.getTz(); 1194 return new Affine( 1195 (getMxx() * txx + getMxy() * tyx + getMxz() * tzx), 1196 (getMxx() * txy + getMxy() * tyy + getMxz() * tzy), 1197 (getMxx() * txz + getMxy() * tyz + getMxz() * tzz), 1198 (getMxx() * ttx + getMxy() * tty + getMxz() * ttz + getTx()), 1199 (getMyx() * txx + getMyy() * tyx + getMyz() * tzx), 1200 (getMyx() * txy + getMyy() * tyy + getMyz() * tzy), 1201 (getMyx() * txz + getMyy() * tyz + getMyz() * tzz), 1202 (getMyx() * ttx + getMyy() * tty + getMyz() * ttz + getTy()), 1203 (getMzx() * txx + getMzy() * tyx + getMzz() * tzx), 1204 (getMzx() * txy + getMzy() * tyy + getMzz() * tzy), 1205 (getMzx() * txz + getMzy() * tyz + getMzz() * tzz), 1206 (getMzx() * ttx + getMzy() * tty + getMzz() * ttz + getTz())); 1207 } 1208 1209 /** 1210 * Returns the inverse transform of this transform. 1211 * @return the inverse transform 1212 * @throws NonInvertibleTransformException if this transform 1213 * cannot be inverted 1214 * @since JavaFX 8.0 1215 */ 1216 public Transform createInverse() throws NonInvertibleTransformException { 1217 return getInverseCache().clone(); 1218 } 1219 1220 /** 1221 * Returns a deep copy of this transform. 1222 * @return a copy of this transform 1223 * @since JavaFX 8.0 1224 */ 1225 @Override 1226 public Transform clone() { 1227 return TransformUtils.immutableTransform(this); 1228 } 1229 1230 /* ************************************************************************* 1231 * * 1232 * Transform, Inverse Transform * 1233 * * 1234 **************************************************************************/ 1235 1236 /** 1237 * Transforms the specified point by this transform. 1238 * This method can be used only for 2D transforms. 1239 * @param x the X coordinate of the point 1240 * @param y the Y coordinate of the point 1241 * @return the transformed point 1242 * @throws IllegalStateException if this is a 3D transform 1243 * @since JavaFX 8.0 1244 */ 1245 public Point2D transform(double x, double y) { 1246 ensureCanTransform2DPoint(); 1247 1248 return new Point2D( 1249 getMxx() * x + getMxy() * y + getTx(), 1250 getMyx() * x + getMyy() * y + getTy()); 1251 } 1252 1253 /** 1254 * Transforms the specified point by this transform. 1255 * This method can be used only for 2D transforms. 1256 * @param point the point to be transformed 1257 * @return the transformed point 1258 * @throws IllegalStateException if this is a 3D transform 1259 * @throws NullPointerException if the specified {@code point} is null 1260 * @since JavaFX 8.0 1261 */ 1262 public Point2D transform(Point2D point) { 1263 return transform(point.getX(), point.getY()); 1264 } 1265 1266 /** 1267 * Transforms the specified point by this transform. 1268 * @param x the X coordinate of the point 1269 * @param y the Y coordinate of the point 1270 * @param z the Z coordinate of the point 1271 * @return the transformed point 1272 * @since JavaFX 8.0 1273 */ 1274 public Point3D transform(double x, double y, double z) { 1275 return new Point3D( 1276 getMxx() * x + getMxy() * y + getMxz() * z + getTx(), 1277 getMyx() * x + getMyy() * y + getMyz() * z + getTy(), 1278 getMzx() * x + getMzy() * y + getMzz() * z + getTz()); 1279 } 1280 1281 /** 1282 * Transforms the specified point by this transform. 1283 * @param point the point to be transformed 1284 * @return the transformed point 1285 * @throws NullPointerException if the specified {@code point} is null 1286 * @since JavaFX 8.0 1287 */ 1288 public Point3D transform(Point3D point) { 1289 return transform(point.getX(), point.getY(), point.getZ()); 1290 } 1291 1292 /** 1293 * Transforms the specified bounds by this transform. 1294 * @param bounds the bounds to be transformed 1295 * @return the transformed bounds 1296 * @since JavaFX 8.0 1297 */ 1298 public Bounds transform(Bounds bounds) { 1299 if (isType2D() && (bounds.getMinZ() == 0) && (bounds.getMaxZ() == 0)) { 1300 Point2D p1 = transform(bounds.getMinX(), bounds.getMinY()); 1301 Point2D p2 = transform(bounds.getMaxX(), bounds.getMinY()); 1302 Point2D p3 = transform(bounds.getMaxX(), bounds.getMaxY()); 1303 Point2D p4 = transform(bounds.getMinX(), bounds.getMaxY()); 1304 1305 return BoundsUtils.createBoundingBox(p1, p2, p3, p4); 1306 } 1307 Point3D p1 = transform(bounds.getMinX(), bounds.getMinY(), bounds.getMinZ()); 1308 Point3D p2 = transform(bounds.getMinX(), bounds.getMinY(), bounds.getMaxZ()); 1309 Point3D p3 = transform(bounds.getMinX(), bounds.getMaxY(), bounds.getMinZ()); 1310 Point3D p4 = transform(bounds.getMinX(), bounds.getMaxY(), bounds.getMaxZ()); 1311 Point3D p5 = transform(bounds.getMaxX(), bounds.getMaxY(), bounds.getMinZ()); 1312 Point3D p6 = transform(bounds.getMaxX(), bounds.getMaxY(), bounds.getMaxZ()); 1313 Point3D p7 = transform(bounds.getMaxX(), bounds.getMinY(), bounds.getMinZ()); 1314 Point3D p8 = transform(bounds.getMaxX(), bounds.getMinY(), bounds.getMaxZ()); 1315 1316 return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8); 1317 } 1318 1319 /** 1320 * Core of the transform2DPoints method. 1321 * All the checks has been performed and the care of the overlaps has been 1322 * taken by the enclosing method, this method only transforms the points 1323 * and fills them to the array. Used by the subclasses to perform 1324 * the transform efficiently. 1325 */ 1326 void transform2DPointsImpl(double[] srcPts, int srcOff, 1327 double[] dstPts, int dstOff, int numPts) { 1328 final double xx = getMxx(); 1329 final double xy = getMxy(); 1330 final double tx = getTx(); 1331 final double yx = getMyx(); 1332 final double yy = getMyy(); 1333 final double ty = getTy(); 1334 1335 while (--numPts >= 0) { 1336 final double x = srcPts[srcOff++]; 1337 final double y = srcPts[srcOff++]; 1338 1339 dstPts[dstOff++] = xx * x + xy * y + tx; 1340 dstPts[dstOff++] = yx * x + yy * y + ty; 1341 } 1342 } 1343 1344 /** 1345 * Core of the transform3DPoints method. 1346 * All the checks has been performed and the care of the overlaps has been 1347 * taken by the enclosing method, this method only transforms the points 1348 * and fills them to the array. Used by the subclasses to perform 1349 * the transform efficiently. 1350 */ 1351 void transform3DPointsImpl(double[] srcPts, int srcOff, 1352 double[] dstPts, int dstOff, int numPts) { 1353 1354 final double xx = getMxx(); 1355 final double xy = getMxy(); 1356 final double xz = getMxz(); 1357 final double tx = getTx(); 1358 final double yx = getMyx(); 1359 final double yy = getMyy(); 1360 final double yz = getMyz(); 1361 final double ty = getTy(); 1362 final double zx = getMzx(); 1363 final double zy = getMzy(); 1364 final double zz = getMzz(); 1365 final double tz = getTz(); 1366 1367 while (--numPts >= 0) { 1368 final double x = srcPts[srcOff++]; 1369 final double y = srcPts[srcOff++]; 1370 final double z = srcPts[srcOff++]; 1371 1372 dstPts[dstOff++] = xx * x + xy * y + xz * z + tx; 1373 dstPts[dstOff++] = yx * x + yy * y + yz * z + ty; 1374 dstPts[dstOff++] = zx * x + zy * y + zz * z + tz; 1375 } 1376 } 1377 1378 /** 1379 * Transforms an array of coordinates by this transform. 1380 * The two coordinate array sections can be exactly the same or 1381 * can be overlapping sections of the same array without affecting the 1382 * validity of the results. 1383 * This method ensures that no source coordinates are overwritten by a 1384 * previous operation before they can be transformed. 1385 * The coordinates are stored in the arrays starting at the specified 1386 * offset in the order <code>[x0, y0, x1, y1, ..., xn, yn]</code>. 1387 * This method can be used only for 2D transforms. 1388 * @param srcPts the array containing the source point coordinates. 1389 * Each point is stored as a pair of x, y coordinates. 1390 * @param srcOff the offset to the first point to be transformed 1391 * in the source array 1392 * @param dstPts the array into which the transformed point coordinates 1393 * are returned. Each point is stored as a pair of x, y 1394 * coordinates. 1395 * @param dstOff the offset to the location of the first 1396 * transformed point that is stored in the destination array 1397 * @param numPts the number of points to be transformed 1398 * @throws IllegalStateException if this is a 3D transform 1399 * @throws NullPointerException if {@code srcPts} or (@code dstPts} is null 1400 * @since JavaFX 8.0 1401 */ 1402 public void transform2DPoints(double[] srcPts, int srcOff, 1403 double[] dstPts, int dstOff, 1404 int numPts) { 1405 1406 if (srcPts == null || dstPts == null) { 1407 throw new NullPointerException(); 1408 } 1409 1410 if (!isType2D()) { 1411 throw new IllegalStateException("Cannot transform 2D points " 1412 + "with a 3D transform"); 1413 } 1414 1415 // deal with overlapping arrays 1416 srcOff = getFixedSrcOffset(srcPts, srcOff, dstPts, dstOff, numPts, 2); 1417 1418 // do the transformations 1419 transform2DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts); 1420 } 1421 1422 /** 1423 * Transforms an array of floating point coordinates by this transform. 1424 * The three coordinate array sections can be exactly the same or 1425 * can be overlapping sections of the same array without affecting the 1426 * validity of the results. 1427 * This method ensures that no source coordinates are overwritten by a 1428 * previous operation before they can be transformed. 1429 * The coordinates are stored in the arrays starting at the specified 1430 * offset in the order <code>[x0, y0, z0, x1, y1, z1, ..., xn, yn, zn]</code>. 1431 * @param srcPts the array containing the source point coordinates. 1432 * Each point is stored as a tiplet of x, y, z coordinates. 1433 * @param srcOff the offset to the first point to be transformed 1434 * in the source array 1435 * @param dstPts the array into which the transformed point coordinates 1436 * are returned. Each point is stored as a triplet of x, y, z 1437 * coordinates. 1438 * @param dstOff the offset to the location of the first 1439 * transformed point that is stored in the destination array 1440 * @param numPts the number of points to be transformed 1441 * @throws NullPointerException if {@code srcPts} or (@code dstPts} is null 1442 * @since JavaFX 8.0 1443 */ 1444 public void transform3DPoints(double[] srcPts, int srcOff, 1445 double[] dstPts, int dstOff, 1446 int numPts) { 1447 1448 if (srcPts == null || dstPts == null) { 1449 throw new NullPointerException(); 1450 } 1451 1452 // deal with overlapping arrays 1453 srcOff = getFixedSrcOffset(srcPts, srcOff, dstPts, dstOff, numPts, 3); 1454 1455 // do the transformations 1456 transform3DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts); 1457 } 1458 1459 /** 1460 * Transforms the relative magnitude vector by this transform. 1461 * The vector is transformed without applying the translation components 1462 * of the affine transformation matrix. 1463 * This method can be used only for a 2D transform. 1464 * @param x vector magnitude in the direction of the X axis 1465 * @param y vector magnitude in the direction of the Y axis 1466 * @return the transformed relative magnitude vector represented 1467 * by a {@code Point2D} instance 1468 * @throws IllegalStateException if this is a 3D transform 1469 * @since JavaFX 8.0 1470 */ 1471 public Point2D deltaTransform(double x, double y) { 1472 ensureCanTransform2DPoint(); 1473 1474 return new Point2D( 1475 getMxx() * x + getMxy() * y, 1476 getMyx() * x + getMyy() * y); 1477 } 1478 1479 /** 1480 * Transforms the relative magnitude vector represented by the specified 1481 * {@code Point2D} instance by this transform. 1482 * The vector is transformed without applying the translation components 1483 * of the affine transformation matrix. 1484 * This method can be used only for a 2D transform. 1485 * @param point the relative magnitude vector 1486 * @return the transformed relative magnitude vector represented 1487 * by a {@code Point2D} instance 1488 * @throws IllegalStateException if this is a 3D transform 1489 * @throws NullPointerException if the specified {@code point} is null 1490 * @since JavaFX 8.0 1491 */ 1492 public Point2D deltaTransform(Point2D point) { 1493 return deltaTransform(point.getX(), point.getY()); 1494 } 1495 1496 /** 1497 * Transforms the relative magnitude vector by this transform. 1498 * The vector is transformed without applying the translation components 1499 * of the affine transformation matrix. 1500 * @param x vector magnitude in the direction of the X axis 1501 * @param y vector magnitude in the direction of the Y axis 1502 * @return the transformed relative magnitude vector represented 1503 * by a {@code Point3D} instance 1504 * @since JavaFX 8.0 1505 */ 1506 public Point3D deltaTransform(double x, double y, double z) { 1507 return new Point3D( 1508 getMxx() * x + getMxy() * y + getMxz() * z, 1509 getMyx() * x + getMyy() * y + getMyz() * z, 1510 getMzx() * x + getMzy() * y + getMzz() * z); 1511 } 1512 1513 /** 1514 * Transforms the relative magnitude vector represented by the specified 1515 * {@code Point3D} instance by this transform. 1516 * The vector is transformed without applying the translation components 1517 * of the affine transformation matrix. 1518 * @param point the relative magnitude vector 1519 * @return the transformed relative magnitude vector represented 1520 * by a {@code Point3D} instance 1521 * @throws NullPointerException if the specified {@code point} is null 1522 * @since JavaFX 8.0 1523 */ 1524 public Point3D deltaTransform(Point3D point) { 1525 return deltaTransform(point.getX(), point.getY(), point.getZ()); 1526 } 1527 1528 /** 1529 * Transforms the specified point by the inverse of this transform. 1530 * This method can be used only for 2D transforms. 1531 * @param x the X coordinate of the point 1532 * @param y the Y coordinate of the point 1533 * @return the inversely transformed point 1534 * @throws IllegalStateException if this is a 3D transform 1535 * @throws NonInvertibleTransformException if this transform 1536 * cannot be inverted 1537 * @since JavaFX 8.0 1538 */ 1539 public Point2D inverseTransform(double x, double y) 1540 throws NonInvertibleTransformException { 1541 1542 ensureCanTransform2DPoint(); 1543 1544 return getInverseCache().transform(x, y); 1545 } 1546 1547 /** 1548 * Transforms the specified point by the inverse of this transform. 1549 * This method can be used only for 2D transforms. 1550 * @param point the point to be transformed 1551 * @return the inversely transformed point 1552 * @throws IllegalStateException if this is a 3D transform 1553 * @throws NonInvertibleTransformException if this transform 1554 * cannot be inverted 1555 * @throws NullPointerException if the specified {@code point} is null 1556 * @since JavaFX 8.0 1557 */ 1558 public Point2D inverseTransform(Point2D point) 1559 throws NonInvertibleTransformException { 1560 return inverseTransform(point.getX(), point.getY()); 1561 } 1562 1563 /** 1564 * Transforms the specified point by the inverse of this transform. 1565 * @param x the X coordinate of the point 1566 * @param y the Y coordinate of the point 1567 * @param z the Z coordinate of the point 1568 * @return the inversely transformed point 1569 * @throws NonInvertibleTransformException if this transform 1570 * cannot be inverted 1571 * @since JavaFX 8.0 1572 */ 1573 public Point3D inverseTransform(double x, double y, double z) 1574 throws NonInvertibleTransformException { 1575 1576 return getInverseCache().transform(x, y, z); 1577 } 1578 1579 /** 1580 * Transforms the specified point by the inverse of this transform. 1581 * @param point the point to be transformed 1582 * @return the inversely transformed point 1583 * @throws NonInvertibleTransformException if this transform 1584 * cannot be inverted 1585 * @throws NullPointerException if the specified {@code point} is null 1586 * @since JavaFX 8.0 1587 */ 1588 public Point3D inverseTransform(Point3D point) 1589 throws NonInvertibleTransformException { 1590 return inverseTransform(point.getX(), point.getY(), point.getZ()); 1591 } 1592 1593 /** 1594 * Transforms the specified bounds by the inverse of this transform. 1595 * @param bounds the bounds to be transformed 1596 * @return the inversely transformed bounds 1597 * @throws NonInvertibleTransformException if this transform 1598 * cannot be inverted 1599 * @throws NullPointerException if the specified {@code bounds} is null 1600 * @since JavaFX 8.0 1601 */ 1602 public Bounds inverseTransform(Bounds bounds) 1603 throws NonInvertibleTransformException { 1604 if (isType2D() && (bounds.getMinZ() == 0) && (bounds.getMaxZ() == 0)) { 1605 Point2D p1 = inverseTransform(bounds.getMinX(), bounds.getMinY()); 1606 Point2D p2 = inverseTransform(bounds.getMaxX(), bounds.getMinY()); 1607 Point2D p3 = inverseTransform(bounds.getMaxX(), bounds.getMaxY()); 1608 Point2D p4 = inverseTransform(bounds.getMinX(), bounds.getMaxY()); 1609 1610 return BoundsUtils.createBoundingBox(p1, p2, p3, p4); 1611 } 1612 Point3D p1 = inverseTransform(bounds.getMinX(), bounds.getMinY(), bounds.getMinZ()); 1613 Point3D p2 = inverseTransform(bounds.getMinX(), bounds.getMinY(), bounds.getMaxZ()); 1614 Point3D p3 = inverseTransform(bounds.getMinX(), bounds.getMaxY(), bounds.getMinZ()); 1615 Point3D p4 = inverseTransform(bounds.getMinX(), bounds.getMaxY(), bounds.getMaxZ()); 1616 Point3D p5 = inverseTransform(bounds.getMaxX(), bounds.getMaxY(), bounds.getMinZ()); 1617 Point3D p6 = inverseTransform(bounds.getMaxX(), bounds.getMaxY(), bounds.getMaxZ()); 1618 Point3D p7 = inverseTransform(bounds.getMaxX(), bounds.getMinY(), bounds.getMinZ()); 1619 Point3D p8 = inverseTransform(bounds.getMaxX(), bounds.getMinY(), bounds.getMaxZ()); 1620 1621 return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8); 1622 1623 } 1624 1625 /** 1626 * Core of the inverseTransform2DPoints method. 1627 * All the checks has been performed and the care of the overlaps has been 1628 * taken by the enclosing method, this method only transforms the points 1629 * and fills them to the array. Used by the subclasses to perform 1630 * the transform efficiently. 1631 */ 1632 void inverseTransform2DPointsImpl(double[] srcPts, int srcOff, 1633 double[] dstPts, int dstOff, int numPts) 1634 throws NonInvertibleTransformException { 1635 1636 getInverseCache().transform2DPointsImpl(srcPts, srcOff, 1637 dstPts, dstOff, numPts); 1638 } 1639 1640 /** 1641 * Core of the inverseTransform3DPoints method. 1642 * All the checks has been performed and the care of the overlaps has been 1643 * taken by the enclosing method, this method only transforms the points 1644 * and fills them to the array. Used by the subclasses to perform 1645 * the transform efficiently. 1646 */ 1647 void inverseTransform3DPointsImpl(double[] srcPts, int srcOff, 1648 double[] dstPts, int dstOff, int numPts) 1649 throws NonInvertibleTransformException { 1650 1651 getInverseCache().transform3DPointsImpl(srcPts, srcOff, 1652 dstPts, dstOff, numPts); 1653 } 1654 1655 /** 1656 * Transforms an array of coordinates by the inverse of this transform. 1657 * The two coordinate array sections can be exactly the same or 1658 * can be overlapping sections of the same array without affecting the 1659 * validity of the results. 1660 * This method ensures that no source coordinates are overwritten by a 1661 * previous operation before they can be transformed. 1662 * The coordinates are stored in the arrays starting at the specified 1663 * offset in the order <code>[x0, y0, x1, y1, ..., xn, yn]</code>. 1664 * This method can be used only for 2D transforms. 1665 * @param srcPts the array containing the source point coordinates. 1666 * Each point is stored as a pair of x, y coordinates. 1667 * @param srcOff the offset to the first point to be transformed 1668 * in the source array 1669 * @param dstPts the array into which the transformed point coordinates 1670 * are returned. Each point is stored as a pair of x, y 1671 * coordinates. 1672 * @param dstOff the offset to the location of the first 1673 * transformed point that is stored in the destination array 1674 * @param numPts the number of points to be transformed 1675 * @throws IllegalStateException if this is a 3D transform 1676 * @throws NonInvertibleTransformException if this transform 1677 * cannot be inverted 1678 * @throws NullPointerException if {@code srcPts} or (@code dstPts} is null 1679 * @since JavaFX 8.0 1680 */ 1681 public void inverseTransform2DPoints(double[] srcPts, int srcOff, 1682 double[] dstPts, int dstOff, 1683 int numPts) throws NonInvertibleTransformException{ 1684 1685 if (srcPts == null || dstPts == null) { 1686 throw new NullPointerException(); 1687 } 1688 1689 if (!isType2D()) { 1690 throw new IllegalStateException("Cannot transform 2D points " 1691 + "with a 3D transform"); 1692 } 1693 1694 // deal with overlapping arrays 1695 srcOff = getFixedSrcOffset(srcPts, srcOff, dstPts, dstOff, numPts, 2); 1696 1697 // do the transformations 1698 inverseTransform2DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts); 1699 } 1700 1701 /** 1702 * Transforms an array of floating point coordinates by the inverse 1703 * of this transform. 1704 * The three coordinate array sections can be exactly the same or 1705 * can be overlapping sections of the same array without affecting the 1706 * validity of the results. 1707 * This method ensures that no source coordinates are overwritten by a 1708 * previous operation before they can be transformed. 1709 * The coordinates are stored in the arrays starting at the specified 1710 * offset in the order <code>[x0, y0, z0, x1, y1, z1, ..., xn, yn, zn]</code>. 1711 * @param srcPts the array containing the source point coordinates. 1712 * Each point is stored as a triplet of x, y, z coordinates. 1713 * @param srcOff the offset to the first point to be transformed 1714 * in the source array 1715 * @param dstPts the array into which the transformed point coordinates 1716 * are returned. Each point is stored as a triplet of x, y, z 1717 * coordinates. 1718 * @param dstOff the offset to the location of the first 1719 * transformed point that is stored in the destination array 1720 * @param numPts the number of points to be transformed 1721 * @throws NonInvertibleTransformException if this transform 1722 * cannot be inverted 1723 * @throws NullPointerException if {@code srcPts} or (@code dstPts} is null 1724 * @since JavaFX 8.0 1725 */ 1726 public void inverseTransform3DPoints(double[] srcPts, int srcOff, 1727 double[] dstPts, int dstOff, 1728 int numPts) throws NonInvertibleTransformException { 1729 1730 if (srcPts == null || dstPts == null) { 1731 throw new NullPointerException(); 1732 } 1733 1734 // deal with overlapping arrays 1735 srcOff = getFixedSrcOffset(srcPts, srcOff, dstPts, dstOff, numPts, 3); 1736 1737 // do the transformations 1738 inverseTransform3DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts); 1739 } 1740 1741 /** 1742 * Transforms the relative magnitude vector by the inverse of this transform. 1743 * The vector is transformed without applying the translation components 1744 * of the affine transformation matrix. 1745 * This method can be used only for a 2D transform. 1746 * @param x vector magnitude in the direction of the X axis 1747 * @param y vector magnitude in the direction of the Y axis 1748 * @return the inversely transformed relative magnitude vector represented 1749 * by a {@code Point2D} instance 1750 * @throws IllegalStateException if this is a 3D transform 1751 * @throws NonInvertibleTransformException if this transform 1752 * cannot be inverted 1753 * @since JavaFX 8.0 1754 */ 1755 public Point2D inverseDeltaTransform(double x, double y) 1756 throws NonInvertibleTransformException { 1757 1758 ensureCanTransform2DPoint(); 1759 1760 return getInverseCache().deltaTransform(x, y); 1761 } 1762 1763 /** 1764 * Transforms the relative magnitude vector represented by the specified 1765 * {@code Point2D} instance by the inverse of this transform. 1766 * The vector is transformed without applying the translation components 1767 * of the affine transformation matrix. 1768 * This method can be used only for a 2D transform. 1769 * @param point the relative magnitude vector 1770 * @return the inversely transformed relative magnitude vector represented 1771 * by a {@code Point2D} instance 1772 * @throws IllegalStateException if this is a 3D transform 1773 * @throws NonInvertibleTransformException if this transform 1774 * cannot be inverted 1775 * @throws NullPointerException if the specified {@code point} is null 1776 * @since JavaFX 8.0 1777 */ 1778 public Point2D inverseDeltaTransform(Point2D point) 1779 throws NonInvertibleTransformException { 1780 return inverseDeltaTransform(point.getX(), point.getY()); 1781 } 1782 1783 /** 1784 * Transforms the relative magnitude vector by the inverse of this transform. 1785 * The vector is transformed without applying the translation components 1786 * of the affine transformation matrix. 1787 * @param x vector magnitude in the direction of the X axis 1788 * @param y vector magnitude in the direction of the Y axis 1789 * @return the inversely transformed relative magnitude vector represented 1790 * by a {@code Point3D} instance 1791 * @throws NonInvertibleTransformException if this transform 1792 * cannot be inverted 1793 * @since JavaFX 8.0 1794 */ 1795 public Point3D inverseDeltaTransform(double x, double y, double z) 1796 throws NonInvertibleTransformException { 1797 1798 return getInverseCache().deltaTransform(x, y, z); 1799 } 1800 1801 /** 1802 * Transforms the relative magnitude vector represented by the specified 1803 * {@code Point3D} instance by the inverse of this transform. 1804 * The vector is transformed without applying the translation components 1805 * of the affine transformation matrix. 1806 * @param point the relative magnitude vector 1807 * @return the inversely transformed relative magnitude vector represented 1808 * by a {@code Point3D} instance 1809 * @throws NonInvertibleTransformException if this transform 1810 * cannot be inverted 1811 * @throws NullPointerException if the specified {@code point} is null 1812 * @since JavaFX 8.0 1813 */ 1814 public Point3D inverseDeltaTransform(Point3D point) 1815 throws NonInvertibleTransformException { 1816 return inverseDeltaTransform(point.getX(), point.getY(), point.getZ()); 1817 } 1818 1819 /** 1820 * Helper method for transforming arrays of points that deals with 1821 * overlapping arrays. 1822 * @return the (if necessary fixed) srcOff 1823 */ 1824 private int getFixedSrcOffset(double[] srcPts, int srcOff, 1825 double[] dstPts, int dstOff, 1826 int numPts, int dimensions) { 1827 1828 if (dstPts == srcPts && 1829 dstOff > srcOff && dstOff < srcOff + numPts * dimensions) 1830 { 1831 // If the arrays overlap partially with the destination higher 1832 // than the source and we transform the coordinates normally 1833 // we would overwrite some of the later source coordinates 1834 // with results of previous transformations. 1835 // To get around this we use arraycopy to copy the points 1836 // to their final destination with correct overwrite 1837 // handling and then transform them in place in the new 1838 // safer location. 1839 System.arraycopy(srcPts, srcOff, dstPts, dstOff, numPts * dimensions); 1840 return dstOff; 1841 } 1842 1843 return srcOff; 1844 } 1845 1846 /* ************************************************************************* 1847 * * 1848 * Event Dispatch * 1849 * * 1850 **************************************************************************/ 1851 1852 private EventHandlerManager internalEventDispatcher; 1853 private EventHandlerManager getInternalEventDispatcher() { 1854 if (internalEventDispatcher == null) { 1855 internalEventDispatcher = new EventHandlerManager(this); 1856 } 1857 return internalEventDispatcher; 1858 } 1859 private ObjectProperty<EventHandler<? super TransformChangedEvent>> 1860 onTransformChanged; 1861 1862 @Override 1863 public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { 1864 return internalEventDispatcher == null 1865 ? tail : tail.append(getInternalEventDispatcher()); 1866 } 1867 1868 /** 1869 * <p> 1870 * Registers an event handler to this transform. Any event filters are first 1871 * processed, then the specified onFoo event handlers, and finally any 1872 * event handlers registered by this method. 1873 * </p><p> 1874 * Currently the only event delivered to a {@code Transform} is the 1875 * {@code TransformChangedEvent} with it's single type 1876 * {@code TRANSFORM_CHANGED}. 1877 * </p> 1878 * 1879 * @param <T> the specific event class of the handler 1880 * @param eventType the type of the events to receive by the handler 1881 * @param eventHandler the handler to register 1882 * @throws NullPointerException if the event type or handler is null 1883 * @since JavaFX 8.0 1884 */ 1885 public final <T extends Event> void addEventHandler( 1886 final EventType<T> eventType, 1887 final EventHandler<? super T> eventHandler) { 1888 getInternalEventDispatcher() 1889 .addEventHandler(eventType, eventHandler); 1890 // need to validate all properties to get the change events 1891 validate(); 1892 } 1893 1894 /** 1895 * Unregisters a previously registered event handler from this transform. 1896 * One handler might have been registered for different event types, so the 1897 * caller needs to specify the particular event type from which to 1898 * unregister the handler. 1899 * 1900 * @param <T> the specific event class of the handler 1901 * @param eventType the event type from which to unregister 1902 * @param eventHandler the handler to unregister 1903 * @throws NullPointerException if the event type or handler is null 1904 * @since JavaFX 8.0 1905 */ 1906 public final <T extends Event> void removeEventHandler( 1907 final EventType<T> eventType, 1908 final EventHandler<? super T> eventHandler) { 1909 getInternalEventDispatcher() 1910 .removeEventHandler(eventType, eventHandler); 1911 } 1912 1913 /** 1914 * <p> 1915 * Registers an event filter to this transform. Registered event filters get 1916 * an event before any associated event handlers. 1917 * </p><p> 1918 * Currently the only event delivered to a {@code Transform} is the 1919 * {@code TransformChangedEvent} with it's single type 1920 * {@code TRANSFORM_CHANGED}. 1921 * <p> 1922 * 1923 * @param <T> the specific event class of the filter 1924 * @param eventType the type of the events to receive by the filter 1925 * @param eventFilter the filter to register 1926 * @throws NullPointerException if the event type or filter is null 1927 * @since JavaFX 8.0 1928 */ 1929 public final <T extends Event> void addEventFilter( 1930 final EventType<T> eventType, 1931 final EventHandler<? super T> eventFilter) { 1932 getInternalEventDispatcher() 1933 .addEventFilter(eventType, eventFilter); 1934 // need to validate all properties to get the change events 1935 validate(); 1936 } 1937 1938 /** 1939 * Unregisters a previously registered event filter from this transform. One 1940 * filter might have been registered for different event types, so the 1941 * caller needs to specify the particular event type from which to 1942 * unregister the filter. 1943 * 1944 * @param <T> the specific event class of the filter 1945 * @param eventType the event type from which to unregister 1946 * @param eventFilter the filter to unregister 1947 * @throws NullPointerException if the event type or filter is null 1948 * @since JavaFX 8.0 1949 */ 1950 public final <T extends Event> void removeEventFilter( 1951 final EventType<T> eventType, 1952 final EventHandler<? super T> eventFilter) { 1953 getInternalEventDispatcher() 1954 .removeEventFilter(eventType, eventFilter); 1955 } 1956 1957 /** 1958 * Sets the onTransformChanged event handler which is called whenever 1959 * the transform changes any of its parameters. 1960 * 1961 * @param value the event handler, can be null to clear it 1962 * @since JavaFX 8.0 1963 */ 1964 public final void setOnTransformChanged( 1965 EventHandler<? super TransformChangedEvent> value) { 1966 onTransformChangedProperty().set(value); 1967 // need to validate all properties to get the change events 1968 validate(); 1969 } 1970 1971 /** 1972 * Gets the onTransformChanged event handler. 1973 * @return the event handler previously set by {@code setOnTransformChanged} 1974 * method, null if the handler is not set. 1975 * @since JavaFX 8.0 1976 */ 1977 public final EventHandler<? super TransformChangedEvent> getOnTransformChanged() { 1978 return (onTransformChanged == null) ? null : onTransformChanged.get(); 1979 } 1980 1981 /** 1982 * The onTransformChanged event handler is called whenever the transform 1983 * changes any of its parameters. 1984 * @since JavaFX 8.0 1985 */ 1986 public final ObjectProperty<EventHandler<? super TransformChangedEvent>> 1987 onTransformChangedProperty() { 1988 if (onTransformChanged == null) { 1989 1990 onTransformChanged = new SimpleObjectProperty<EventHandler 1991 <? super TransformChangedEvent>>(this, "onTransformChanged") { 1992 1993 @Override protected void invalidated() { 1994 getInternalEventDispatcher().setEventHandler( 1995 TransformChangedEvent.TRANSFORM_CHANGED, get()); 1996 } 1997 }; 1998 } 1999 2000 return onTransformChanged; 2001 } 2002 2003 /* ************************************************************************* 2004 * * 2005 * Internal implementation stuff * 2006 * * 2007 **************************************************************************/ 2008 2009 /** 2010 * Makes sure the specified matrix type can be requested from this transform. 2011 * Is used for convenience in various methods that accept 2012 * the MatrixType argument. 2013 * @param type matrix type to check 2014 * @throws IllegalArgumentException if this is a 3D transform and 2015 * a 2D type is requested 2016 */ 2017 void checkRequestedMAT(MatrixType type) throws IllegalArgumentException{ 2018 if (type.is2D() && !isType2D()) { 2019 throw new IllegalArgumentException("Cannot access 2D matrix " 2020 + "for a 3D transform"); 2021 } 2022 } 2023 2024 /** 2025 * Makes sure this is a 2D transform. 2026 * Is used for convenience in various 2D point transformation methods. 2027 * @throws IllegalStateException if this is a 2D transform 2028 */ 2029 void ensureCanTransform2DPoint() throws IllegalStateException { 2030 if (!isType2D()) { 2031 throw new IllegalStateException("Cannot transform 2D point " 2032 + "with a 3D transform"); 2033 } 2034 } 2035 2036 /** 2037 * Needed for the proper delivery of the TransformChangedEvent. 2038 * If the members are invalid, the transformChanged() notification 2039 * is not called and the event is not delivered. To avoid that 2040 * we need to manually validate all properties. Subclasses validate 2041 * their specific properties. 2042 */ 2043 void validate() { 2044 getMxx(); getMxy(); getMxz(); getTx(); 2045 getMyx(); getMyy(); getMyz(); getTy(); 2046 getMzx(); getMzy(); getMzz(); getTz(); 2047 } 2048 2049 abstract void apply(Affine3D t); 2050 2051 abstract BaseTransform derive(BaseTransform t); 2052 2053 void add(final Node node) { 2054 nodes.add(node); 2055 } 2056 2057 void remove(final Node node) { 2058 nodes.remove(node); 2059 } 2060 2061 /** 2062 * This method must be called by all transforms whenever any of their 2063 * parameters changes. It is typically called when any of the transform's 2064 * properties is invalidated (it is OK to skip the call if an invalid 2065 * property is set). 2066 * @since JavaFX 8.0 2067 */ 2068 protected void transformChanged() { 2069 inverseCache = null; 2070 final Iterator iterator = nodes.iterator(); 2071 while (iterator.hasNext()) { 2072 NodeHelper.transformsChanged(((Node) iterator.next())); 2073 } 2074 2075 if (type2D != null) { 2076 type2D.invalidate(); 2077 } 2078 2079 if (identity != null) { 2080 identity.invalidate(); 2081 } 2082 2083 if (internalEventDispatcher != null) { 2084 // need to validate all properties for the event to be fired next time 2085 validate(); 2086 Event.fireEvent(this, new TransformChangedEvent(this, this)); 2087 } 2088 } 2089 2090 /** 2091 * Visitor from {@code Affine} class which provides an efficient 2092 * {@code append} operation for the subclasses. 2093 * @param a {@code Affine} instance to append to 2094 */ 2095 void appendTo(Affine a) { 2096 a.append(getMxx(), getMxy(), getMxz(), getTx(), 2097 getMyx(), getMyy(), getMyz(), getTy(), 2098 getMzx(), getMzy(), getMzz(), getTz()); 2099 } 2100 2101 /** 2102 * Visitor from {@code Affine} class which provides an efficient 2103 * {@code prepend} operation for the subclasses. 2104 * @param a {@code Affine} instance to prepend to 2105 */ 2106 void prependTo(Affine a) { 2107 a.prepend(getMxx(), getMxy(), getMxz(), getTx(), 2108 getMyx(), getMyy(), getMyz(), getTy(), 2109 getMzx(), getMzy(), getMzz(), getTz()); 2110 } 2111 2112 /** 2113 * <p> 2114 * Gets the inverse transform cache. 2115 * </p><p> 2116 * Computing the inverse transform is generally an expensive operation, 2117 * so once it is needed we cache the result (throwing it away when the 2118 * transform changes). The subclasses may avoid using the cache if their 2119 * inverse can be computed quickly on the fly. 2120 * </p><p> 2121 * This method computes the inverse if the cache is not valid. 2122 * </p> 2123 * @return the cached inverse transformation 2124 * @throws NonInvertibleTransformException if this transform 2125 * cannot be inverted 2126 */ 2127 private Transform getInverseCache() throws NonInvertibleTransformException { 2128 if (inverseCache == null || inverseCache.get() == null) { 2129 Affine inv = new Affine( 2130 getMxx(), getMxy(), getMxz(), getTx(), 2131 getMyx(), getMyy(), getMyz(), getTy(), 2132 getMzx(), getMzy(), getMzz(), getTz()); 2133 inv.invert(); 2134 inverseCache = new SoftReference<Transform>(inv); 2135 return inv; 2136 } 2137 2138 return inverseCache.get(); 2139 } 2140 2141 /** 2142 * Used only by tests to emulate garbage collecting the soft references 2143 */ 2144 void clearInverseCache() { 2145 if (inverseCache != null) { 2146 inverseCache.clear(); 2147 } 2148 } 2149 2150 /************************************************************************** 2151 * ImmutableTransform Class and supporting methods 2152 **************************************************************************/ 2153 2154 static Transform createImmutableTransform() { 2155 return new ImmutableTransform(); 2156 } 2157 2158 static Transform createImmutableTransform( 2159 double mxx, double mxy, double mxz, double tx, 2160 double myx, double myy, double myz, double ty, 2161 double mzx, double mzy, double mzz, double tz) { 2162 return new ImmutableTransform( 2163 mxx, mxy, mxz, tx, 2164 myx, myy, myz, ty, 2165 mzx, mzy, mzz, tz); 2166 } 2167 2168 static Transform createImmutableTransform(Transform transform, 2169 double mxx, double mxy, double mxz, double tx, 2170 double myx, double myy, double myz, double ty, 2171 double mzx, double mzy, double mzz, double tz) { 2172 if (transform == null) { 2173 return new ImmutableTransform( 2174 mxx, mxy, mxz, tx, 2175 myx, myy, myz, ty, 2176 mzx, mzy, mzz, tz); 2177 } 2178 ((Transform.ImmutableTransform) transform).setToTransform( 2179 mxx, mxy, mxz, tx, 2180 myx, myy, myz, ty, 2181 mzx, mzy, mzz, tz); 2182 return transform; 2183 } 2184 2185 static Transform createImmutableTransform(Transform transform, 2186 Transform left, Transform right) { 2187 if (transform == null) { 2188 transform = new ImmutableTransform(); 2189 } 2190 ((Transform.ImmutableTransform) transform).setToConcatenation( 2191 (ImmutableTransform) left, (ImmutableTransform) right); 2192 return transform; 2193 } 2194 2195 /** 2196 * Immutable transformation with performance optimizations based on Affine. 2197 * 2198 * From user's perspective, this transform is immutable. However, we can 2199 * modify it internally. This allows for reusing instances that were 2200 * not handed to users. The caller is responsible for not modifying 2201 * user-visible instances. 2202 * 2203 * Note: can't override Transform's package private methods so they cannot 2204 * be optimized. Currently not a big deal. 2205 */ 2206 static class ImmutableTransform extends Transform { 2207 2208 private static final int APPLY_IDENTITY = 0; 2209 private static final int APPLY_TRANSLATE = 1; 2210 private static final int APPLY_SCALE = 2; 2211 private static final int APPLY_SHEAR = 4; 2212 private static final int APPLY_NON_3D = 0; 2213 private static final int APPLY_3D_COMPLEX = 4; 2214 private transient int state2d; 2215 private transient int state3d; 2216 2217 private double xx; 2218 private double xy; 2219 private double xz; 2220 private double yx; 2221 private double yy; 2222 private double yz; 2223 private double zx; 2224 private double zy; 2225 private double zz; 2226 private double xt; 2227 private double yt; 2228 private double zt; 2229 2230 ImmutableTransform() { 2231 xx = yy = zz = 1.0; 2232 } 2233 2234 ImmutableTransform(Transform transform) { 2235 this(transform.getMxx(), transform.getMxy(), transform.getMxz(), 2236 transform.getTx(), 2237 transform.getMyx(), transform.getMyy(), transform.getMyz(), 2238 transform.getTy(), 2239 transform.getMzx(), transform.getMzy(), transform.getMzz(), 2240 transform.getTz()); 2241 } 2242 2243 ImmutableTransform(double mxx, double mxy, double mxz, double tx, 2244 double myx, double myy, double myz, double ty, 2245 double mzx, double mzy, double mzz, double tz) { 2246 xx = mxx; 2247 xy = mxy; 2248 xz = mxz; 2249 xt = tx; 2250 2251 yx = myx; 2252 yy = myy; 2253 yz = myz; 2254 yt = ty; 2255 2256 zx = mzx; 2257 zy = mzy; 2258 zz = mzz; 2259 zt = tz; 2260 2261 updateState(); 2262 } 2263 2264 // Beware: this is modifying immutable transform! 2265 // It is private and it is there just for the purpose of reusing 2266 // instances not given to users 2267 private void setToTransform(double mxx, double mxy, double mxz, double tx, 2268 double myx, double myy, double myz, double ty, 2269 double mzx, double mzy, double mzz, double tz) 2270 { 2271 xx = mxx; 2272 xy = mxy; 2273 xz = mxz; 2274 xt = tx; 2275 yx = myx; 2276 yy = myy; 2277 yz = myz; 2278 yt = ty; 2279 zx = mzx; 2280 zy = mzy; 2281 zz = mzz; 2282 zt = tz; 2283 updateState(); 2284 } 2285 2286 // Beware: this is modifying immutable transform! 2287 // It is private and it is there just for the purpose of reusing 2288 // instances not given to users 2289 private void setToConcatenation(ImmutableTransform left, ImmutableTransform right) { 2290 if (left.state3d == APPLY_NON_3D && right.state3d == APPLY_NON_3D) { 2291 xx = left.xx * right.xx + left.xy * right.yx; 2292 xy = left.xx * right.xy + left.xy * right.yy; 2293 xt = left.xx * right.xt + left.xy * right.yt + left.xt; 2294 yx = left.yx * right.xx + left.yy * right.yx; 2295 yy = left.yx * right.xy + left.yy * right.yy; 2296 yt = left.yx * right.xt + left.yy * right.yt + left.yt; 2297 if (state3d != APPLY_NON_3D) { 2298 xz = yz = zx = zy = zt = 0.0; 2299 zz = 1.0; 2300 state3d = APPLY_NON_3D; 2301 } 2302 updateState2D(); 2303 } else { 2304 xx = left.xx * right.xx + left.xy * right.yx + left.xz * right.zx; 2305 xy = left.xx * right.xy + left.xy * right.yy + left.xz * right.zy; 2306 xz = left.xx * right.xz + left.xy * right.yz + left.xz * right.zz; 2307 xt = left.xx * right.xt + left.xy * right.yt + left.xz * right.zt + left.xt; 2308 yx = left.yx * right.xx + left.yy * right.yx + left.yz * right.zx; 2309 yy = left.yx * right.xy + left.yy * right.yy + left.yz * right.zy; 2310 yz = left.yx * right.xz + left.yy * right.yz + left.yz * right.zz; 2311 yt = left.yx * right.xt + left.yy * right.yt + left.yz * right.zt + left.yt; 2312 zx = left.zx * right.xx + left.zy * right.yx + left.zz * right.zx; 2313 zy = left.zx * right.xy + left.zy * right.yy + left.zz * right.zy; 2314 zz = left.zx * right.xz + left.zy * right.yz + left.zz * right.zz; 2315 zt = left.zx * right.xt + left.zy * right.yt + left.zz * right.zt + left.zt; 2316 updateState(); 2317 } 2318 // could be further optimized using the states, but that would 2319 // require a lot of code (see Affine and all its append* methods) 2320 } 2321 2322 @Override 2323 public double getMxx() { 2324 return xx; 2325 } 2326 2327 @Override 2328 public double getMxy() { 2329 return xy; 2330 } 2331 2332 @Override 2333 public double getMxz() { 2334 return xz; 2335 } 2336 2337 @Override 2338 public double getTx() { 2339 return xt; 2340 } 2341 2342 @Override 2343 public double getMyx() { 2344 return yx; 2345 } 2346 2347 @Override 2348 public double getMyy() { 2349 return yy; 2350 } 2351 2352 @Override 2353 public double getMyz() { 2354 return yz; 2355 } 2356 2357 @Override 2358 public double getTy() { 2359 return yt; 2360 } 2361 2362 @Override 2363 public double getMzx() { 2364 return zx; 2365 } 2366 2367 @Override 2368 public double getMzy() { 2369 return zy; 2370 } 2371 2372 @Override 2373 public double getMzz() { 2374 return zz; 2375 } 2376 2377 @Override 2378 public double getTz() { 2379 return zt; 2380 } 2381 2382 /* ************************************************************************* 2383 * * 2384 * State getters * 2385 * * 2386 **************************************************************************/ 2387 2388 @Override 2389 public double determinant() { 2390 switch(state3d) { 2391 default: 2392 stateError(); 2393 // cannot reach 2394 case APPLY_NON_3D: 2395 switch (state2d) { 2396 default: 2397 stateError(); 2398 // cannot reach 2399 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 2400 case APPLY_SHEAR | APPLY_SCALE: 2401 return xx * yy - xy * yx; 2402 case APPLY_SHEAR | APPLY_TRANSLATE: 2403 case APPLY_SHEAR: 2404 return -(xy* yx); 2405 case APPLY_SCALE | APPLY_TRANSLATE: 2406 case APPLY_SCALE: 2407 return xx * yy; 2408 case APPLY_TRANSLATE: 2409 case APPLY_IDENTITY: 2410 return 1.0; 2411 } 2412 case APPLY_TRANSLATE: 2413 return 1.0; 2414 case APPLY_SCALE: 2415 case APPLY_SCALE | APPLY_TRANSLATE: 2416 return xx * yy * zz; 2417 case APPLY_3D_COMPLEX: 2418 return (xx* (yy * zz - zy * yz) + 2419 xy* (yz * zx - zz * yx) + 2420 xz* (yx * zy - zx * yy)); 2421 } 2422 } 2423 2424 @Override 2425 public Transform createConcatenation(Transform transform) { 2426 javafx.scene.transform.Affine a = new Affine(this); 2427 a.append(transform); 2428 return a; 2429 } 2430 2431 @Override 2432 public javafx.scene.transform.Affine createInverse() throws NonInvertibleTransformException { 2433 javafx.scene.transform.Affine t = new Affine(this); 2434 t.invert(); 2435 return t; 2436 } 2437 2438 @Override 2439 public Transform clone() { 2440 return new ImmutableTransform(this); 2441 } 2442 2443 /* ************************************************************************* 2444 * * 2445 * Transform, Inverse Transform * 2446 * * 2447 **************************************************************************/ 2448 2449 @Override 2450 public Point2D transform(double x, double y) { 2451 ensureCanTransform2DPoint(); 2452 2453 switch (state2d) { 2454 default: 2455 stateError(); 2456 // cannot reach 2457 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 2458 return new Point2D( 2459 xx * x + xy * y + xt, 2460 yx * x + yy * y + yt); 2461 case APPLY_SHEAR | APPLY_SCALE: 2462 return new Point2D( 2463 xx * x + xy * y, 2464 yx * x + yy * y); 2465 case APPLY_SHEAR | APPLY_TRANSLATE: 2466 return new Point2D( 2467 xy * y + xt, 2468 yx * x + yt); 2469 case APPLY_SHEAR: 2470 return new Point2D(xy * y, yx * x); 2471 case APPLY_SCALE | APPLY_TRANSLATE: 2472 return new Point2D( 2473 xx * x + xt, 2474 yy * y + yt); 2475 case APPLY_SCALE: 2476 return new Point2D(xx * x, yy * y); 2477 case APPLY_TRANSLATE: 2478 return new Point2D(x + xt, y + yt); 2479 case APPLY_IDENTITY: 2480 return new Point2D(x, y); 2481 } 2482 } 2483 2484 @Override 2485 public Point3D transform(double x, double y, double z) { 2486 switch (state3d) { 2487 default: 2488 stateError(); 2489 // cannot reach 2490 case APPLY_NON_3D: 2491 switch (state2d) { 2492 default: 2493 stateError(); 2494 // cannot reach 2495 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 2496 return new Point3D( 2497 xx * x + xy * y + xt, 2498 yx * x + yy * y + yt, z); 2499 case APPLY_SHEAR | APPLY_SCALE: 2500 return new Point3D( 2501 xx * x + xy * y, 2502 yx * x + yy * y, z); 2503 case APPLY_SHEAR | APPLY_TRANSLATE: 2504 return new Point3D( 2505 xy * y + xt, yx * x + yt, 2506 z); 2507 case APPLY_SHEAR: 2508 return new Point3D(xy * y, yx * x, z); 2509 case APPLY_SCALE | APPLY_TRANSLATE: 2510 return new Point3D( 2511 xx * x + xt, yy * y + yt, 2512 z); 2513 case APPLY_SCALE: 2514 return new Point3D(xx * x, yy * y, z); 2515 case APPLY_TRANSLATE: 2516 return new Point3D(x + xt, y + yt, z); 2517 case APPLY_IDENTITY: 2518 return new Point3D(x, y, z); 2519 } 2520 case APPLY_TRANSLATE: 2521 return new Point3D(x + xt, y + yt, z + zt); 2522 case APPLY_SCALE: 2523 return new Point3D(xx * x, yy * y, zz * z); 2524 case APPLY_SCALE | APPLY_TRANSLATE: 2525 return new Point3D( 2526 xx * x + xt, 2527 yy * y + yt, 2528 zz * z + zt); 2529 case APPLY_3D_COMPLEX: 2530 return new Point3D( 2531 xx * x + xy * y + xz * z + xt, 2532 yx * x + yy * y + yz * z + yt, 2533 zx * x + zy * y + zz * z + zt); 2534 } 2535 } 2536 2537 @Override 2538 public Point2D deltaTransform(double x, double y) { 2539 ensureCanTransform2DPoint(); 2540 2541 switch (state2d) { 2542 default: 2543 stateError(); 2544 // cannot reach 2545 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 2546 case APPLY_SHEAR | APPLY_SCALE: 2547 return new Point2D( 2548 xx * x + xy * y, 2549 yx * x + yy * y); 2550 case APPLY_SHEAR | APPLY_TRANSLATE: 2551 case APPLY_SHEAR: 2552 return new Point2D(xy * y, yx * x); 2553 case APPLY_SCALE | APPLY_TRANSLATE: 2554 case APPLY_SCALE: 2555 return new Point2D(xx * x, yy * y); 2556 case APPLY_TRANSLATE: 2557 case APPLY_IDENTITY: 2558 return new Point2D(x, y); 2559 } 2560 } 2561 2562 @Override 2563 public Point3D deltaTransform(double x, double y, double z) { 2564 switch (state3d) { 2565 default: 2566 stateError(); 2567 // cannot reach 2568 case APPLY_NON_3D: 2569 switch (state2d) { 2570 default: 2571 stateError(); 2572 // cannot reach 2573 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 2574 case APPLY_SHEAR | APPLY_SCALE: 2575 return new Point3D( 2576 xx * x + xy * y, 2577 yx * x + yy * y, z); 2578 case APPLY_SHEAR | APPLY_TRANSLATE: 2579 case APPLY_SHEAR: 2580 return new Point3D(xy * y, yx * x, z); 2581 case APPLY_SCALE | APPLY_TRANSLATE: 2582 case APPLY_SCALE: 2583 return new Point3D(xx * x, yy * y, z); 2584 case APPLY_TRANSLATE: 2585 case APPLY_IDENTITY: 2586 return new Point3D(x, y, z); 2587 } 2588 case APPLY_TRANSLATE: 2589 return new Point3D(x, y, z); 2590 case APPLY_SCALE: 2591 case APPLY_SCALE | APPLY_TRANSLATE: 2592 return new Point3D(xx * x, yy * y, zz * z); 2593 case APPLY_3D_COMPLEX: 2594 return new Point3D( 2595 xx * x + xy * y + xz * z, 2596 yx * x + yy * y + yz * z, 2597 zx * x + zy * y + zz * z); 2598 } 2599 } 2600 2601 @Override 2602 public Point2D inverseTransform(double x, double y) 2603 throws NonInvertibleTransformException { 2604 ensureCanTransform2DPoint(); 2605 2606 switch (state2d) { 2607 default: 2608 return super.inverseTransform(x, y); 2609 case APPLY_SHEAR | APPLY_TRANSLATE: 2610 if (xy == 0.0 || yx == 0.0) { 2611 throw new NonInvertibleTransformException("Determinant is 0"); 2612 } 2613 return new Point2D( 2614 (1.0 / yx) * y - yt / yx, 2615 (1.0 / xy) * x - xt / xy); 2616 case APPLY_SHEAR: 2617 if (xy == 0.0 || yx == 0.0) { 2618 throw new NonInvertibleTransformException("Determinant is 0"); 2619 } 2620 return new Point2D((1.0 / yx) * y, (1.0 / xy) * x); 2621 case APPLY_SCALE | APPLY_TRANSLATE: 2622 if (xx == 0.0 || yy == 0.0) { 2623 throw new NonInvertibleTransformException("Determinant is 0"); 2624 } 2625 return new Point2D( 2626 (1.0 / xx) * x - xt / xx, 2627 (1.0 / yy) * y - yt / yy); 2628 case APPLY_SCALE: 2629 if (xx == 0.0 || yy == 0.0) { 2630 throw new NonInvertibleTransformException("Determinant is 0"); 2631 } 2632 return new Point2D((1.0 / xx) * x, (1.0 / yy) * y); 2633 case APPLY_TRANSLATE: 2634 return new Point2D(x - xt, y - yt); 2635 case APPLY_IDENTITY: 2636 return new Point2D(x, y); 2637 } 2638 } 2639 2640 @Override 2641 public Point3D inverseTransform(double x, double y, double z) 2642 throws NonInvertibleTransformException { 2643 switch(state3d) { 2644 default: 2645 stateError(); 2646 // cannot reach 2647 case APPLY_NON_3D: 2648 switch (state2d) { 2649 default: 2650 return super.inverseTransform(x, y, z); 2651 case APPLY_SHEAR | APPLY_TRANSLATE: 2652 if (xy == 0.0 || yx == 0.0) { 2653 throw new NonInvertibleTransformException( 2654 "Determinant is 0"); 2655 } 2656 return new Point3D( 2657 (1.0 / yx) * y - yt / yx, 2658 (1.0 / xy) * x - xt / xy, z); 2659 case APPLY_SHEAR: 2660 if (xy == 0.0 || yx == 0.0) { 2661 throw new NonInvertibleTransformException( 2662 "Determinant is 0"); 2663 } 2664 return new Point3D( 2665 (1.0 / yx) * y, 2666 (1.0 / xy) * x, z); 2667 case APPLY_SCALE | APPLY_TRANSLATE: 2668 if (xx == 0.0 || yy == 0.0) { 2669 throw new NonInvertibleTransformException( 2670 "Determinant is 0"); 2671 } 2672 return new Point3D( 2673 (1.0 / xx) * x - xt / xx, 2674 (1.0 / yy) * y - yt / yy, z); 2675 case APPLY_SCALE: 2676 if (xx == 0.0 || yy == 0.0) { 2677 throw new NonInvertibleTransformException( 2678 "Determinant is 0"); 2679 } 2680 return new Point3D((1.0 / xx) * x, (1.0 / yy) * y, z); 2681 case APPLY_TRANSLATE: 2682 return new Point3D(x - xt, y - yt, z); 2683 case APPLY_IDENTITY: 2684 return new Point3D(x, y, z); 2685 } 2686 case APPLY_TRANSLATE: 2687 return new Point3D(x - xt, y - yt, z - zt); 2688 case APPLY_SCALE: 2689 if (xx == 0.0 || yy == 0.0 || zz == 0.0) { 2690 throw new NonInvertibleTransformException("Determinant is 0"); 2691 } 2692 return new Point3D( 2693 (1.0 / xx) * x, 2694 (1.0 / yy) * y, 2695 (1.0 / zz) * z); 2696 case APPLY_SCALE | APPLY_TRANSLATE: 2697 if (xx == 0.0 || yy == 0.0 || zz == 0.0) { 2698 throw new NonInvertibleTransformException("Determinant is 0"); 2699 } 2700 return new Point3D( 2701 (1.0 / xx) * x - xt / xx, 2702 (1.0 / yy) * y - yt / yy, 2703 (1.0 / zz) * z - zt / zz); 2704 case APPLY_3D_COMPLEX: 2705 return super.inverseTransform(x, y, z); 2706 } 2707 } 2708 2709 @Override 2710 public Point2D inverseDeltaTransform(double x, double y) 2711 throws NonInvertibleTransformException { 2712 ensureCanTransform2DPoint(); 2713 2714 switch (state2d) { 2715 default: 2716 return super.inverseDeltaTransform(x, y); 2717 case APPLY_SHEAR | APPLY_TRANSLATE: 2718 case APPLY_SHEAR: 2719 if (xy == 0.0 || yx == 0.0) { 2720 throw new NonInvertibleTransformException("Determinant is 0"); 2721 } 2722 return new Point2D((1.0 / yx) * y, (1.0 / xy) * x); 2723 case APPLY_SCALE | APPLY_TRANSLATE: 2724 case APPLY_SCALE: 2725 if (xx == 0.0 || yy == 0.0) { 2726 throw new NonInvertibleTransformException("Determinant is 0"); 2727 } 2728 return new Point2D((1.0 / xx) * x, (1.0 / yy) * y); 2729 case APPLY_TRANSLATE: 2730 case APPLY_IDENTITY: 2731 return new Point2D(x, y); 2732 } 2733 } 2734 2735 @Override 2736 public Point3D inverseDeltaTransform(double x, double y, double z) 2737 throws NonInvertibleTransformException { 2738 switch(state3d) { 2739 default: 2740 stateError(); 2741 // cannot reach 2742 case APPLY_NON_3D: 2743 switch (state2d) { 2744 default: 2745 return super.inverseDeltaTransform(x, y, z); 2746 case APPLY_SHEAR | APPLY_TRANSLATE: 2747 case APPLY_SHEAR: 2748 if (xy == 0.0 || yx == 0.0) { 2749 throw new NonInvertibleTransformException( 2750 "Determinant is 0"); 2751 } 2752 return new Point3D( 2753 (1.0 / yx) * y, 2754 (1.0 / xy) * x, z); 2755 case APPLY_SCALE | APPLY_TRANSLATE: 2756 case APPLY_SCALE: 2757 if (xx == 0.0 || yy == 0.0) { 2758 throw new NonInvertibleTransformException( 2759 "Determinant is 0"); 2760 } 2761 return new Point3D( 2762 (1.0 / xx) * x, 2763 (1.0 / yy) * y, z); 2764 case APPLY_TRANSLATE: 2765 case APPLY_IDENTITY: 2766 return new Point3D(x, y, z); 2767 } 2768 2769 case APPLY_TRANSLATE: 2770 return new Point3D(x, y, z); 2771 case APPLY_SCALE | APPLY_TRANSLATE: 2772 case APPLY_SCALE: 2773 if (xx == 0.0 || yy == 0.0 || zz == 0.0) { 2774 throw new NonInvertibleTransformException("Determinant is 0"); 2775 } 2776 return new Point3D( 2777 (1.0 / xx) * x, 2778 (1.0 / yy) * y, 2779 (1.0 / zz) * z); 2780 case APPLY_3D_COMPLEX: 2781 return super.inverseDeltaTransform(x, y, z); 2782 } 2783 } 2784 2785 /* ************************************************************************* 2786 * * 2787 * Other API * 2788 * * 2789 **************************************************************************/ 2790 2791 @Override 2792 public String toString() { 2793 final StringBuilder sb = new StringBuilder("Transform [\n"); 2794 2795 sb.append("\t").append(xx); 2796 sb.append(", ").append(xy); 2797 sb.append(", ").append(xz); 2798 sb.append(", ").append(xt); 2799 sb.append('\n'); 2800 sb.append("\t").append(yx); 2801 sb.append(", ").append(yy); 2802 sb.append(", ").append(yz); 2803 sb.append(", ").append(yt); 2804 sb.append('\n'); 2805 sb.append("\t").append(zx); 2806 sb.append(", ").append(zy); 2807 sb.append(", ").append(zz); 2808 sb.append(", ").append(zt); 2809 2810 return sb.append("\n]").toString(); 2811 } 2812 2813 /* ************************************************************************* 2814 * * 2815 * Internal implementation stuff * 2816 * * 2817 **************************************************************************/ 2818 2819 private void updateState() { 2820 updateState2D(); 2821 2822 state3d = APPLY_NON_3D; 2823 2824 if (xz != 0.0 || 2825 yz != 0.0 || 2826 zx != 0.0 || 2827 zy != 0.0) 2828 { 2829 state3d = APPLY_3D_COMPLEX; 2830 } else { 2831 if ((state2d & APPLY_SHEAR) == 0) { 2832 if (zt != 0.0) { 2833 state3d |= APPLY_TRANSLATE; 2834 } 2835 if (zz != 1.0) { 2836 state3d |= APPLY_SCALE; 2837 } 2838 if (state3d != APPLY_NON_3D) { 2839 state3d |= (state2d & (APPLY_SCALE | APPLY_TRANSLATE)); 2840 } 2841 } else { 2842 if (zz != 1.0 || zt != 0.0) { 2843 state3d = APPLY_3D_COMPLEX; 2844 } 2845 } 2846 } 2847 } 2848 2849 private void updateState2D() { 2850 if (xy == 0.0 && yx == 0.0) { 2851 if (xx == 1.0 && yy == 1.0) { 2852 if (xt == 0.0 && yt == 0.0) { 2853 state2d = APPLY_IDENTITY; 2854 } else { 2855 state2d = APPLY_TRANSLATE; 2856 } 2857 } else { 2858 if (xt == 0.0 && yt == 0.0) { 2859 state2d = APPLY_SCALE; 2860 } else { 2861 state2d = (APPLY_SCALE | APPLY_TRANSLATE); 2862 } 2863 } 2864 } else { 2865 if (xx == 0.0 && yy == 0.0) { 2866 if (xt == 0.0 && yt == 0.0) { 2867 state2d = APPLY_SHEAR; 2868 } else { 2869 state2d = (APPLY_SHEAR | APPLY_TRANSLATE); 2870 } 2871 } else { 2872 if (xt == 0.0 && yt == 0.0) { 2873 state2d = (APPLY_SHEAR | APPLY_SCALE); 2874 } else { 2875 state2d = (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE); 2876 } 2877 } 2878 } 2879 } 2880 2881 void ensureCanTransform2DPoint() throws IllegalStateException { 2882 if (state3d != APPLY_NON_3D) { 2883 throw new IllegalStateException("Cannot transform 2D point " 2884 + "with a 3D transform"); 2885 } 2886 } 2887 2888 private static void stateError() { 2889 throw new InternalError("missing case in a switch"); 2890 } 2891 2892 2893 @Override 2894 void apply(final Affine3D trans) { 2895 trans.concatenate(xx, xy, xz, xt, 2896 yx, yy, yz, yt, 2897 zx, zy, zz, zt); 2898 } 2899 2900 @Override 2901 BaseTransform derive(final BaseTransform trans) { 2902 return trans.deriveWithConcatenation(xx, xy, xz, xt, 2903 yx, yy, yz, yt, 2904 zx, zy, zz, zt); 2905 } 2906 2907 /** 2908 * Used only by tests to check the 2d matrix state 2909 */ 2910 int getState2d() { 2911 return state2d; 2912 } 2913 2914 /** 2915 * Used only by tests to check the 3d matrix state 2916 */ 2917 int getState3d() { 2918 return state3d; 2919 } 2920 2921 } 2922 2923 }