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