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