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 29 import com.sun.javafx.geom.transform.Affine3D; 30 import com.sun.javafx.geom.transform.BaseTransform; 31 import javafx.beans.property.DoubleProperty; 32 import javafx.beans.property.SimpleDoubleProperty; 33 import javafx.geometry.Point2D; 34 import javafx.geometry.Point3D; 35 36 // PENDING_DOC_REVIEW of this whole class 37 /** 38 * <p> 39 * The {@code Affine} class represents a general affine transform. An affine 40 * transform performs a linear mapping from 2D/3D coordinates to other 2D/3D 41 * coordinates while preserving the "straightness" and "parallelness" 42 * of lines. 43 * Affine transformations can be constructed using sequence rotations, 44 * translations, scales, and shears.</p> 45 * 46 * <p> 47 * For simple transformations application developers should use the 48 * specific {@code Translate}, {@code Scale}, {@code Rotate}, or {@code Shear} 49 * transforms, which are more lightweight and thus more optimal for this simple 50 * purpose. The {@code Affine} class, on the other hand, has the advantage 51 * of being able to represent a general affine transform and perform matrix 52 * operations on it in place, so it fits better for more complex transformation 53 * usages.</p> 54 55 * <p> 56 * Such a coordinate transformation can be represented by a 3 row by 57 * 4 column matrix. This matrix transforms source coordinates {@code (x,y,z)} 58 * into destination coordinates {@code (x',y',z')} by considering 59 * them to be a column vector and multiplying the coordinate vector 60 * by the matrix according to the following process:</p> 61 * 62 * <pre> 63 * [ x'] [ mxx mxy mxz tx ] [ x ] [ mxx * x + mxy * y + mxz * z + tx ] 64 * [ y'] = [ myx myy myz ty ] [ y ] = [ myx * x + myy * y + myz * z + ty ] 65 * [ z'] [ mzx mzy mzz tz ] [ z ] [ mzx * x + mzy * y + mzz * z + tz ] 66 * [ 1 ] 67 * </pre> 68 * @since JavaFX 2.0 69 */ 70 public class Affine extends Transform { 71 72 /** 73 * Tracks atomic changes of more elements. 74 */ 75 AffineAtomicChange atomicChange = new AffineAtomicChange(); 76 77 /** 78 * This constant is used for the internal state2d variable to indicate 79 * that no calculations need to be performed and that the source 80 * coordinates only need to be copied to their destinations to 81 * complete the transformation equation of this transform. 82 * @see #state2d 83 */ 84 private static final int APPLY_IDENTITY = 0; 85 86 /** 87 * This constant is used for the internal state2d and state3d variables 88 * that the translation components of the matrix need to be added 89 * to complete the transformation equation of this transform. 90 * @see #state2d 91 * @see #state3d 92 */ 93 private static final int APPLY_TRANSLATE = 1; 94 95 /** 96 * This constant is used for the internal state2d and state3d variables 97 * to indicate that the scaling components of the matrix need 98 * to be factored in to complete the transformation equation of 99 * this transform. If the APPLY_SHEAR bit is also set then it 100 * indicates that the scaling components are 0.0. If the 101 * APPLY_SHEAR bit is not also set then it indicates that the 102 * scaling components are not 1.0. If neither the APPLY_SHEAR 103 * nor the APPLY_SCALE bits are set then the scaling components 104 * are 1.0, which means that the x and y components contribute 105 * to the transformed coordinate, but they are not multiplied by 106 * any scaling factor. 107 * @see #state2d 108 * @see #state3d 109 */ 110 private static final int APPLY_SCALE = 2; 111 112 /** 113 * This constant is used for the internal state2d variable to indicate 114 * that the shearing components of the matrix (mxy and myx) need 115 * to be factored in to complete the transformation equation of this 116 * transform. The presence of this bit in the state variable changes 117 * the interpretation of the APPLY_SCALE bit as indicated in its 118 * documentation. 119 * @see #state2d 120 */ 121 private static final int APPLY_SHEAR = 4; 122 123 /** 124 * This constant is used for the internal state3d variable to indicate 125 * that the matrix represents a 2D-only transform. 126 */ 127 private static final int APPLY_NON_3D = 0; 128 129 /** 130 * This constant is used for the internal state3d variable to indicate 131 * that the matrix is not in any of the recognized simple states 132 * and therefore needs a full usage of all elements to complete 133 * the transformation equation of this transform. 134 */ 135 private static final int APPLY_3D_COMPLEX = 4; 136 137 /** 138 * If this is a 2D transform, this field keeps track of which components 139 * of the matrix need to be applied when performing a transformation. 140 * If this is a 3D transform, its state is store in the state3d variable 141 * and value of state2d is undefined. 142 * @see #APPLY_IDENTITY 143 * @see #APPLY_TRANSLATE 144 * @see #APPLY_SCALE 145 * @see #APPLY_SHEAR 146 * @see #state3d 147 * @see #updateState() 148 */ 149 private transient int state2d; 150 151 /** 152 * This field keeps track of whether or not this transform is 3D and if so 153 * it tracks several simple states that can be treated faster. If the state 154 * is equal to APPLY_NON_3D, this is a 2D transform with its state stored 155 * in the state2d variable. If the state is equal to APPLY_3D_COMPLEX, 156 * the matrix is not in any of the simple states and needs to be fully 157 * processed. Otherwise we recognize scale (mxx, myy and mzz 158 * are not all equal to 1.0), translation (tx, ty and tz are not all 159 * equal to 0.0) and their combination. In one of the simple states 160 * all of the other elements of the matrix are equal to 0.0 (not even 161 * shear is allowed). 162 * @see #APPLY_NON_3D 163 * @see #APPLY_TRANSLATE 164 * @see #APPLY_SCALE 165 * @see #APPLY_3D_COMPLEX 166 * @see #state2d 167 * @see #updateState() 168 */ 169 private transient int state3d; 170 171 // Variables used for the elements until user requests creation 172 // of the heavy-weight properties 173 private double xx; 174 private double xy; 175 private double xz; 176 private double yx; 177 private double yy; 178 private double yz; 179 private double zx; 180 private double zy; 181 private double zz; 182 private double xt; 183 private double yt; 184 private double zt; 185 186 /** 187 * Creates a new instance of {@code Affine} containing an identity transform. 188 */ 189 public Affine() { 190 xx = yy = zz = 1.0; 191 } 192 193 /** 194 * Creates a new instance of {@code Affine} filled with the values from 195 * the specified transform. 196 * @param transform transform whose matrix is to be filled to the new 197 * instance 198 * @throws NullPointerException if the specified {@code transform} is null 199 * @since JavaFX 8.0 200 */ 201 public Affine(Transform transform) { 202 this(transform.getMxx(), transform.getMxy(), transform.getMxz(), 203 transform.getTx(), 204 transform.getMyx(), transform.getMyy(), transform.getMyz(), 205 transform.getTy(), 206 transform.getMzx(), transform.getMzy(), transform.getMzz(), 207 transform.getTz()); 208 } 209 210 /** 211 * Creates a new instance of {@code Affine} with a 2D transform specified 212 * by the element values. 213 * @param mxx the X coordinate scaling element 214 * @param mxy the XY coordinate element 215 * @param tx the X coordinate translation element 216 * @param myx the YX coordinate element 217 * @param myy the Y coordinate scaling element 218 * @param ty the Y coordinate translation element 219 * @since JavaFX 8.0 220 */ 221 public Affine(double mxx, double mxy, double tx, 222 double myx, double myy, double ty) { 223 xx = mxx; 224 xy = mxy; 225 xt = tx; 226 227 yx = myx; 228 yy = myy; 229 yt = ty; 230 231 zz = 1.0; 232 233 updateState2D(); 234 } 235 236 /** 237 * Creates a new instance of {@code Affine} with a transform specified 238 * by the element values. 239 * @param mxx the X coordinate scaling element 240 * @param mxy the XY coordinate element 241 * @param mxz the XZ coordinate element 242 * @param tx the X coordinate translation element 243 * @param myx the YX coordinate element 244 * @param myy the Y coordinate scaling element 245 * @param myz the YZ coordinate element 246 * @param ty the Y coordinate translation element 247 * @param mzx the ZX coordinate element 248 * @param mzy the ZY coordinate element 249 * @param mzz the Z coordinate scaling element 250 * @param tz the Z coordinate translation element 251 * @since JavaFX 8.0 252 */ 253 public Affine(double mxx, double mxy, double mxz, double tx, 254 double myx, double myy, double myz, double ty, 255 double mzx, double mzy, double mzz, double tz) { 256 xx = mxx; 257 xy = mxy; 258 xz = mxz; 259 xt = tx; 260 261 yx = myx; 262 yy = myy; 263 yz = myz; 264 yt = ty; 265 266 zx = mzx; 267 zy = mzy; 268 zz = mzz; 269 zt = tz; 270 271 updateState(); 272 } 273 274 /** 275 * Creates a new instance of {@code Affine} with a transformation matrix 276 * specified by an array. 277 * @param matrix array containing the flattened transformation matrix 278 * @param type type of matrix contained in the array 279 * @param offset offset of the first element in the array 280 * @throws IndexOutOfBoundsException if the array is too short for 281 * the specified {@code type} and {@code offset} 282 * @throws IllegalArgumentException if the specified matrix is not affine 283 * (the last line of a 2D 3x3 matrix is not {@code [0, 0, 1]} or 284 * the last line of a 3D 4x4 matrix is not {@code [0, 0, 0, 1]}. 285 * @throws NullPointerException if the specified {@code matrix} 286 * or {@code type} is null 287 * @since JavaFX 8.0 288 */ 289 public Affine(double[] matrix, MatrixType type, int offset) { 290 if (matrix.length < offset + type.elements()) { 291 throw new IndexOutOfBoundsException("The array is too short."); 292 } 293 294 switch(type) { 295 default: 296 stateError(); 297 // cannot reach 298 case MT_2D_3x3: 299 if (matrix[offset + 6] != 0.0 || 300 matrix[offset + 7] != 0.0 || 301 matrix[offset + 8] != 1.0) { 302 throw new IllegalArgumentException("The matrix is " 303 + "not affine"); 304 } 305 // fall-through 306 case MT_2D_2x3: 307 xx = matrix[offset++]; 308 xy = matrix[offset++]; 309 xt = matrix[offset++]; 310 yx = matrix[offset++]; 311 yy = matrix[offset++]; 312 yt = matrix[offset]; 313 zz = 1.0; 314 updateState2D(); 315 return; 316 case MT_3D_4x4: 317 if (matrix[offset + 12] != 0.0 || 318 matrix[offset + 13] != 0.0 || 319 matrix[offset + 14] != 0.0 || 320 matrix[offset + 15] != 1.0) { 321 throw new IllegalArgumentException("The matrix is " 322 + "not affine"); 323 } 324 // fall-through 325 case MT_3D_3x4: 326 xx = matrix[offset++]; 327 xy = matrix[offset++]; 328 xz = matrix[offset++]; 329 xt = matrix[offset++]; 330 yx = matrix[offset++]; 331 yy = matrix[offset++]; 332 yz = matrix[offset++]; 333 yt = matrix[offset++]; 334 zx = matrix[offset++]; 335 zy = matrix[offset++]; 336 zz = matrix[offset++]; 337 zt = matrix[offset]; 338 updateState(); 339 return; 340 } 341 } 342 343 /** 344 * Defines the X coordinate scaling element of the 3x4 matrix. 345 */ 346 private AffineElementProperty mxx; 347 348 349 public final void setMxx(double value) { 350 if (mxx == null) { 351 if (xx != value) { 352 xx = value; 353 postProcessChange(); 354 } 355 } else { 356 mxxProperty().set(value); 357 } 358 } 359 360 @Override 361 public final double getMxx() { 362 return mxx == null ? xx : mxx.get(); 363 } 364 365 public final DoubleProperty mxxProperty() { 366 if (mxx == null) { 367 mxx = new AffineElementProperty(xx) { 368 @Override 369 public Object getBean() { 370 return Affine.this; 371 } 372 373 @Override 374 public String getName() { 375 return "mxx"; 376 } 377 }; 378 } 379 return mxx; 380 } 381 382 /** 383 * Defines the XY coordinate element of the 3x4 matrix. 384 */ 385 private AffineElementProperty mxy; 386 387 388 public final void setMxy(double value) { 389 if (mxy == null) { 390 if (xy != value) { 391 xy = value; 392 postProcessChange(); 393 } 394 } else { 395 mxyProperty().set(value); 396 } 397 } 398 399 @Override 400 public final double getMxy() { 401 return mxy == null ? xy : mxy.get(); 402 } 403 404 public final DoubleProperty mxyProperty() { 405 if (mxy == null) { 406 mxy = new AffineElementProperty(xy) { 407 @Override 408 public Object getBean() { 409 return Affine.this; 410 } 411 412 @Override 413 public String getName() { 414 return "mxy"; 415 } 416 }; 417 } 418 return mxy; 419 } 420 421 /** 422 * Defines the XZ coordinate element of the 3x4 matrix. 423 */ 424 private AffineElementProperty mxz; 425 426 427 public final void setMxz(double value) { 428 if (mxz == null) { 429 if (xz != value) { 430 xz = value; 431 postProcessChange(); 432 } 433 } else { 434 mxzProperty().set(value); 435 } 436 } 437 438 @Override 439 public final double getMxz() { 440 return mxz == null ? xz : mxz.get(); 441 } 442 443 public final DoubleProperty mxzProperty() { 444 if (mxz == null) { 445 mxz = new AffineElementProperty(xz) { 446 @Override 447 public Object getBean() { 448 return Affine.this; 449 } 450 451 @Override 452 public String getName() { 453 return "mxz"; 454 } 455 }; 456 } 457 return mxz; 458 } 459 460 /** 461 * Defines the X coordinate translation element of the 3x4 matrix. 462 */ 463 private AffineElementProperty tx; 464 465 466 public final void setTx(double value) { 467 if (tx == null) { 468 if (xt != value) { 469 xt = value; 470 postProcessChange(); 471 } 472 } else { 473 txProperty().set(value); 474 } 475 } 476 477 @Override 478 public final double getTx() { 479 return tx == null ? xt : tx.get(); 480 } 481 482 public final DoubleProperty txProperty() { 483 if (tx == null) { 484 tx = new AffineElementProperty(xt) { 485 @Override 486 public Object getBean() { 487 return Affine.this; 488 } 489 490 @Override 491 public String getName() { 492 return "tx"; 493 } 494 }; 495 } 496 return tx; 497 } 498 499 /** 500 * Defines the YX coordinate element of the 3x4 matrix. 501 */ 502 private AffineElementProperty myx; 503 504 505 public final void setMyx(double value) { 506 if (myx == null) { 507 if (yx != value) { 508 yx = value; 509 postProcessChange(); 510 } 511 } else { 512 myxProperty().set(value); 513 } 514 } 515 516 @Override 517 public final double getMyx() { 518 return myx == null ? yx : myx.get(); 519 } 520 521 public final DoubleProperty myxProperty() { 522 if (myx == null) { 523 myx = new AffineElementProperty(yx) { 524 @Override 525 public Object getBean() { 526 return Affine.this; 527 } 528 529 @Override 530 public String getName() { 531 return "myx"; 532 } 533 }; 534 } 535 return myx; 536 } 537 538 /** 539 * Defines the Y coordinate scaling element of the 3x4 matrix. 540 */ 541 private AffineElementProperty myy; 542 543 544 public final void setMyy(double value) { 545 if (myy == null) { 546 if (yy != value) { 547 yy = value; 548 postProcessChange(); 549 } 550 } else{ 551 myyProperty().set(value); 552 } 553 } 554 555 @Override 556 public final double getMyy() { 557 return myy == null ? yy : myy.get(); 558 } 559 560 public final DoubleProperty myyProperty() { 561 if (myy == null) { 562 myy = new AffineElementProperty(yy) { 563 @Override 564 public Object getBean() { 565 return Affine.this; 566 } 567 568 @Override 569 public String getName() { 570 return "myy"; 571 } 572 }; 573 } 574 return myy; 575 } 576 577 /** 578 * Defines the YZ coordinate element of the 3x4 matrix. 579 */ 580 private AffineElementProperty myz; 581 582 583 public final void setMyz(double value) { 584 if (myz == null) { 585 if (yz != value) { 586 yz = value; 587 postProcessChange(); 588 } 589 } else { 590 myzProperty().set(value); 591 } 592 } 593 594 @Override 595 public final double getMyz() { 596 return myz == null ? yz : myz.get(); 597 } 598 599 public final DoubleProperty myzProperty() { 600 if (myz == null) { 601 myz = new AffineElementProperty(yz) { 602 @Override 603 public Object getBean() { 604 return Affine.this; 605 } 606 607 @Override 608 public String getName() { 609 return "myz"; 610 } 611 }; 612 } 613 return myz; 614 } 615 616 /** 617 * Defines the Y coordinate translation element of the 3x4 matrix. 618 */ 619 private AffineElementProperty ty; 620 621 622 public final void setTy(double value) { 623 if (ty == null) { 624 if (yt != value) { 625 yt = value; 626 postProcessChange(); 627 } 628 } else { 629 tyProperty().set(value); 630 } 631 } 632 633 @Override 634 public final double getTy() { 635 return ty == null ? yt : ty.get(); 636 } 637 638 public final DoubleProperty tyProperty() { 639 if (ty == null) { 640 ty = new AffineElementProperty(yt) { 641 @Override 642 public Object getBean() { 643 return Affine.this; 644 } 645 646 @Override 647 public String getName() { 648 return "ty"; 649 } 650 }; 651 } 652 return ty; 653 } 654 655 /** 656 * Defines the ZX coordinate element of the 3x4 matrix. 657 */ 658 private AffineElementProperty mzx; 659 660 661 public final void setMzx(double value) { 662 if (mzx == null) { 663 if (zx != value) { 664 zx = value; 665 postProcessChange(); 666 } 667 } else { 668 mzxProperty().set(value); 669 } 670 } 671 672 @Override 673 public final double getMzx() { 674 return mzx == null ? zx : mzx.get(); 675 } 676 677 public final DoubleProperty mzxProperty() { 678 if (mzx == null) { 679 mzx = new AffineElementProperty(zx) { 680 @Override 681 public Object getBean() { 682 return Affine.this; 683 } 684 685 @Override 686 public String getName() { 687 return "mzx"; 688 } 689 }; 690 } 691 return mzx; 692 } 693 694 /** 695 * Defines the ZY coordinate element of the 3x4 matrix. 696 */ 697 private AffineElementProperty mzy; 698 699 700 public final void setMzy(double value) { 701 if (mzy == null) { 702 if (zy != value) { 703 zy = value; 704 postProcessChange(); 705 } 706 } else { 707 mzyProperty().set(value); 708 } 709 } 710 711 @Override 712 public final double getMzy() { 713 return mzy == null ? zy : mzy.get(); 714 } 715 716 public final DoubleProperty mzyProperty() { 717 if (mzy == null) { 718 mzy = new AffineElementProperty(zy) { 719 @Override 720 public Object getBean() { 721 return Affine.this; 722 } 723 724 @Override 725 public String getName() { 726 return "mzy"; 727 } 728 }; 729 } 730 return mzy; 731 } 732 733 /** 734 * Defines the Z coordinate scaling element of the 3x4 matrix. 735 */ 736 private AffineElementProperty mzz; 737 738 739 public final void setMzz(double value) { 740 if (mzz == null) { 741 if (zz != value) { 742 zz = value; 743 postProcessChange(); 744 } 745 } else { 746 mzzProperty().set(value); 747 } 748 } 749 750 @Override 751 public final double getMzz() { 752 return mzz == null ? zz : mzz.get(); 753 } 754 755 public final DoubleProperty mzzProperty() { 756 if (mzz == null) { 757 mzz = new AffineElementProperty(zz) { 758 @Override 759 public Object getBean() { 760 return Affine.this; 761 } 762 763 @Override 764 public String getName() { 765 return "mzz"; 766 } 767 }; 768 } 769 return mzz; 770 } 771 772 /** 773 * Defines the Z coordinate translation element of the 3x4 matrix. 774 */ 775 private AffineElementProperty tz; 776 777 778 public final void setTz(double value) { 779 if (tz == null) { 780 if (zt != value) { 781 zt = value; 782 postProcessChange(); 783 } 784 } else { 785 tzProperty().set(value); 786 } 787 } 788 789 @Override 790 public final double getTz() { 791 return tz == null ? zt : tz.get(); 792 } 793 794 public final DoubleProperty tzProperty() { 795 if (tz == null) { 796 tz = new AffineElementProperty(zt) { 797 @Override 798 public Object getBean() { 799 return Affine.this; 800 } 801 802 @Override 803 public String getName() { 804 return "tz"; 805 } 806 }; 807 } 808 return tz; 809 } 810 811 /** 812 * Sets the specified element of the transformation matrix. 813 * @param type type of matrix to work with 814 * @param row zero-based row number 815 * @param column zero-based column number 816 * @param value new value of the specified transformation matrix element 817 * @throws IndexOutOfBoundsException if the indices are not within 818 * the specified matrix type 819 * @throws IllegalArgumentException if setting the value would break 820 * transform's affinity (for convenience the method allows to set 821 * the elements of the last line of a 2D 3x3 matrix to 822 * {@code [0, 0, 1]} and the elements of the last line 823 * of a 3D 4x4 matrix to {@code [0, 0, 0, 1]}). 824 * @throws NullPointerException if the specified {@code type} is null 825 * @since JavaFX 8.0 826 */ 827 public void setElement(MatrixType type, int row, int column, double value) { 828 if (row < 0 || row >= type.rows() || 829 column < 0 || column >= type.columns()) { 830 throw new IndexOutOfBoundsException("Index outside of affine " 831 + "matrix " + type + ": [" + row + ", " + column + "]"); 832 } 833 switch(type) { 834 default: 835 stateError(); 836 // cannot reach 837 case MT_2D_2x3: 838 // fall-through 839 case MT_2D_3x3: 840 if (!isType2D()) { 841 throw new IllegalArgumentException("Cannot access 2D matrix " 842 + "of a 3D transform"); 843 } 844 switch(row) { 845 case 0: 846 switch(column) { 847 case 0: setMxx(value); return; 848 case 1: setMxy(value); return; 849 case 2: setTx(value); return; 850 } 851 case 1: 852 switch(column) { 853 case 0: setMyx(value); return; 854 case 1: setMyy(value); return; 855 case 2: setTy(value); return; 856 } 857 case 2: 858 switch(column) { 859 case 0: if (value == 0.0) return; else break; 860 case 1: if (value == 0.0) return; else break; 861 case 2: if (value == 1.0) return; else break; 862 } 863 } 864 break; 865 case MT_3D_3x4: 866 // fall-through 867 case MT_3D_4x4: 868 switch(row) { 869 case 0: 870 switch(column) { 871 case 0: setMxx(value); return; 872 case 1: setMxy(value); return; 873 case 2: setMxz(value); return; 874 case 3: setTx(value); return; 875 } 876 case 1: 877 switch(column) { 878 case 0: setMyx(value); return; 879 case 1: setMyy(value); return; 880 case 2: setMyz(value); return; 881 case 3: setTy(value); return; 882 } 883 case 2: 884 switch(column) { 885 case 0: setMzx(value); return; 886 case 1: setMzy(value); return; 887 case 2: setMzz(value); return; 888 case 3: setTz(value); return; 889 } 890 case 3: 891 switch(column) { 892 case 0: if (value == 0.0) return; else break; 893 case 1: if (value == 0.0) return; else break; 894 case 2: if (value == 0.0) return; else break; 895 case 3: if (value == 1.0) return; else break; 896 } 897 } 898 break; 899 } 900 // reaches here when last line is set to something else than 0 .. 0 1 901 throw new IllegalArgumentException("Cannot set affine matrix " + type + 902 " element " + "[" + row + ", " + column + "] to " + value); 903 } 904 905 /** 906 * Affine element property which handles the atomic changes of more 907 * properties. 908 */ 909 private class AffineElementProperty extends SimpleDoubleProperty { 910 911 private boolean needsValueChangedEvent = false; 912 private double oldValue; 913 914 public AffineElementProperty(double initialValue) { 915 super(initialValue); 916 } 917 918 @Override 919 public void invalidated() { 920 // if an atomic change runs, postpone the change notifications 921 if (!atomicChange.runs()) { 922 updateState(); 923 transformChanged(); 924 } 925 } 926 927 @Override 928 protected void fireValueChangedEvent() { 929 // if an atomic change runs, postpone the change notifications 930 if (!atomicChange.runs()) { 931 super.fireValueChangedEvent(); 932 } else { 933 needsValueChangedEvent = true; 934 } 935 } 936 937 /** 938 * Called before an atomic change 939 */ 940 private void preProcessAtomicChange() { 941 // remember the value before an atomic change 942 oldValue = get(); 943 } 944 945 /** 946 * Called after an atomic change 947 */ 948 private void postProcessAtomicChange() { 949 // if there was a change notification during the atomic change, 950 // fire it now 951 if (needsValueChangedEvent) { 952 needsValueChangedEvent = false; 953 // the value might have change back an forth 954 // (happens quite commonly for transforms with pivot) 955 if (oldValue != get()) { 956 super.fireValueChangedEvent(); 957 } 958 } 959 } 960 } 961 962 /** 963 * Called by each element property after value change to 964 * update the state variables and call transform change notifications. 965 * If an atomic change runs, this is a NOP and the work is done 966 * in the end of the entire atomic change. 967 */ 968 private void postProcessChange() { 969 if (!atomicChange.runs()) { 970 updateState(); 971 transformChanged(); 972 } 973 } 974 975 /* ************************************************************************* 976 * * 977 * State getters * 978 * * 979 **************************************************************************/ 980 981 @Override 982 boolean computeIs2D() { 983 return (state3d == APPLY_NON_3D); 984 } 985 986 @Override 987 boolean computeIsIdentity() { 988 return state3d == APPLY_NON_3D && state2d == APPLY_IDENTITY; 989 } 990 991 @Override 992 public double determinant() { 993 if (state3d == APPLY_NON_3D) { 994 return getDeterminant2D(); 995 } else { 996 return getDeterminant3D(); 997 } 998 } 999 1000 /** 1001 * 2D implementation of {@code determinant()}. 1002 * The behavior is undefined if this is a 3D transform. 1003 * @return determinant 1004 */ 1005 private double getDeterminant2D() { 1006 // assert(state3d == APPLY_NON_3D) 1007 switch (state2d) { 1008 default: 1009 stateError(); 1010 // cannot reach 1011 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 1012 case APPLY_SHEAR | APPLY_SCALE: 1013 return getMxx() * getMyy() - getMxy() * getMyx(); 1014 case APPLY_SHEAR | APPLY_TRANSLATE: 1015 case APPLY_SHEAR: 1016 return -(getMxy() * getMyx()); 1017 case APPLY_SCALE | APPLY_TRANSLATE: 1018 case APPLY_SCALE: 1019 return getMxx() * getMyy(); 1020 case APPLY_TRANSLATE: 1021 case APPLY_IDENTITY: 1022 return 1.0; 1023 } 1024 } 1025 1026 /** 1027 * 3D implementation of {@code determinant()}. 1028 * The behavior is undefined if this is a 2D transform. 1029 * @return determinant 1030 */ 1031 private double getDeterminant3D() { 1032 // assert(state3d != APPLY_NON_3D) 1033 switch(state3d) { 1034 default: 1035 stateError(); 1036 // cannot reach 1037 case APPLY_TRANSLATE: 1038 return 1.0; 1039 case APPLY_SCALE: 1040 case APPLY_SCALE | APPLY_TRANSLATE: 1041 return getMxx() * getMyy() * getMzz(); 1042 case APPLY_3D_COMPLEX: 1043 final double myx = getMyx(); 1044 final double myy = getMyy(); 1045 final double myz = getMyz(); 1046 final double mzx = getMzx(); 1047 final double mzy = getMzy(); 1048 final double mzz = getMzz(); 1049 1050 return (getMxx() * (myy * mzz - mzy * myz) + 1051 getMxy() * (myz * mzx - mzz * myx) + 1052 getMxz() * (myx * mzy - mzx * myy)); 1053 } 1054 } 1055 1056 /* ************************************************************************* 1057 * * 1058 * Transform creators * 1059 * * 1060 **************************************************************************/ 1061 1062 @Override 1063 public Transform createConcatenation(Transform transform) { 1064 Affine a = clone(); 1065 a.append(transform); 1066 return a; 1067 } 1068 1069 @Override 1070 public Affine createInverse() throws NonInvertibleTransformException { 1071 Affine t = clone(); 1072 t.invert(); 1073 return t; 1074 } 1075 1076 @Override 1077 public Affine clone() { 1078 return new Affine(this); 1079 } 1080 1081 /* ************************************************************************* 1082 * * 1083 * Matrix setters * 1084 * * 1085 **************************************************************************/ 1086 1087 /** 1088 * Sets the values of this instance to the values provided by the specified 1089 * transform. 1090 * @param transform transform whose matrix is to be filled to this instance 1091 * @throws NullPointerException if the specified {@code transform} is null 1092 * @since JavaFX 8.0 1093 */ 1094 public void setToTransform(Transform transform) { 1095 setToTransform( 1096 transform.getMxx(), transform.getMxy(), 1097 transform.getMxz(), transform.getTx(), 1098 transform.getMyx(), transform.getMyy(), 1099 transform.getMyz(), transform.getTy(), 1100 transform.getMzx(), transform.getMzy(), 1101 transform.getMzz(), transform.getTz()); 1102 } 1103 1104 /** 1105 * Sets the values of this instance to the 2D transform specified 1106 * by the element values. 1107 * @param mxx the X coordinate scaling element 1108 * @param mxy the XY coordinate element 1109 * @param tx the X coordinate translation element 1110 * @param myx the YX coordinate element 1111 * @param myy the Y coordinate scaling element 1112 * @param ty the Y coordinate translation element 1113 * @since JavaFX 8.0 1114 */ 1115 public void setToTransform(double mxx, double mxy, double tx, 1116 double myx, double myy, double ty) { 1117 setToTransform(mxx, mxy, 0.0, tx, 1118 myx, myy, 0.0, ty, 1119 0.0, 0.0, 1.0, 0.0); 1120 } 1121 1122 /** 1123 * Sets the values of this instance to the transform specified 1124 * by the element values. 1125 * @param mxx the X coordinate scaling element 1126 * @param mxy the XY coordinate element 1127 * @param mxz the XZ coordinate element 1128 * @param tx the X coordinate translation element 1129 * @param myx the YX coordinate element 1130 * @param myy the Y coordinate scaling element 1131 * @param myz the YZ coordinate element 1132 * @param ty the Y coordinate translation element 1133 * @param mzx the ZX coordinate element 1134 * @param mzy the ZY coordinate element 1135 * @param mzz the Z coordinate scaling element 1136 * @param tz the Z coordinate translation element 1137 * @since JavaFX 8.0 1138 */ 1139 public void setToTransform(double mxx, double mxy, double mxz, double tx, 1140 double myx, double myy, double myz, double ty, 1141 double mzx, double mzy, double mzz, double tz) 1142 { 1143 atomicChange.start(); 1144 1145 setMxx(mxx); 1146 setMxy(mxy); 1147 setMxz(mxz); 1148 setTx(tx); 1149 1150 setMyx(myx); 1151 setMyy(myy); 1152 setMyz(myz); 1153 setTy(ty); 1154 1155 setMzx(mzx); 1156 setMzy(mzy); 1157 setMzz(mzz); 1158 setTz(tz); 1159 1160 updateState(); 1161 atomicChange.end(); 1162 } 1163 1164 /** 1165 * Sets the values of this instance to the transformation matrix 1166 * specified by an array. 1167 * @param matrix array containing the flattened transformation matrix 1168 * @param type type of matrix contained in the array 1169 * @param offset offset of the first element in the array 1170 * @throws IndexOutOfBoundsException if the array is too short for 1171 * the specified {@code type} and {@code offset} 1172 * @throws IllegalArgumentException if the specified matrix is not affine 1173 * (the last line of a 2D 3x3 matrix is not {@code [0, 0, 1]} or 1174 * the last line of a 3D 4x4 matrix is not {@code [0, 0, 0, 1]}. 1175 * @throws NullPointerException if the specified {@code matrix} 1176 * or {@code type} is null 1177 * @since JavaFX 8.0 1178 */ 1179 public void setToTransform(double[] matrix, MatrixType type, int offset) { 1180 if (matrix.length < offset + type.elements()) { 1181 throw new IndexOutOfBoundsException("The array is too short."); 1182 } 1183 1184 switch(type) { 1185 default: 1186 stateError(); 1187 // cannot reach 1188 case MT_2D_3x3: 1189 if (matrix[offset + 6] != 0.0 || 1190 matrix[offset + 7] != 0.0 || 1191 matrix[offset + 8] != 1.0) { 1192 throw new IllegalArgumentException("The matrix is " 1193 + "not affine"); 1194 } 1195 // fall-through 1196 case MT_2D_2x3: 1197 setToTransform(matrix[offset++], matrix[offset++], 1198 matrix[offset++], matrix[offset++], 1199 matrix[offset++], matrix[offset++]); 1200 return; 1201 case MT_3D_4x4: 1202 if (matrix[offset + 12] != 0.0 || 1203 matrix[offset + 13] != 0.0 || 1204 matrix[offset + 14] != 0.0 || 1205 matrix[offset + 15] != 1.0) { 1206 throw new IllegalArgumentException("The matrix is " 1207 + "not affine"); 1208 } 1209 // fall-through 1210 case MT_3D_3x4: 1211 setToTransform(matrix[offset++], matrix[offset++], 1212 matrix[offset++], matrix[offset++], matrix[offset++], 1213 matrix[offset++], matrix[offset++], matrix[offset++], 1214 matrix[offset++], matrix[offset++], matrix[offset++], 1215 matrix[offset++]); 1216 return; 1217 } 1218 } 1219 1220 /** 1221 * Resets this transform to the identity transform. 1222 * @since JavaFX 8.0 1223 */ 1224 public void setToIdentity() { 1225 atomicChange.start(); 1226 1227 if (state3d != APPLY_NON_3D) { 1228 setMxx(1.0); setMxy(0.0); setMxz(0.0); setTx(0.0); 1229 setMyx(0.0); setMyy(1.0); setMyz(0.0); setTy(0.0); 1230 setMzx(0.0); setMzy(0.0); setMzz(1.0); setTz(0.0); 1231 state3d = APPLY_NON_3D; 1232 state2d = APPLY_IDENTITY; 1233 } else if (state2d != APPLY_IDENTITY) { 1234 setMxx(1.0); setMxy(0.0); setTx(0.0); 1235 setMyx(0.0); setMyy(1.0); setTy(0.0); 1236 state2d = APPLY_IDENTITY; 1237 } 1238 1239 atomicChange.end(); 1240 } 1241 1242 /* ************************************************************************* 1243 * * 1244 * Matrix operations * 1245 * * 1246 **************************************************************************/ 1247 1248 1249 /* ************************************************* 1250 * Inversion * 1251 **************************************************/ 1252 1253 /** 1254 * Inverts this transform in place. 1255 * @throws NonInvertibleTransformException if this transform 1256 * cannot be inverted 1257 * @since JavaFX 8.0 1258 */ 1259 public void invert() throws NonInvertibleTransformException { 1260 atomicChange.start(); 1261 1262 if (state3d == APPLY_NON_3D) { 1263 invert2D(); 1264 updateState2D(); 1265 } else { 1266 invert3D(); 1267 updateState(); 1268 } 1269 1270 atomicChange.end(); 1271 } 1272 1273 /** 1274 * 2D implementation of {@code invert()}. 1275 * The behavior is undefined for a 3D transform. 1276 */ 1277 private void invert2D() throws NonInvertibleTransformException { 1278 double Mxx, Mxy, Mxt; 1279 double Myx, Myy, Myt; 1280 double det; 1281 // assert(state3d == APPLY_NON_3D) 1282 1283 switch (state2d) { 1284 default: 1285 stateError(); 1286 // cannot reach 1287 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 1288 Mxx = getMxx(); Mxy = getMxy(); Mxt = getTx(); 1289 Myx = getMyx(); Myy = getMyy(); Myt = getTy(); 1290 det = getDeterminant2D(); 1291 if (det == 0.0) { 1292 atomicChange.cancel(); 1293 throw new NonInvertibleTransformException("Determinant is 0"); 1294 } 1295 setMxx(Myy / det); 1296 setMyx(-Myx / det); 1297 setMxy(-Mxy / det); 1298 setMyy(Mxx / det); 1299 setTx((Mxy * Myt - Myy * Mxt) / det); 1300 setTy((Myx * Mxt - Mxx * Myt) / det); 1301 return; 1302 case APPLY_SHEAR | APPLY_SCALE: 1303 Mxx = getMxx(); Mxy = getMxy(); 1304 Myx = getMyx(); Myy = getMyy(); 1305 det = getDeterminant2D(); 1306 if (det == 0.0) { 1307 atomicChange.cancel(); 1308 throw new NonInvertibleTransformException("Determinant is 0"); 1309 } 1310 setMxx(Myy / det); 1311 setMyx(-Myx / det); 1312 setMxy(-Mxy / det); 1313 setMyy(Mxx / det); 1314 return; 1315 case APPLY_SHEAR | APPLY_TRANSLATE: 1316 Mxy = getMxy(); Mxt = getTx(); 1317 Myx = getMyx(); Myt = getTy(); 1318 if (Mxy == 0.0 || Myx == 0.0) { 1319 atomicChange.cancel(); 1320 throw new NonInvertibleTransformException("Determinant is 0"); 1321 } 1322 setMyx(1.0 / Mxy); 1323 setMxy(1.0 / Myx); 1324 setTx(-Myt / Myx); 1325 setTy(-Mxt / Mxy); 1326 return; 1327 case APPLY_SHEAR: 1328 Mxy = getMxy(); 1329 Myx = getMyx(); 1330 if (Mxy == 0.0 || Myx == 0.0) { 1331 atomicChange.cancel(); 1332 throw new NonInvertibleTransformException("Determinant is 0"); 1333 } 1334 setMyx(1.0 / Mxy); 1335 setMxy(1.0 / Myx); 1336 return; 1337 case APPLY_SCALE | APPLY_TRANSLATE: 1338 Mxx = getMxx(); Mxt = getTx(); 1339 Myy = getMyy(); Myt = getTy(); 1340 if (Mxx == 0.0 || Myy == 0.0) { 1341 atomicChange.cancel(); 1342 throw new NonInvertibleTransformException("Determinant is 0"); 1343 } 1344 setMxx(1.0 / Mxx); 1345 setMyy(1.0 / Myy); 1346 setTx(-Mxt / Mxx); 1347 setTy(-Myt / Myy); 1348 return; 1349 case APPLY_SCALE: 1350 Mxx = getMxx(); 1351 Myy = getMyy(); 1352 if (Mxx == 0.0 || Myy == 0.0) { 1353 atomicChange.cancel(); 1354 throw new NonInvertibleTransformException("Determinant is 0"); 1355 } 1356 setMxx(1.0 / Mxx); 1357 setMyy(1.0 / Myy); 1358 return; 1359 case APPLY_TRANSLATE: 1360 setTx(-getTx()); 1361 setTy(-getTy()); 1362 return; 1363 case APPLY_IDENTITY: 1364 return; 1365 } 1366 } 1367 1368 /** 1369 * 3D implementation of {@code invert()}. 1370 * The behavior is undefined if this is a 2D transform. 1371 */ 1372 private void invert3D() throws NonInvertibleTransformException { 1373 1374 switch(state3d) { 1375 default: 1376 stateError(); 1377 // cannot reach 1378 case APPLY_TRANSLATE: 1379 setTx(-getTx()); 1380 setTy(-getTy()); 1381 setTz(-getTz()); 1382 return; 1383 case APPLY_SCALE: 1384 final double mxx_s = getMxx(); 1385 final double myy_s = getMyy(); 1386 final double mzz_s = getMzz(); 1387 if (mxx_s == 0.0 || myy_s == 0.0 || mzz_s == 0.0) { 1388 atomicChange.cancel(); 1389 throw new NonInvertibleTransformException("Determinant is 0"); 1390 } 1391 setMxx(1.0 / mxx_s); 1392 setMyy(1.0 / myy_s); 1393 setMzz(1.0 / mzz_s); 1394 return; 1395 case APPLY_SCALE | APPLY_TRANSLATE: 1396 final double mxx_st = getMxx(); 1397 final double tx_st = getTx(); 1398 final double myy_st = getMyy(); 1399 final double ty_st = getTy(); 1400 final double mzz_st = getMzz(); 1401 final double tz_st = getTz(); 1402 if (mxx_st == 0.0 || myy_st == 0.0 || mzz_st == 0.0) { 1403 atomicChange.cancel(); 1404 throw new NonInvertibleTransformException("Determinant is 0"); 1405 } 1406 setMxx(1.0 / mxx_st); 1407 setMyy(1.0 / myy_st); 1408 setMzz(1.0 / mzz_st); 1409 setTx(-tx_st / mxx_st); 1410 setTy(-ty_st / myy_st); 1411 setTz(-tz_st / mzz_st); 1412 return; 1413 case APPLY_3D_COMPLEX: 1414 1415 // InvM = Transpose(Cofactor(M)) / det(M) 1416 // Cofactor(M) = matrix of cofactors(0..3,0..3) 1417 // cofactor(r,c) = (-1 if r+c is odd) * minor(r,c) 1418 // minor(r,c) = det(M with row r and col c removed) 1419 // For an Affine3D matrix, minor(r, 3) is {0, 0, 0, det} 1420 // which generates {0, 0, 0, 1} and so can be ignored. 1421 1422 final double mxx = getMxx(); 1423 final double mxy = getMxy(); 1424 final double mxz = getMxz(); 1425 final double tx = getTx(); 1426 final double myx = getMyx(); 1427 final double myy = getMyy(); 1428 final double myz = getMyz(); 1429 final double ty = getTy(); 1430 final double mzy = getMzy(); 1431 final double mzx = getMzx(); 1432 final double mzz = getMzz(); 1433 final double tz = getTz(); 1434 1435 final double det = 1436 mxx * (myy * mzz - mzy * myz) + 1437 mxy * (myz * mzx - mzz * myx) + 1438 mxz * (myx * mzy - mzx * myy); 1439 1440 if (det == 0.0) { 1441 atomicChange.cancel(); 1442 throw new NonInvertibleTransformException("Determinant is 0"); 1443 } 1444 1445 final double cxx = myy * mzz - myz * mzy; 1446 final double cyx = - myx * mzz + myz * mzx; 1447 final double czx = myx * mzy - myy * mzx; 1448 final double cxt = - mxy * (myz * tz - mzz * ty) 1449 - mxz * (ty * mzy - tz * myy) 1450 - tx * (myy * mzz - mzy * myz); 1451 final double cxy = - mxy * mzz + mxz * mzy; 1452 final double cyy = mxx * mzz - mxz * mzx; 1453 final double czy = - mxx * mzy + mxy * mzx; 1454 final double cyt = mxx * (myz * tz - mzz * ty) 1455 + mxz * (ty * mzx - tz * myx) 1456 + tx * (myx * mzz - mzx * myz); 1457 final double cxz = mxy * myz - mxz * myy; 1458 final double cyz = - mxx * myz + mxz * myx; 1459 final double czz = mxx * myy - mxy * myx; 1460 final double czt = - mxx * (myy * tz - mzy * ty) 1461 - mxy * (ty * mzx - tz * myx) 1462 - tx * (myx * mzy - mzx * myy); 1463 1464 setMxx(cxx / det); 1465 setMxy(cxy / det); 1466 setMxz(cxz / det); 1467 setTx(cxt / det); 1468 setMyx(cyx / det); 1469 setMyy(cyy / det); 1470 setMyz(cyz / det); 1471 setTy(cyt / det); 1472 setMzx(czx / det); 1473 setMzy(czy / det); 1474 setMzz(czz / det); 1475 setTz(czt / det); 1476 return; 1477 } 1478 } 1479 1480 /* ************************************************* 1481 * General concatenations * 1482 **************************************************/ 1483 1484 /** 1485 * <p> 1486 * Appends the specified transform to this instance. 1487 * The operation modifies this transform in a way that applying it to a node 1488 * has the same effect as adding the two transforms to its 1489 * {@code getTransforms()} list, {@code this} transform first and the specified 1490 * {@code transform} second. 1491 * </p><p> 1492 * From the matrix point of view, the transformation matrix of this 1493 * transform is multiplied on the right by the transformation matrix of 1494 * the specified transform. 1495 * </p> 1496 * 1497 * @param transform transform to be appended to this instance 1498 * @throws NullPointerException if the specified {@code transform} is null 1499 * @since JavaFX 8.0 1500 */ 1501 public void append(Transform transform) { 1502 transform.appendTo(this); 1503 } 1504 1505 /** 1506 * <p> 1507 * Appends the 2D transform specified by the element values to this instance. 1508 * The operation modifies this transform in a way that applying it to a node 1509 * has the same effect as adding the two transforms to its 1510 * {@code getTransforms()} list, {@code this} transform first and the specified 1511 * {@code transform} second. 1512 * </p><p> 1513 * From the matrix point of view, the transformation matrix of this 1514 * transform is multiplied on the right by the transformation matrix of 1515 * the specified transform. 1516 * </p> 1517 * @param mxx the X coordinate scaling element of the transform to be 1518 * appended 1519 * @param mxy the XY coordinate element of the transform to be appended 1520 * @param tx the X coordinate translation element of the transform to be 1521 * appended 1522 * @param myx the YX coordinate element of the transform to be appended 1523 * @param myy the Y coordinate scaling element of the transform to be 1524 * appended 1525 * @param ty the Y coordinate translation element of the transform to be 1526 * appended 1527 * @since JavaFX 8.0 1528 */ 1529 public void append(double mxx, double mxy, double tx, 1530 double myx, double myy, double ty) { 1531 1532 if (state3d == APPLY_NON_3D) { 1533 1534 atomicChange.start(); 1535 1536 final double m_xx = getMxx(); 1537 final double m_xy = getMxy(); 1538 final double m_yx = getMyx(); 1539 final double m_yy = getMyy(); 1540 1541 setMxx(m_xx * mxx + m_xy * myx); 1542 setMxy(m_xx * mxy + m_xy * myy); 1543 setTx(m_xx * tx + m_xy * ty + getTx()); 1544 setMyx(m_yx * mxx + m_yy * myx); 1545 setMyy(m_yx * mxy + m_yy * myy); 1546 setTy(m_yx * tx + m_yy * ty + getTy()); 1547 1548 updateState(); 1549 atomicChange.end(); 1550 } else { 1551 append(mxx, mxy, 0.0, tx, 1552 myx, myy, 0.0, ty, 1553 0.0, 0.0, 1.0, 0.0); 1554 } 1555 } 1556 1557 /** 1558 * <p> 1559 * Appends the transform specified by the element values to this instance. 1560 * The operation modifies this transform in a way that applying it to a node 1561 * has the same effect as adding the two transforms to its 1562 * {@code getTransforms()} list, {@code this} transform first and the specified 1563 * {@code transform} second. 1564 * </p><p> 1565 * From the matrix point of view, the transformation matrix of this 1566 * transform is multiplied on the right by the transformation matrix of 1567 * the specified transform. 1568 * </p> 1569 * 1570 * @param mxx the X coordinate scaling element of the transform to be 1571 * appended 1572 * @param mxy the XY coordinate element of the transform to be appended 1573 * @param mxz the XZ coordinate element of the transform to be appended 1574 * @param tx the X coordinate translation element of the transform to be 1575 * appended 1576 * @param myx the YX coordinate element of the transform to be appended 1577 * @param myy the Y coordinate scaling element of the transform to be 1578 * appended 1579 * @param myz the YZ coordinate element of the transform to be appended 1580 * @param ty the Y coordinate translation element of the transform to be 1581 * appended 1582 * @param mzx the ZX coordinate element of the transform to be appended 1583 * @param mzy the ZY coordinate element of the transform to be appended 1584 * @param mzz the Z coordinate scaling element of the transform to be 1585 * appended 1586 * @param tz the Z coordinate translation element of the transform to be 1587 * appended 1588 * @since JavaFX 8.0 1589 */ 1590 public void append(double mxx, double mxy, double mxz, double tx, 1591 double myx, double myy, double myz, double ty, 1592 double mzx, double mzy, double mzz, double tz) 1593 { 1594 atomicChange.start(); 1595 1596 final double m_xx = getMxx(); 1597 final double m_xy = getMxy(); 1598 final double m_xz = getMxz(); 1599 final double t_x = getTx(); 1600 final double m_yx = getMyx(); 1601 final double m_yy = getMyy(); 1602 final double m_yz = getMyz(); 1603 final double t_y = getTy(); 1604 final double m_zx = getMzx(); 1605 final double m_zy = getMzy(); 1606 final double m_zz = getMzz(); 1607 final double t_z = getTz(); 1608 1609 setMxx(m_xx * mxx + m_xy * myx + m_xz * mzx); 1610 setMxy(m_xx * mxy + m_xy * myy + m_xz * mzy); 1611 setMxz(m_xx * mxz + m_xy * myz + m_xz * mzz); 1612 setTx( m_xx * tx + m_xy * ty + m_xz * tz + t_x); 1613 setMyx(m_yx * mxx + m_yy * myx + m_yz * mzx); 1614 setMyy(m_yx * mxy + m_yy * myy + m_yz * mzy); 1615 setMyz(m_yx * mxz + m_yy * myz + m_yz * mzz); 1616 setTy( m_yx * tx + m_yy * ty + m_yz * tz + t_y); 1617 setMzx(m_zx * mxx + m_zy * myx + m_zz * mzx); 1618 setMzy(m_zx * mxy + m_zy * myy + m_zz * mzy); 1619 setMzz(m_zx * mxz + m_zy * myz + m_zz * mzz); 1620 setTz( m_zx * tx + m_zy * ty + m_zz * tz + t_z); 1621 1622 updateState(); 1623 atomicChange.end(); 1624 } 1625 1626 /** 1627 * <p> 1628 * Appends the transform specified by the array to this instance. 1629 * The operation modifies this transform in a way that applying it to a node 1630 * has the same effect as adding the two transforms to its 1631 * {@code getTransforms()} list, {@code this} transform first and the specified 1632 * {@code transform} second. 1633 * </p><p> 1634 * From the matrix point of view, the transformation matrix of this 1635 * transform is multiplied on the right by the transformation matrix of 1636 * the specified transform. 1637 * </p> 1638 * 1639 * @param matrix array containing the flattened transformation matrix 1640 * to be appended 1641 * @param type type of matrix contained in the array 1642 * @param offset offset of the first matrix element in the array 1643 * @throws IndexOutOfBoundsException if the array is too short for 1644 * the specified {@code type} and {@code offset} 1645 * @throws IllegalArgumentException if the specified matrix is not affine 1646 * (the last line of a 2D 3x3 matrix is not {@code [0, 0, 1]} or 1647 * the last line of a 3D 4x4 matrix is not {@code [0, 0, 0, 1]}. 1648 * @throws NullPointerException if the specified {@code matrix} 1649 * or {@code type} is null 1650 * @since JavaFX 8.0 1651 */ 1652 public void append(double[] matrix, MatrixType type, int offset) { 1653 if (matrix.length < offset + type.elements()) { 1654 throw new IndexOutOfBoundsException("The array is too short."); 1655 } 1656 1657 switch(type) { 1658 default: 1659 stateError(); 1660 // cannot reach 1661 case MT_2D_3x3: 1662 if (matrix[offset + 6] != 0.0 || 1663 matrix[offset + 7] != 0.0 || 1664 matrix[offset + 8] != 1.0) { 1665 throw new IllegalArgumentException("The matrix is " 1666 + "not affine"); 1667 } 1668 // fall-through 1669 case MT_2D_2x3: 1670 append(matrix[offset++], matrix[offset++], 1671 matrix[offset++], matrix[offset++], 1672 matrix[offset++], matrix[offset++]); 1673 return; 1674 case MT_3D_4x4: 1675 if (matrix[offset + 12] != 0.0 || 1676 matrix[offset + 13] != 0.0 || 1677 matrix[offset + 14] != 0.0 || 1678 matrix[offset + 15] != 1.0) { 1679 throw new IllegalArgumentException("The matrix is " 1680 + "not affine"); 1681 } 1682 // fall-through 1683 case MT_3D_3x4: 1684 append(matrix[offset++], matrix[offset++], matrix[offset++], 1685 matrix[offset++], matrix[offset++], matrix[offset++], 1686 matrix[offset++], matrix[offset++], matrix[offset++], 1687 matrix[offset++], matrix[offset++], matrix[offset++]); 1688 return; 1689 } 1690 } 1691 1692 @Override 1693 void appendTo(Affine a) { 1694 switch(state3d) { 1695 default: 1696 stateError(); 1697 // cannot reach 1698 case APPLY_NON_3D: 1699 switch(state2d) { 1700 case APPLY_IDENTITY: 1701 return; 1702 case APPLY_TRANSLATE: 1703 a.appendTranslation(getTx(), getTy()); 1704 return; 1705 case APPLY_SCALE: 1706 a.appendScale(getMxx(), getMyy()); 1707 return; 1708 case APPLY_SCALE | APPLY_TRANSLATE: 1709 a.appendTranslation(getTx(), getTy()); 1710 a.appendScale(getMxx(), getMyy()); 1711 return; 1712 default: 1713 a.append(getMxx(), getMxy(), getTx(), 1714 getMyx(), getMyy(), getTy()); 1715 return; 1716 } 1717 case APPLY_TRANSLATE: 1718 a.appendTranslation(getTx(), getTy(), getTz()); 1719 return; 1720 case APPLY_SCALE: 1721 a.appendScale(getMxx(), getMyy(), getMzz()); 1722 return; 1723 case APPLY_SCALE | APPLY_TRANSLATE: 1724 a.appendTranslation(getTx(), getTy(), getTz()); 1725 a.appendScale(getMxx(), getMyy(), getMzz()); 1726 return; 1727 case APPLY_3D_COMPLEX: 1728 a.append(getMxx(), getMxy(), getMxz(), getTx(), 1729 getMyx(), getMyy(), getMyz(), getTy(), 1730 getMzx(), getMzy(), getMzz(), getTz()); 1731 return; 1732 } 1733 } 1734 1735 /** 1736 * <p> 1737 * Prepends the specified transform to this instance. 1738 * The operation modifies this transform in a way that applying it to a node 1739 * has the same effect as adding the two transforms to its 1740 * {@code getTransforms()} list, the specified {@code transform} first 1741 * and {@code this} transform second. 1742 * </p><p> 1743 * From the matrix point of view, the transformation matrix of this 1744 * transform is multiplied on the left by the transformation matrix of 1745 * the specified transform. 1746 * </p> 1747 * 1748 * @param transform transform to be prepended to this instance 1749 * @throws NullPointerException if the specified {@code transform} is null 1750 * @since JavaFX 8.0 1751 */ 1752 public void prepend(Transform transform) { 1753 transform.prependTo(this); 1754 } 1755 1756 /** 1757 * <p> 1758 * Prepends the 2D transform specified by the element values to this instance. 1759 * The operation modifies this transform in a way that applying it to a node 1760 * has the same effect as adding the two transforms to its 1761 * {@code getTransforms()} list, the specified {@code transform} first 1762 * and {@code this} transform second. 1763 * </p><p> 1764 * From the matrix point of view, the transformation matrix of this 1765 * transform is multiplied on the left by the transformation matrix of 1766 * the specified transform. 1767 * </p> 1768 * 1769 * @param mxx the X coordinate scaling element of the transform to be 1770 * prepended 1771 * @param mxy the XY coordinate element of the transform to be prepended 1772 * @param tx the X coordinate translation element of the transform to be 1773 * prepended 1774 * @param myx the YX coordinate element of the transform to be prepended 1775 * @param myy the Y coordinate scaling element of the transform to be 1776 * prepended 1777 * @param ty the Y coordinate translation element of the transform to be 1778 * prepended 1779 * @since JavaFX 8.0 1780 */ 1781 public void prepend(double mxx, double mxy, double tx, 1782 double myx, double myy, double ty) { 1783 1784 if (state3d == APPLY_NON_3D) { 1785 atomicChange.start(); 1786 1787 final double m_xx = getMxx(); 1788 final double m_xy = getMxy(); 1789 final double t_x = getTx(); 1790 final double m_yx = getMyx(); 1791 final double m_yy = getMyy(); 1792 final double t_y = getTy(); 1793 1794 setMxx(mxx * m_xx + mxy * m_yx); 1795 setMxy(mxx * m_xy + mxy * m_yy); 1796 setTx(mxx * t_x + mxy * t_y + tx); 1797 setMyx(myx * m_xx + myy * m_yx); 1798 setMyy(myx * m_xy + myy * m_yy); 1799 setTy(myx * t_x + myy * t_y + ty); 1800 1801 updateState2D(); 1802 atomicChange.end(); 1803 } else { 1804 prepend(mxx, mxy, 0.0, tx, 1805 myx, myy, 0.0, ty, 1806 0.0, 0.0, 1.0, 0.0); 1807 } 1808 } 1809 1810 /** 1811 * <p> 1812 * Prepends the transform specified by the element values to this instance. 1813 * The operation modifies this transform in a way that applying it to a node 1814 * has the same effect as adding the two transforms to its 1815 * {@code getTransforms()} list, the specified {@code transform} first 1816 * and {@code this} transform second. 1817 * </p><p> 1818 * From the matrix point of view, the transformation matrix of this 1819 * transform is multiplied on the left by the transformation matrix of 1820 * the specified transform. 1821 * </p> 1822 * 1823 * @param mxx the X coordinate scaling element of the transform to be 1824 * prepended 1825 * @param mxy the XY coordinate element of the transform to be prepended 1826 * @param mxz the XZ coordinate element of the transform to be prepended 1827 * @param tx the X coordinate translation element of the transform to be 1828 * prepended 1829 * @param myx the YX coordinate element of the transform to be prepended 1830 * @param myy the Y coordinate scaling element of the transform to be 1831 * prepended 1832 * @param myz the YZ coordinate element of the transform to be prepended 1833 * @param ty the Y coordinate translation element of the transform to be 1834 * prepended 1835 * @param mzx the ZX coordinate element of the transform to be prepended 1836 * @param mzy the ZY coordinate element of the transform to be prepended 1837 * @param mzz the Z coordinate scaling element of the transform to be 1838 * prepended 1839 * @param tz the Z coordinate translation element of the transform to be 1840 * prepended 1841 * @since JavaFX 8.0 1842 */ 1843 public void prepend(double mxx, double mxy, double mxz, double tx, 1844 double myx, double myy, double myz, double ty, 1845 double mzx, double mzy, double mzz, double tz) { 1846 atomicChange.start(); 1847 1848 final double m_xx = getMxx(); 1849 final double m_xy = getMxy(); 1850 final double m_xz = getMxz(); 1851 final double t_x = getTx(); 1852 final double m_yx = getMyx(); 1853 final double m_yy = getMyy(); 1854 final double m_yz = getMyz(); 1855 final double t_y = getTy(); 1856 final double m_zx = getMzx(); 1857 final double m_zy = getMzy(); 1858 final double m_zz = getMzz(); 1859 final double t_z = getTz(); 1860 1861 setMxx(mxx * m_xx + mxy * m_yx + mxz * m_zx); 1862 setMxy(mxx * m_xy + mxy * m_yy + mxz * m_zy); 1863 setMxz(mxx * m_xz + mxy * m_yz + mxz * m_zz); 1864 setTx( mxx * t_x + mxy * t_y + mxz * t_z + tx); 1865 setMyx(myx * m_xx + myy * m_yx + myz * m_zx); 1866 setMyy(myx * m_xy + myy * m_yy + myz * m_zy); 1867 setMyz(myx * m_xz + myy * m_yz + myz * m_zz); 1868 setTy( myx * t_x + myy * t_y + myz * t_z + ty); 1869 setMzx(mzx * m_xx + mzy * m_yx + mzz * m_zx); 1870 setMzy(mzx * m_xy + mzy * m_yy + mzz * m_zy); 1871 setMzz(mzx * m_xz + mzy * m_yz + mzz * m_zz); 1872 setTz( mzx * t_x + mzy * t_y + mzz * t_z + tz); 1873 1874 updateState(); 1875 atomicChange.end(); 1876 } 1877 1878 /** 1879 * <p> 1880 * Prepends the transform specified by the array to this instance. 1881 * The operation modifies this transform in a way that applying it to a node 1882 * has the same effect as adding the two transforms to its 1883 * {@code getTransforms()} list, the specified {@code transform} first 1884 * and {@code this} transform second. 1885 * </p><p> 1886 * From the matrix point of view, the transformation matrix of this 1887 * transform is multiplied on the left by the transformation matrix of 1888 * the specified transform. 1889 * </p> 1890 * 1891 * @param matrix array containing the flattened transformation matrix 1892 * to be prepended 1893 * @param type type of matrix contained in the array 1894 * @param offset offset of the first matrix element in the array 1895 * @throws IndexOutOfBoundsException if the array is too short for 1896 * the specified {@code type} and {@code offset} 1897 * @throws IllegalArgumentException if the specified matrix is not affine 1898 * (the last line of a 2D 3x3 matrix is not {@code [0, 0, 1]} or 1899 * the last line of a 3D 4x4 matrix is not {@code [0, 0, 0, 1]}. 1900 * @throws NullPointerException if the specified {@code matrix} 1901 * or {@code type} is null 1902 * @since JavaFX 8.0 1903 */ 1904 public void prepend(double[] matrix, MatrixType type, int offset) { 1905 if (matrix.length < offset + type.elements()) { 1906 throw new IndexOutOfBoundsException("The array is too short."); 1907 } 1908 1909 switch(type) { 1910 default: 1911 stateError(); 1912 // cannot reach 1913 case MT_2D_3x3: 1914 if (matrix[offset + 6] != 0.0 || 1915 matrix[offset + 7] != 0.0 || 1916 matrix[offset + 8] != 1.0) { 1917 throw new IllegalArgumentException("The matrix is " 1918 + "not affine"); 1919 } 1920 // fall-through 1921 case MT_2D_2x3: 1922 prepend(matrix[offset++], matrix[offset++], 1923 matrix[offset++], matrix[offset++], 1924 matrix[offset++], matrix[offset++]); 1925 return; 1926 case MT_3D_4x4: 1927 if (matrix[offset + 12] != 0.0 || 1928 matrix[offset + 13] != 0.0 || 1929 matrix[offset + 14] != 0.0 || 1930 matrix[offset + 15] != 1.0) { 1931 throw new IllegalArgumentException("The matrix is " 1932 + "not affine"); 1933 } 1934 // fall-through 1935 case MT_3D_3x4: 1936 prepend(matrix[offset++], matrix[offset++], matrix[offset++], 1937 matrix[offset++], matrix[offset++], matrix[offset++], 1938 matrix[offset++], matrix[offset++], matrix[offset++], 1939 matrix[offset++], matrix[offset++], matrix[offset++]); 1940 return; 1941 } 1942 } 1943 1944 @Override 1945 void prependTo(Affine a) { 1946 switch(state3d) { 1947 default: 1948 stateError(); 1949 // cannot reach 1950 case APPLY_NON_3D: 1951 switch(state2d) { 1952 case APPLY_IDENTITY: 1953 return; 1954 case APPLY_TRANSLATE: 1955 a.prependTranslation(getTx(), getTy()); 1956 return; 1957 case APPLY_SCALE: 1958 a.prependScale(getMxx(), getMyy()); 1959 return; 1960 case APPLY_SCALE | APPLY_TRANSLATE: 1961 a.prependScale(getMxx(), getMyy()); 1962 a.prependTranslation(getTx(), getTy()); 1963 return; 1964 default: 1965 a.prepend(getMxx(), getMxy(), getTx(), 1966 getMyx(), getMyy(), getTy()); 1967 return; 1968 } 1969 case APPLY_TRANSLATE: 1970 a.prependTranslation(getTx(), getTy(), getTz()); 1971 return; 1972 case APPLY_SCALE: 1973 a.prependScale(getMxx(), getMyy(), getMzz()); 1974 return; 1975 case APPLY_SCALE | APPLY_TRANSLATE: 1976 a.prependScale(getMxx(), getMyy(), getMzz()); 1977 a.prependTranslation(getTx(), getTy(), getTz()); 1978 return; 1979 case APPLY_3D_COMPLEX: 1980 a.prepend(getMxx(), getMxy(), getMxz(), getTx(), 1981 getMyx(), getMyy(), getMyz(), getTy(), 1982 getMzx(), getMzy(), getMzz(), getTz()); 1983 return; 1984 } 1985 } 1986 1987 1988 /* ************************************************* 1989 * Translate * 1990 **************************************************/ 1991 1992 /** 1993 * <p> 1994 * Appends the 2D translation to this instance. 1995 * It is equivalent to {@code append(new Translate(tx, ty))}. 1996 * </p><p> 1997 * The operation modifies this transform in a way that applying it to a node 1998 * has the same effect as adding two transforms to its 1999 * {@code getTransforms()} list, {@code this} transform first and the specified 2000 * translation second. 2001 * </p><p> 2002 * From the matrix point of view, the transformation matrix of this 2003 * transform is multiplied on the right by the transformation matrix of 2004 * the specified translation. 2005 * </p> 2006 * @param tx the X coordinate translation 2007 * @param ty the Y coordinate translation 2008 * @since JavaFX 8.0 2009 */ 2010 public void appendTranslation(double tx, double ty) { 2011 atomicChange.start(); 2012 translate2D(tx, ty); 2013 atomicChange.end(); 2014 } 2015 2016 /** 2017 * <p> 2018 * Appends the translation to this instance. 2019 * It is equivalent to {@code append(new Translate(tx, ty, tz))}. 2020 * </p><p> 2021 * The operation modifies this transform in a way that applying it to a node 2022 * has the same effect as adding two transforms to its 2023 * {@code getTransforms()} list, {@code this} transform first and the specified 2024 * translation second. 2025 * </p><p> 2026 * From the matrix point of view, the transformation matrix of this 2027 * transform is multiplied on the right by the transformation matrix of 2028 * the specified translation. 2029 * </p> 2030 * @param tx the X coordinate translation 2031 * @param ty the Y coordinate translation 2032 * @param tz the Z coordinate translation 2033 * @since JavaFX 8.0 2034 */ 2035 public void appendTranslation(double tx, double ty, double tz) { 2036 atomicChange.start(); 2037 translate3D(tx, ty, tz); 2038 atomicChange.end(); 2039 } 2040 2041 /** 2042 * 2D implementation of {@code appendTranslation()}. 2043 * If this is a 3D transform, the call is redirected to {@code transalte3D()}. 2044 */ 2045 private void translate2D(double tx, double ty) { 2046 if (state3d != APPLY_NON_3D) { 2047 translate3D(tx, ty, 0.0); 2048 return; 2049 } 2050 2051 switch (state2d) { 2052 default: 2053 stateError(); 2054 // cannot reach 2055 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 2056 setTx(tx * getMxx() + ty * getMxy() + getTx()); 2057 setTy(tx * getMyx() + ty * getMyy() + getTy()); 2058 if (getTx() == 0.0 && getTy() == 0.0) { 2059 state2d = APPLY_SHEAR | APPLY_SCALE; 2060 } 2061 return; 2062 case APPLY_SHEAR | APPLY_SCALE: 2063 setTx(tx * getMxx() + ty * getMxy()); 2064 setTy(tx * getMyx() + ty * getMyy()); 2065 if (getTx() != 0.0 || getTy() != 0.0) { 2066 state2d = APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE; 2067 } 2068 return; 2069 case APPLY_SHEAR | APPLY_TRANSLATE: 2070 setTx(ty * getMxy() + getTx()); 2071 setTy(tx * getMyx() + getTy()); 2072 if (getTx() == 0.0 && getTy() == 0.0) { 2073 state2d = APPLY_SHEAR; 2074 } 2075 return; 2076 case APPLY_SHEAR: 2077 setTx(ty * getMxy()); 2078 setTy(tx * getMyx()); 2079 if (getTx() != 0.0 || getTy() != 0.0) { 2080 state2d = APPLY_SHEAR | APPLY_TRANSLATE; 2081 } 2082 return; 2083 case APPLY_SCALE | APPLY_TRANSLATE: 2084 setTx(tx * getMxx() + getTx()); 2085 setTy(ty * getMyy() + getTy()); 2086 if (getTx() == 0.0 && getTy() == 0.0) { 2087 state2d = APPLY_SCALE; 2088 } 2089 return; 2090 case APPLY_SCALE: 2091 setTx(tx * getMxx()); 2092 setTy(ty * getMyy()); 2093 if (getTx() != 0.0 || getTy() != 0.0) { 2094 state2d = APPLY_SCALE | APPLY_TRANSLATE; 2095 } 2096 return; 2097 case APPLY_TRANSLATE: 2098 setTx(tx + getTx()); 2099 setTy(ty + getTy()); 2100 if (getTx() == 0.0 && getTy() == 0.0) { 2101 state2d = APPLY_IDENTITY; 2102 } 2103 return; 2104 case APPLY_IDENTITY: 2105 setTx(tx); 2106 setTy(ty); 2107 if (tx != 0.0 || ty != 0.0) { 2108 state2d = APPLY_TRANSLATE; 2109 } 2110 return; 2111 } 2112 } 2113 2114 /** 2115 * 3D implementation of {@code appendTranslation()}. 2116 * Works fine if this is a 2D transform. 2117 */ 2118 private void translate3D(double tx, double ty, double tz) { 2119 switch(state3d) { 2120 default: 2121 stateError(); 2122 // cannot reach 2123 case APPLY_NON_3D: 2124 translate2D(tx, ty); 2125 if (tz != 0.0) { 2126 setTz(tz); 2127 if ((state2d & APPLY_SHEAR) == 0) { 2128 state3d = (state2d & APPLY_SCALE) | APPLY_TRANSLATE; 2129 } else { 2130 state3d = APPLY_3D_COMPLEX; 2131 } 2132 } 2133 return; 2134 case APPLY_TRANSLATE: 2135 setTx(tx + getTx()); 2136 setTy(ty + getTy()); 2137 setTz(tz + getTz()); 2138 if (getTz() == 0.0) { 2139 state3d = APPLY_NON_3D; 2140 if (getTx() == 0.0 && getTy() == 0.0) { 2141 state2d = APPLY_IDENTITY; 2142 } else { 2143 state2d = APPLY_TRANSLATE; 2144 } 2145 } 2146 return; 2147 case APPLY_SCALE: 2148 setTx(tx * getMxx()); 2149 setTy(ty * getMyy()); 2150 setTz(tz * getMzz()); 2151 if (getTx() != 0.0 || getTy() != 0.0 || getTz() != 0.0) { 2152 state3d |= APPLY_TRANSLATE; 2153 } 2154 return; 2155 case APPLY_SCALE | APPLY_TRANSLATE: 2156 setTx(tx * getMxx() + getTx()); 2157 setTy(ty * getMyy() + getTy()); 2158 setTz(tz * getMzz() + getTz()); 2159 if (getTz() == 0.0) { 2160 if (getTx() == 0.0 && getTy() == 0.0) { 2161 state3d = APPLY_SCALE; 2162 } 2163 if (getMzz() == 1.0) { 2164 state2d = state3d; 2165 state3d = APPLY_NON_3D; 2166 } 2167 } 2168 return; 2169 case APPLY_3D_COMPLEX: 2170 setTx(tx * getMxx() + ty * getMxy() + tz * getMxz() + getTx()); 2171 setTy(tx * getMyx() + ty * getMyy() + tz * getMyz() + getTy()); 2172 setTz(tx * getMzx() + ty * getMzy() + tz * getMzz() + getTz()); 2173 updateState(); 2174 return; 2175 } 2176 } 2177 2178 /** 2179 * <p> 2180 * Prepends the translation to this instance. 2181 * It is equivalent to {@code prepend(new Translate(tx, ty, tz))}. 2182 * </p><p> 2183 * The operation modifies this transform in a way that applying it to a node 2184 * has the same effect as adding two transforms to its 2185 * {@code getTransforms()} list, the specified translation first 2186 * and {@code this} transform second. 2187 * </p><p> 2188 * From the matrix point of view, the transformation matrix of this 2189 * transform is multiplied on the left by the transformation matrix of 2190 * the specified translation. 2191 * </p> 2192 * @param tx the X coordinate translation 2193 * @param ty the Y coordinate translation 2194 * @param tz the Z coordinate translation 2195 * @since JavaFX 8.0 2196 */ 2197 public void prependTranslation(double tx, double ty, double tz) { 2198 atomicChange.start(); 2199 preTranslate3D(tx, ty, tz); 2200 atomicChange.end(); 2201 } 2202 2203 /** 2204 * <p> 2205 * Prepends the 2D translation to this instance. 2206 * It is equivalent to {@code prepend(new Translate(tx, ty))}. 2207 * </p><p> 2208 * The operation modifies this transform in a way that applying it to a node 2209 * has the same effect as adding two transforms to its 2210 * {@code getTransforms()} list, the specified translation first 2211 * and {@code this} transform second. 2212 * </p><p> 2213 * From the matrix point of view, the transformation matrix of this 2214 * transform is multiplied on the left by the transformation matrix of 2215 * the specified translation. 2216 * </p> 2217 * @param tx the X coordinate translation 2218 * @param ty the Y coordinate translation 2219 * @since JavaFX 8.0 2220 */ 2221 public void prependTranslation(double tx, double ty) { 2222 atomicChange.start(); 2223 preTranslate2D(tx, ty); 2224 atomicChange.end(); 2225 } 2226 2227 /** 2228 * 2D implementation of {@code prependTranslation()}. 2229 * If this is a 3D transform, the call is redirected to 2230 * {@code preTransalte3D}. 2231 * @since JavaFX 8.0 2232 */ 2233 private void preTranslate2D(double tx, double ty) { 2234 if (state3d != APPLY_NON_3D) { 2235 preTranslate3D(tx, ty, 0.0); 2236 return; 2237 } 2238 2239 setTx(getTx() + tx); 2240 setTy(getTy() + ty); 2241 2242 if (getTx() == 0.0 && getTy() == 0.0) { 2243 state2d &= ~APPLY_TRANSLATE; 2244 } else { 2245 state2d |= APPLY_TRANSLATE; 2246 } 2247 } 2248 2249 /** 2250 * 3D implementation of {@code prependTranslation()}. 2251 * Works fine if this is a 2D transform. 2252 */ 2253 private void preTranslate3D(double tx, double ty, double tz) { 2254 switch(state3d) { 2255 default: 2256 stateError(); 2257 // cannot reach 2258 case APPLY_NON_3D: 2259 preTranslate2D(tx, ty); 2260 2261 if (tz != 0.0) { 2262 setTz(tz); 2263 2264 if ((state2d & APPLY_SHEAR) == 0) { 2265 state3d = (state2d & APPLY_SCALE) | APPLY_TRANSLATE; 2266 } else { 2267 state3d = APPLY_3D_COMPLEX; 2268 } 2269 } 2270 return; 2271 case APPLY_TRANSLATE: 2272 setTx(getTx() + tx); 2273 setTy(getTy() + ty); 2274 setTz(getTz() + tz); 2275 if (getTz() == 0.0) { 2276 state3d = APPLY_NON_3D; 2277 if (getTx() == 0.0 && getTy() == 0.0) { 2278 state2d = APPLY_IDENTITY; 2279 } else { 2280 state2d = APPLY_TRANSLATE; 2281 } 2282 } 2283 return; 2284 case APPLY_SCALE: 2285 setTx(tx); 2286 setTy(ty); 2287 setTz(tz); 2288 if (tx != 0.0 || ty != 0.0 || tz != 0.0) { 2289 state3d |= APPLY_TRANSLATE; 2290 } 2291 return; 2292 case APPLY_SCALE | APPLY_TRANSLATE: 2293 setTx(getTx() + tx); 2294 setTy(getTy() + ty); 2295 setTz(getTz() + tz); 2296 2297 if (getTz() == 0.0) { 2298 if (getTx() == 0.0 && getTy() == 0.0) { 2299 state3d = APPLY_SCALE; 2300 } 2301 if (getMzz() == 1.0) { 2302 state2d = state3d; 2303 state3d = APPLY_NON_3D; 2304 } 2305 } 2306 return; 2307 case APPLY_3D_COMPLEX: 2308 setTx(getTx() + tx); 2309 setTy(getTy() + ty); 2310 setTz(getTz() + tz); 2311 if (getTz() == 0.0 && getMxz() == 0.0 && getMyz() == 0.0 && 2312 getMzx() == 0.0 && getMzy() == 0.0 && getMzz() == 1.0) { 2313 state3d = APPLY_NON_3D; 2314 updateState2D(); 2315 } // otherwise state remains COMPLEX 2316 return; 2317 } 2318 } 2319 2320 /* ************************************************* 2321 * Scale * 2322 **************************************************/ 2323 2324 /** 2325 * <p> 2326 * Appends the 2D scale to this instance. 2327 * It is equivalent to {@code append(new Scale(sx, sy))}. 2328 * </p><p> 2329 * The operation modifies this transform in a way that applying it to a node 2330 * has the same effect as adding two transforms to its 2331 * {@code getTransforms()} list, {@code this} transform first and the specified 2332 * scale second. 2333 * </p><p> 2334 * From the matrix point of view, the transformation matrix of this 2335 * transform is multiplied on the right by the transformation matrix of 2336 * the specified scale. 2337 * </p> 2338 * @param sx the X coordinate scale factor 2339 * @param sy the Y coordinate scale factor 2340 * @since JavaFX 8.0 2341 */ 2342 public void appendScale(double sx, double sy) { 2343 atomicChange.start(); 2344 scale2D(sx, sy); 2345 atomicChange.end(); 2346 } 2347 2348 /** 2349 * <p> 2350 * Appends the 2D scale with pivot to this instance. 2351 * It is equivalent to {@code append(new Scale(sx, sy, pivotX, pivotY))}. 2352 * </p><p> 2353 * The operation modifies this transform in a way that applying it to a node 2354 * has the same effect as adding two transforms to its 2355 * {@code getTransforms()} list, {@code this} transform first and the specified 2356 * scale second. 2357 * </p><p> 2358 * From the matrix point of view, the transformation matrix of this 2359 * transform is multiplied on the right by the transformation matrix of 2360 * the specified scale. 2361 * </p> 2362 * @param sx the X coordinate scale factor 2363 * @param sy the Y coordinate scale factor 2364 * @param pivotX the X coordinate of the point about which the scale occurs 2365 * @param pivotY the Y coordinate of the point about which the scale occurs 2366 * @since JavaFX 8.0 2367 */ 2368 public void appendScale(double sx, double sy, 2369 double pivotX, double pivotY) { 2370 atomicChange.start(); 2371 if (pivotX != 0.0 || pivotY != 0.0) { 2372 translate2D(pivotX, pivotY); 2373 scale2D(sx, sy); 2374 translate2D(-pivotX, -pivotY); 2375 } else { 2376 scale2D(sx, sy); 2377 } 2378 atomicChange.end(); 2379 } 2380 2381 /** 2382 * <p> 2383 * Appends the 2D scale with pivot to this instance. 2384 * It is equivalent to 2385 * {@code append(new Scale(sx, sy, pivot.getX(), pivot.getY())}. 2386 * </p><p> 2387 * The operation modifies this transform in a way that applying it to a node 2388 * has the same effect as adding two transforms to its 2389 * {@code getTransforms()} list, {@code this} transform first and the specified 2390 * scale second. 2391 * </p><p> 2392 * From the matrix point of view, the transformation matrix of this 2393 * transform is multiplied on the right by the transformation matrix of 2394 * the specified scale. 2395 * </p> 2396 * @param sx the X coordinate scale factor 2397 * @param sy the Y coordinate scale factor 2398 * @param pivot the point about which the scale occurs 2399 * @throws NullPointerException if the specified {@code pivot} is null 2400 * @since JavaFX 8.0 2401 */ 2402 public void appendScale(double sx, double sy, Point2D pivot) { 2403 appendScale(sx, sy, pivot.getX(), pivot.getY()); 2404 } 2405 2406 /** 2407 * <p> 2408 * Appends the scale to this instance. 2409 * It is equivalent to {@code append(new Scale(sx, sy, sz))}. 2410 * </p><p> 2411 * The operation modifies this transform in a way that applying it to a node 2412 * has the same effect as adding two transforms to its 2413 * {@code getTransforms()} list, {@code this} transform first and the specified 2414 * scale second. 2415 * </p><p> 2416 * From the matrix point of view, the transformation matrix of this 2417 * transform is multiplied on the right by the transformation matrix of 2418 * the specified scale. 2419 * </p> 2420 * @param sx the X coordinate scale factor 2421 * @param sy the Y coordinate scale factor 2422 * @param sz the Z coordinate scale factor 2423 * @since JavaFX 8.0 2424 */ 2425 public void appendScale(double sx, double sy, double sz) { 2426 atomicChange.start(); 2427 scale3D(sx, sy, sz); 2428 atomicChange.end(); 2429 } 2430 2431 /** 2432 * <p> 2433 * Appends the scale with pivot to this instance. 2434 * It is equivalent to {@code append(new Scale(sx, sy, sz, pivotX, 2435 * pivotY, pivotZ))}. 2436 * </p><p> 2437 * The operation modifies this transform in a way that applying it to a node 2438 * has the same effect as adding two transforms to its 2439 * {@code getTransforms()} list, {@code this} transform first and the specified 2440 * scale second. 2441 * </p><p> 2442 * From the matrix point of view, the transformation matrix of this 2443 * transform is multiplied on the right by the transformation matrix of 2444 * the specified scale. 2445 * </p> 2446 * @param sx the X coordinate scale factor 2447 * @param sy the Y coordinate scale factor 2448 * @param sz the Z coordinate scale factor 2449 * @param pivotX the X coordinate of the point about which the scale occurs 2450 * @param pivotY the Y coordinate of the point about which the scale occurs 2451 * @param pivotZ the Z coordinate of the point about which the scale occurs 2452 * @since JavaFX 8.0 2453 */ 2454 public void appendScale(double sx, double sy, double sz, 2455 double pivotX, double pivotY, double pivotZ) { 2456 atomicChange.start(); 2457 if (pivotX != 0.0 || pivotY != 0.0 || pivotZ != 0.0) { 2458 translate3D(pivotX, pivotY, pivotZ); 2459 scale3D(sx, sy, sz); 2460 translate3D(-pivotX, -pivotY, -pivotZ); 2461 } else { 2462 scale3D(sx, sy, sz); 2463 } 2464 atomicChange.end(); 2465 } 2466 2467 /** 2468 * <p> 2469 * Appends the scale with pivot to this instance. 2470 * It is equivalent to {@code append(new Scale(sx, sy, sz, pivot.getX(), 2471 * pivot.getY(), pivot.getZ()))}. 2472 * </p><p> 2473 * The operation modifies this transform in a way that applying it to a node 2474 * has the same effect as adding two transforms to its 2475 * {@code getTransforms()} list, {@code this} transform first and the specified 2476 * scale second. 2477 * </p><p> 2478 * From the matrix point of view, the transformation matrix of this 2479 * transform is multiplied on the right by the transformation matrix of 2480 * the specified scale. 2481 * </p> 2482 * @param sx the X coordinate scale factor 2483 * @param sy the Y coordinate scale factor 2484 * @param sz the Z coordinate scale factor 2485 * @param pivot the point about which the scale occurs 2486 * @throws NullPointerException if the specified {@code pivot} is null 2487 * @since JavaFX 8.0 2488 */ 2489 public void appendScale(double sx, double sy, double sz, Point3D pivot) { 2490 appendScale(sx, sy, sz, pivot.getX(), pivot.getY(), pivot.getZ()); 2491 } 2492 2493 /** 2494 * 2D implementation of {@code appendScale()}. 2495 * If this is a 3D transform, the call is redirected to {@code scale3D()}. 2496 */ 2497 private void scale2D(double sx, double sy) { 2498 if (state3d != APPLY_NON_3D) { 2499 scale3D(sx, sy, 1.0); 2500 return; 2501 } 2502 2503 int mystate = state2d; 2504 switch (mystate) { 2505 default: 2506 stateError(); 2507 // cannot reach 2508 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 2509 case APPLY_SHEAR | APPLY_SCALE: 2510 setMxx(getMxx() * sx); 2511 setMyy(getMyy() * sy); 2512 // fall-through 2513 case APPLY_SHEAR | APPLY_TRANSLATE: 2514 case APPLY_SHEAR: 2515 setMxy(getMxy() * sy); 2516 setMyx(getMyx() * sx); 2517 if (getMxy() == 0.0 && getMyx() == 0.0) { 2518 mystate &= APPLY_TRANSLATE; 2519 if (getMxx() != 1.0 || getMyy() != 1.0) { 2520 mystate |= APPLY_SCALE; 2521 } 2522 state2d = mystate; 2523 } else if (getMxx() == 0.0 && getMyy() == 0.0) { 2524 state2d &= ~APPLY_SCALE; 2525 } 2526 return; 2527 case APPLY_SCALE | APPLY_TRANSLATE: 2528 case APPLY_SCALE: 2529 setMxx(getMxx() * sx); 2530 setMyy(getMyy() * sy); 2531 if (getMxx() == 1.0 && getMyy() == 1.0) { 2532 state2d = (mystate &= APPLY_TRANSLATE); 2533 } 2534 return; 2535 case APPLY_TRANSLATE: 2536 case APPLY_IDENTITY: 2537 setMxx(sx); 2538 setMyy(sy); 2539 if (sx != 1.0 || sy != 1.0) { 2540 state2d = (mystate | APPLY_SCALE); 2541 } 2542 return; 2543 } 2544 } 2545 2546 /** 2547 * 3D implementation of {@code appendScale()}. 2548 * Works fine if this is a 2D transform. 2549 */ 2550 private void scale3D(double sx, double sy, double sz) { 2551 switch (state3d) { 2552 default: 2553 stateError(); 2554 // cannot reach 2555 case APPLY_NON_3D: 2556 scale2D(sx, sy); 2557 if (sz != 1.0) { 2558 setMzz(sz); 2559 if ((state2d & APPLY_SHEAR) == 0) { 2560 state3d = (state2d & APPLY_TRANSLATE) | APPLY_SCALE; 2561 } else { 2562 state3d = APPLY_3D_COMPLEX; 2563 } 2564 } 2565 return; 2566 case APPLY_TRANSLATE: 2567 setMxx(sx); 2568 setMyy(sy); 2569 setMzz(sz); 2570 if (sx != 1.0 || sy != 1.0 || sz != 1.0) { 2571 state3d |= APPLY_SCALE; 2572 } 2573 return; 2574 case APPLY_SCALE: 2575 setMxx(getMxx() * sx); 2576 setMyy(getMyy() * sy); 2577 setMzz(getMzz() * sz); 2578 if (getMzz() == 1.0) { 2579 state3d = APPLY_NON_3D; 2580 if (getMxx() == 1.0 && getMyy() == 1.0) { 2581 state2d = APPLY_IDENTITY; 2582 } else { 2583 state2d = APPLY_SCALE; 2584 } 2585 } 2586 return; 2587 case APPLY_SCALE | APPLY_TRANSLATE: 2588 setMxx(getMxx() * sx); 2589 setMyy(getMyy() * sy); 2590 setMzz(getMzz() * sz); 2591 2592 if (getMxx() == 1.0 && getMyy() == 1.0 && getMzz() == 1.0) { 2593 state3d &= ~APPLY_SCALE; 2594 } 2595 if (getTz() == 0.0 && getMzz() == 1.0) { 2596 state2d = state3d; 2597 state3d = APPLY_NON_3D; 2598 } 2599 return; 2600 case APPLY_3D_COMPLEX: 2601 setMxx(getMxx() * sx); 2602 setMxy(getMxy() * sy); 2603 setMxz(getMxz() * sz); 2604 2605 setMyx(getMyx() * sx); 2606 setMyy(getMyy() * sy); 2607 setMyz(getMyz() * sz); 2608 2609 setMzx(getMzx() * sx); 2610 setMzy(getMzy() * sy); 2611 setMzz(getMzz() * sz); 2612 2613 if (sx == 0.0 || sy == 0.0 || sz == 0.0) { 2614 updateState(); 2615 } // otherwise state remains COMPLEX 2616 return; 2617 } 2618 } 2619 2620 /** 2621 * <p> 2622 * Prepends the 2D scale to this instance. 2623 * It is equivalent to {@code prepend(new Scale(sx, sy))}. 2624 * </p><p> 2625 * The operation modifies this transform in a way that applying it to a node 2626 * has the same effect as adding two transforms to its 2627 * {@code getTransforms()} list, the specified scale first 2628 * and {@code this} transform second. 2629 * </p><p> 2630 * From the matrix point of view, the transformation matrix of this 2631 * transform is multiplied on the left by the transformation matrix of 2632 * the specified scale. 2633 * </p> 2634 * @param sx the X coordinate scale factor 2635 * @param sy the Y coordinate scale factor 2636 * @since JavaFX 8.0 2637 */ 2638 public void prependScale(double sx, double sy) { 2639 2640 atomicChange.start(); 2641 preScale2D(sx, sy); 2642 atomicChange.end(); 2643 } 2644 2645 /** 2646 * <p> 2647 * Prepends the 2D scale with pivot to this instance. 2648 * It is equivalent to {@code prepend(new Scale(sx, sy, pivotX, pivotY))}. 2649 * </p><p> 2650 * The operation modifies this transform in a way that applying it to a node 2651 * has the same effect as adding two transforms to its 2652 * {@code getTransforms()} list, the specified scale first 2653 * and {@code this} transform second. 2654 * </p><p> 2655 * From the matrix point of view, the transformation matrix of this 2656 * transform is multiplied on the left by the transformation matrix of 2657 * the specified scale. 2658 * </p> 2659 * @param sx the X coordinate scale factor 2660 * @param sy the Y coordinate scale factor 2661 * @param pivotX the X coordinate of the point about which the scale occurs 2662 * @param pivotY the Y coordinate of the point about which the scale occurs 2663 * @since JavaFX 8.0 2664 */ 2665 public void prependScale(double sx, double sy, 2666 double pivotX, double pivotY) { 2667 atomicChange.start(); 2668 if (pivotX != 0.0 || pivotY != 0.0) { 2669 preTranslate2D(-pivotX, -pivotY); 2670 preScale2D(sx, sy); 2671 preTranslate2D(pivotX, pivotY); 2672 } else { 2673 preScale2D(sx, sy); 2674 } 2675 atomicChange.end(); 2676 } 2677 2678 /** 2679 * <p> 2680 * Prepends the 2D scale with pivot to this instance. 2681 * It is equivalent to {@code prepend(new Scale(sx, sy, pivot.getX(), 2682 * </p>pivot.getY()))}. 2683 * </p><p> 2684 * The operation modifies this transform in a way that applying it to a node 2685 * has the same effect as adding two transforms to its 2686 * {@code getTransforms()} list, the specified scale first 2687 * and {@code this} transform second. 2688 * </p><p> 2689 * From the matrix point of view, the transformation matrix of this 2690 * transform is multiplied on the left by the transformation matrix of 2691 * the specified scale. 2692 * </p> 2693 * @param sx the X coordinate scale factor 2694 * @param sy the Y coordinate scale factor 2695 * @param pivot the point about which the scale occurs 2696 * @throws NullPointerException if the specified {@code pivot} is null 2697 * @since JavaFX 8.0 2698 */ 2699 public void prependScale(double sx, double sy, Point2D pivot) { 2700 prependScale(sx, sy, pivot.getX(), pivot.getY()); 2701 } 2702 2703 /** 2704 * <p> 2705 * Prepends the scale to this instance. 2706 * It is equivalent to {@code prepend(new Scale(sx, sy, sz))}. 2707 * </p><p> 2708 * The operation modifies this transform in a way that applying it to a node 2709 * has the same effect as adding two transforms to its 2710 * {@code getTransforms()} list, the specified scale first 2711 * and {@code this} transform second. 2712 * </p><p> 2713 * From the matrix point of view, the transformation matrix of this 2714 * transform is multiplied on the left by the transformation matrix of 2715 * the specified scale. 2716 * </p> 2717 * @param sx the X coordinate scale factor 2718 * @param sy the Y coordinate scale factor 2719 * @param sz the Z coordinate scale factor 2720 * @since JavaFX 8.0 2721 */ 2722 public void prependScale(double sx, double sy, double sz) { 2723 atomicChange.start(); 2724 preScale3D(sx, sy, sz); 2725 atomicChange.end(); 2726 } 2727 2728 /** 2729 * <p> 2730 * Prepends the scale with pivot to this instance. 2731 * It is equivalent to 2732 * {@code prepend(new Scale(sx, sy, sz, pivotX, pivotY, pivotZ))}. 2733 * </p><p> 2734 * The operation modifies this transform in a way that applying it to a node 2735 * has the same effect as adding two transforms to its 2736 * {@code getTransforms()} list, the specified scale first 2737 * and {@code this} transform second. 2738 * </p><p> 2739 * From the matrix point of view, the transformation matrix of this 2740 * transform is multiplied on the left by the transformation matrix of 2741 * the specified scale. 2742 * </p> 2743 * @param sx the X coordinate scale factor 2744 * @param sy the Y coordinate scale factor 2745 * @param sz the Z coordinate scale factor 2746 * @param pivotX the X coordinate of the point about which the scale occurs 2747 * @param pivotY the Y coordinate of the point about which the scale occurs 2748 * @param pivotZ the Z coordinate of the point about which the scale occurs 2749 * @since JavaFX 8.0 2750 */ 2751 public void prependScale(double sx, double sy, double sz, 2752 double pivotX, double pivotY, double pivotZ) { 2753 2754 atomicChange.start(); 2755 if (pivotX != 0.0 || pivotY != 0.0 || pivotZ != 0.0) { 2756 preTranslate3D(-pivotX, -pivotY, -pivotZ); 2757 preScale3D(sx, sy, sz); 2758 preTranslate3D(pivotX, pivotY, pivotZ); 2759 } else { 2760 preScale3D(sx, sy, sz); 2761 } 2762 atomicChange.end(); 2763 } 2764 2765 /** 2766 * <p> 2767 * Prepends the scale with pivot to this instance. 2768 * It is equivalent to {@code prepend(new Scale(sx, sy, sz, pivot.getX(), 2769 * pivot.getY(), pivot.getZ()))}. 2770 * </p><p> 2771 * The operation modifies this transform in a way that applying it to a node 2772 * has the same effect as adding two transforms to its 2773 * {@code getTransforms()} list, the specified scale first 2774 * and {@code this} transform second. 2775 * </p><p> 2776 * From the matrix point of view, the transformation matrix of this 2777 * transform is multiplied on the left by the transformation matrix of 2778 * the specified scale. 2779 * </p> 2780 * @param sx the X coordinate scale factor 2781 * @param sy the Y coordinate scale factor 2782 * @param sz the Z coordinate scale factor 2783 * @param pivot the point about which the scale occurs 2784 * @throws NullPointerException if the specified {@code pivot} is null 2785 * @since JavaFX 8.0 2786 */ 2787 public void prependScale(double sx, double sy, double sz, Point3D pivot) { 2788 prependScale(sx, sy, sz, pivot.getX(), pivot.getY(), pivot.getZ()); 2789 } 2790 2791 /** 2792 * 2D implementation of {@code prependScale()}. 2793 * If this is a 3D transform, the call is redirected to {@code preScale3D()}. 2794 */ 2795 private void preScale2D(double sx, double sy) { 2796 2797 if (state3d != APPLY_NON_3D) { 2798 preScale3D(sx, sy, 1.0); 2799 return; 2800 } 2801 2802 int mystate = state2d; 2803 switch (mystate) { 2804 default: 2805 stateError(); 2806 // cannot reach 2807 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 2808 setTx(getTx() * sx); 2809 setTy(getTy() * sy); 2810 if (getTx() == 0.0 && getTy() == 0.0) { 2811 mystate = mystate & ~APPLY_TRANSLATE; 2812 state2d = mystate; 2813 } 2814 // fall-through 2815 case APPLY_SHEAR | APPLY_SCALE: 2816 setMxx(getMxx() * sx); 2817 setMyy(getMyy() * sy); 2818 // fall-through 2819 case APPLY_SHEAR: 2820 setMxy(getMxy() * sx); 2821 setMyx(getMyx() * sy); 2822 if (getMxy() == 0.0 && getMyx() == 0.0) { 2823 mystate &= APPLY_TRANSLATE; 2824 if (getMxx() != 1.0 || getMyy() != 1.0) { 2825 mystate |= APPLY_SCALE; 2826 } 2827 state2d = mystate; 2828 } 2829 return; 2830 case APPLY_SHEAR | APPLY_TRANSLATE: 2831 setTx(getTx() * sx); 2832 setTy(getTy() * sy); 2833 setMxy(getMxy() * sx); 2834 setMyx(getMyx() * sy); 2835 if (getMxy() == 0.0 && getMyx() == 0.0) { 2836 if (getTx() == 0.0 && getTy() == 0.0) { 2837 state2d = APPLY_SCALE; 2838 } else { 2839 state2d = APPLY_SCALE | APPLY_TRANSLATE; 2840 } 2841 } else if (getTx() ==0.0 && getTy() == 0.0) { 2842 state2d = APPLY_SHEAR; 2843 } 2844 return; 2845 case APPLY_SCALE | APPLY_TRANSLATE: 2846 setTx(getTx() * sx); 2847 setTy(getTy() * sy); 2848 if (getTx() == 0.0 && getTy() == 0.0) { 2849 mystate = mystate & ~APPLY_TRANSLATE; 2850 state2d = mystate; 2851 } 2852 // fall-through 2853 case APPLY_SCALE: 2854 setMxx(getMxx() * sx); 2855 setMyy(getMyy() * sy); 2856 if (getMxx() == 1.0 && getMyy() == 1.0) { 2857 state2d = (mystate &= APPLY_TRANSLATE); 2858 } 2859 return; 2860 case APPLY_TRANSLATE: 2861 setTx(getTx() * sx); 2862 setTy(getTy() * sy); 2863 if (getTx() == 0.0 && getTy() == 0.0) { 2864 mystate = mystate & ~APPLY_TRANSLATE; 2865 state2d = mystate; 2866 } 2867 // fall-through 2868 case APPLY_IDENTITY: 2869 setMxx(sx); 2870 setMyy(sy); 2871 if (sx != 1.0 || sy != 1.0) { 2872 state2d = mystate | APPLY_SCALE; 2873 } 2874 return; 2875 } 2876 } 2877 2878 /** 2879 * 3D implementation of {@code prependScale()}. 2880 * Works fine if this is a 2D transform. 2881 */ 2882 private void preScale3D(double sx, double sy, double sz) { 2883 switch (state3d) { 2884 default: 2885 stateError(); 2886 // cannot reach 2887 case APPLY_NON_3D: 2888 preScale2D(sx, sy); 2889 if (sz != 1.0) { 2890 setMzz(sz); 2891 if ((state2d & APPLY_SHEAR) == 0) { 2892 state3d = (state2d & APPLY_TRANSLATE) | APPLY_SCALE; 2893 } else { 2894 state3d = APPLY_3D_COMPLEX; 2895 } 2896 } 2897 return; 2898 case APPLY_SCALE | APPLY_TRANSLATE: 2899 setTx(getTx() * sx); 2900 setTy(getTy() * sy); 2901 setTz(getTz() * sz); 2902 setMxx(getMxx() * sx); 2903 setMyy(getMyy() * sy); 2904 setMzz(getMzz() * sz); 2905 if (getTx() == 0.0 && getTy() == 0.0 && getTz() == 0.0) { 2906 state3d &= ~APPLY_TRANSLATE; 2907 } 2908 if (getMxx() == 1.0 && getMyy() == 1.0 && getMzz() == 1.0) { 2909 state3d &= ~APPLY_SCALE; 2910 } 2911 if (getTz() == 0.0 && getMzz() == 1.0) { 2912 state2d = state3d; 2913 state3d = APPLY_NON_3D; 2914 } 2915 return; 2916 case APPLY_SCALE: 2917 setMxx(getMxx() * sx); 2918 setMyy(getMyy() * sy); 2919 setMzz(getMzz() * sz); 2920 if (getMzz() == 1.0) { 2921 state3d = APPLY_NON_3D; 2922 if (getMxx() == 1.0 && getMyy() == 1.0) { 2923 state2d = APPLY_IDENTITY; 2924 } else { 2925 state2d = APPLY_SCALE; 2926 } 2927 } 2928 return; 2929 case APPLY_TRANSLATE: 2930 setTx(getTx() * sx); 2931 setTy(getTy() * sy); 2932 setTz(getTz() * sz); 2933 setMxx(sx); 2934 setMyy(sy); 2935 setMzz(sz); 2936 if (getTx() == 0.0 && getTy() == 0.0 && getTz() == 0.0) { 2937 state3d &= ~APPLY_TRANSLATE; 2938 } 2939 if (sx != 1.0 || sy != 1.0 || sz != 1.0) { 2940 state3d |= APPLY_SCALE; 2941 } 2942 return; 2943 case APPLY_3D_COMPLEX: 2944 setMxx(getMxx() * sx); 2945 setMxy(getMxy() * sx); 2946 setMxz(getMxz() * sx); 2947 setTx(getTx() * sx); 2948 2949 setMyx(getMyx() * sy); 2950 setMyy(getMyy() * sy); 2951 setMyz(getMyz() * sy); 2952 setTy(getTy() * sy); 2953 2954 setMzx(getMzx() * sz); 2955 setMzy(getMzy() * sz); 2956 setMzz(getMzz() * sz); 2957 setTz(getTz() * sz); 2958 2959 if (sx == 0.0 || sy == 0.0 || sz == 0.0) { 2960 updateState(); 2961 } // otherwise state remains COMPLEX 2962 return; 2963 } 2964 } 2965 2966 /* ************************************************* 2967 * Shear * 2968 **************************************************/ 2969 2970 /** 2971 * <p> 2972 * Appends the shear to this instance. 2973 * It is equivalent to {@code append(new Shear(sx, sy))}. 2974 * </p><p> 2975 * The operation modifies this transform in a way that applying it to a node 2976 * has the same effect as adding two transforms to its 2977 * {@code getTransforms()} list, {@code this} transform first and the specified 2978 * shear second. 2979 * </p><p> 2980 * From the matrix point of view, the transformation matrix of this 2981 * transform is multiplied on the right by the transformation matrix of 2982 * the specified shear. 2983 * </p> 2984 * @param shx the XY coordinate element 2985 * @param shy the YX coordinate element 2986 * @since JavaFX 8.0 2987 */ 2988 public void appendShear(double shx, double shy) { 2989 atomicChange.start(); 2990 shear2D(shx, shy); 2991 atomicChange.end(); 2992 } 2993 2994 /** 2995 * <p> 2996 * Appends the shear with pivot to this instance. 2997 * It is equivalent to {@code append(new Shear(sx, sy, pivotX, pivotY))}. 2998 * </p><p> 2999 * The operation modifies this transform in a way that applying it to a node 3000 * has the same effect as adding two transforms to its 3001 * {@code getTransforms()} list, {@code this} transform first and the specified 3002 * shear second. 3003 * </p><p> 3004 * From the matrix point of view, the transformation matrix of this 3005 * transform is multiplied on the right by the transformation matrix of 3006 * the specified shear. 3007 * </p> 3008 * @param shx the XY coordinate element 3009 * @param shy the YX coordinate element 3010 * @param pivotX the X coordinate of the shear pivot point 3011 * @param pivotY the Y coordinate of the shear pivot point 3012 * @since JavaFX 8.0 3013 */ 3014 public void appendShear(double shx, double shy, 3015 double pivotX, double pivotY) { 3016 atomicChange.start(); 3017 if (pivotX != 0.0 || pivotY != 0.0) { 3018 translate2D(pivotX, pivotY); 3019 shear2D(shx, shy); 3020 translate2D(-pivotX, -pivotY); 3021 } else { 3022 shear2D(shx, shy); 3023 } 3024 atomicChange.end(); 3025 } 3026 3027 /** 3028 * <p> 3029 * Appends the shear with pivot to this instance. 3030 * It is equivalent to {@code append(new Shear(sx, sy, 3031 * pivot.getX(), pivot.getY()))}. 3032 * </p><p> 3033 * The operation modifies this transform in a way that applying it to a node 3034 * has the same effect as adding two transforms to its 3035 * {@code getTransforms()} list, {@code this} transform first and the specified 3036 * shear second. 3037 * </p><p> 3038 * From the matrix point of view, the transformation matrix of this 3039 * transform is multiplied on the right by the transformation matrix of 3040 * the specified shear. 3041 * </p> 3042 * @param shx the XY coordinate element 3043 * @param shy the YX coordinate element 3044 * @param pivot the shear pivot point 3045 * @throws NullPointerException if the specified {@code pivot} is null 3046 * @since JavaFX 8.0 3047 */ 3048 public void appendShear(double shx, double shy, Point2D pivot) { 3049 appendShear(shx, shy, pivot.getX(), pivot.getY()); 3050 } 3051 3052 /** 3053 * 2D implementation of {@code appendShear()}. 3054 * If this is a 3D transform, the call is redirected to {@code shear3D()}. 3055 */ 3056 private void shear2D(double shx, double shy) { 3057 3058 if (state3d != APPLY_NON_3D) { 3059 shear3D(shx, shy); 3060 return; 3061 } 3062 3063 int mystate = state2d; 3064 switch (mystate) { 3065 default: 3066 stateError(); 3067 // cannot reach 3068 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 3069 case APPLY_SHEAR | APPLY_SCALE: 3070 double M0, M1; 3071 M0 = getMxx(); 3072 M1 = getMxy(); 3073 setMxx(M0 + M1 * shy); 3074 setMxy(M0 * shx + M1); 3075 3076 M0 = getMyx(); 3077 M1 = getMyy(); 3078 setMyx(M0 + M1 * shy); 3079 setMyy(M0 * shx + M1); 3080 updateState2D(); 3081 return; 3082 case APPLY_SHEAR | APPLY_TRANSLATE: 3083 case APPLY_SHEAR: 3084 setMxx(getMxy() * shy); 3085 setMyy(getMyx() * shx); 3086 if (getMxx() != 0.0 || getMyy() != 0.0) { 3087 state2d = mystate | APPLY_SCALE; 3088 } 3089 return; 3090 case APPLY_SCALE | APPLY_TRANSLATE: 3091 case APPLY_SCALE: 3092 setMxy(getMxx() * shx); 3093 setMyx(getMyy() * shy); 3094 if (getMxy() != 0.0 || getMyx() != 0.0) { 3095 state2d = mystate | APPLY_SHEAR; 3096 } 3097 return; 3098 case APPLY_TRANSLATE: 3099 case APPLY_IDENTITY: 3100 setMxy(shx); 3101 setMyx(shy); 3102 if (getMxy() != 0.0 || getMyx() != 0.0) { 3103 state2d = mystate | APPLY_SCALE | APPLY_SHEAR; 3104 } 3105 return; 3106 } 3107 } 3108 3109 /** 3110 * 3D implementation of {@code appendShear()}. 3111 * Works fine if this is a 2D transform. 3112 */ 3113 private void shear3D(double shx, double shy) { 3114 switch (state3d) { 3115 default: 3116 stateError(); 3117 // cannot reach 3118 case APPLY_NON_3D: 3119 // cannot happen because currently there is no 3D appendShear 3120 // that would call this method directly 3121 shear2D(shx, shy); 3122 return; 3123 case APPLY_TRANSLATE: 3124 setMxy(shx); 3125 setMyx(shy); 3126 if (shx != 0.0 || shy != 0.0) { 3127 state3d = APPLY_3D_COMPLEX; 3128 } 3129 return; 3130 case APPLY_SCALE: 3131 case APPLY_SCALE | APPLY_TRANSLATE: 3132 setMxy(getMxx() * shx); 3133 setMyx(getMyy() * shy); 3134 if (getMxy() != 0.0 || getMyx() != 0.0) { 3135 state3d = APPLY_3D_COMPLEX; 3136 } 3137 return; 3138 case APPLY_3D_COMPLEX: 3139 final double m_xx = getMxx(); 3140 final double m_xy = getMxy(); 3141 final double m_yx = getMyx(); 3142 final double m_yy = getMyy(); 3143 final double m_zx = getMzx(); 3144 final double m_zy = getMzy(); 3145 3146 setMxx(m_xx + m_xy * shy); 3147 setMxy(m_xy + m_xx * shx); 3148 setMyx(m_yx + m_yy * shy); 3149 setMyy(m_yy + m_yx * shx); 3150 setMzx(m_zx + m_zy * shy); 3151 setMzy(m_zy + m_zx * shx); 3152 updateState(); 3153 return; 3154 } 3155 } 3156 3157 /** 3158 * <p> 3159 * Prepends the shear to this instance. 3160 * It is equivalent to {@code prepend(new Shear(sx, sy))}. 3161 * </p><p> 3162 * The operation modifies this transform in a way that applying it to a node 3163 * has the same effect as adding two transforms to its 3164 * {@code getTransforms()} list, the specified shear first 3165 * and {@code this} transform second. 3166 * </p><p> 3167 * From the matrix point of view, the transformation matrix of this 3168 * transform is multiplied on the left by the transformation matrix of 3169 * the specified shear. 3170 * </p> 3171 * @param shx the XY coordinate element 3172 * @param shy the YX coordinate element 3173 * @since JavaFX 8.0 3174 */ 3175 public void prependShear(double shx, double shy) { 3176 atomicChange.start(); 3177 preShear2D(shx, shy); 3178 atomicChange.end(); 3179 } 3180 3181 /** 3182 * <p> 3183 * Prepends the shear with pivot to this instance. 3184 * It is equivalent to {@code prepend(new Shear(sx, sy, pivotX, pivotY))}. 3185 * </p><p> 3186 * The operation modifies this transform in a way that applying it to a node 3187 * has the same effect as adding two transforms to its 3188 * {@code getTransforms()} list, the specified shear first 3189 * and {@code this} transform second. 3190 * </p><p> 3191 * From the matrix point of view, the transformation matrix of this 3192 * transform is multiplied on the left by the transformation matrix of 3193 * the specified shear. 3194 * </p> 3195 * @param shx the XY coordinate element 3196 * @param shy the YX coordinate element 3197 * @param pivotX the X coordinate of the shear pivot point 3198 * @param pivotY the Y coordinate of the shear pivot point 3199 * @since JavaFX 8.0 3200 */ 3201 public void prependShear(double shx, double shy, 3202 double pivotX, double pivotY) { 3203 atomicChange.start(); 3204 if (pivotX != 0.0 || pivotY != 0.0) { 3205 preTranslate2D(-pivotX, -pivotY); 3206 preShear2D(shx, shy); 3207 preTranslate2D(pivotX, pivotY); 3208 } else { 3209 preShear2D(shx, shy); 3210 } 3211 atomicChange.end(); 3212 } 3213 3214 /** 3215 * <p> 3216 * Prepends the shear with pivot to this instance. 3217 * It is equivalent to {@code prepend(new Shear(sx, sy, pivot.getX(), 3218 * pivot.getY()))}. 3219 * </p><p> 3220 * The operation modifies this transform in a way that applying it to a node 3221 * has the same effect as adding two transforms to its 3222 * {@code getTransforms()} list, the specified shear first 3223 * and {@code this} transform second. 3224 * </p><p> 3225 * From the matrix point of view, the transformation matrix of this 3226 * transform is multiplied on the left by the transformation matrix of 3227 * the specified shear. 3228 * </p> 3229 * @param shx the XY coordinate element 3230 * @param shy the YX coordinate element 3231 * @param pivot the shear pivot point 3232 * @throws NullPointerException if the specified {@code pivot} is null 3233 * @since JavaFX 8.0 3234 */ 3235 public void prependShear(double shx, double shy, Point2D pivot) { 3236 prependShear(shx, shy, pivot.getX(), pivot.getY()); 3237 } 3238 3239 /** 3240 * 2D implementation of {@code prependShear()}. 3241 * If this is a 3D transform, the call is redirected to {@code preShear3D()}. 3242 */ 3243 private void preShear2D(double shx, double shy) { 3244 3245 if (state3d != APPLY_NON_3D) { 3246 preShear3D(shx, shy); 3247 return; 3248 } 3249 3250 int mystate = state2d; 3251 3252 switch (mystate) { 3253 default: 3254 stateError(); 3255 // cannot reach 3256 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 3257 case APPLY_SHEAR | APPLY_TRANSLATE: 3258 final double t_x_1 = getTx(); 3259 final double t_y_1 = getTy(); 3260 setTx(t_x_1 + shx * t_y_1); 3261 setTy(t_y_1 + shy * t_x_1); 3262 // fall-through 3263 case APPLY_SHEAR | APPLY_SCALE: 3264 case APPLY_SHEAR: 3265 final double m_xx = getMxx(); 3266 final double m_xy = getMxy(); 3267 final double m_yx = getMyx(); 3268 final double m_yy = getMyy(); 3269 3270 setMxx(m_xx + shx * m_yx); 3271 setMxy(m_xy + shx * m_yy); 3272 setMyx(shy * m_xx + m_yx); 3273 setMyy(shy * m_xy + m_yy); 3274 updateState2D(); 3275 return; 3276 case APPLY_SCALE | APPLY_TRANSLATE: 3277 final double t_x_2 = getTx(); 3278 final double t_y_2 = getTy(); 3279 setTx(t_x_2 + shx * t_y_2); 3280 setTy(t_y_2 + shy * t_x_2); 3281 if (getTx() == 0.0 && getTy() == 0.0) { 3282 mystate = mystate & ~APPLY_TRANSLATE; 3283 state2d = mystate; 3284 } 3285 // fall-through 3286 case APPLY_SCALE: 3287 setMxy(shx * getMyy()); 3288 setMyx(shy * getMxx()); 3289 if (getMxy() != 0.0 || getMyx() != 0.0) { 3290 state2d = mystate | APPLY_SHEAR; 3291 } 3292 return; 3293 case APPLY_TRANSLATE: 3294 final double t_x_3 = getTx(); 3295 final double t_y_3 = getTy(); 3296 setTx(t_x_3 + shx * t_y_3); 3297 setTy(t_y_3 + shy * t_x_3); 3298 if (getTx() == 0.0 && getTy() == 0.0) { 3299 mystate = mystate & ~APPLY_TRANSLATE; 3300 state2d = mystate; 3301 } 3302 // fall-through 3303 case APPLY_IDENTITY: 3304 setMxy(shx); 3305 setMyx(shy); 3306 if (getMxy() != 0.0 || getMyx() != 0.0) { 3307 state2d = mystate | APPLY_SCALE | APPLY_SHEAR; 3308 } 3309 return; 3310 } 3311 } 3312 3313 /** 3314 * 3D implementation of {@code prependShear()}. 3315 * Works fine if this is a 2D transform. 3316 */ 3317 private void preShear3D(double shx, double shy) { 3318 3319 switch (state3d) { 3320 default: 3321 stateError(); 3322 // cannot reach 3323 case APPLY_NON_3D: 3324 // cannot happen because currently there is no 3D prependShear 3325 // that would call this method directly 3326 preShear2D(shx, shy); 3327 return; 3328 case APPLY_TRANSLATE: 3329 final double tx_t = getTx(); 3330 setMxy(shx); 3331 setTx(tx_t + getTy() * shx); 3332 setMyx(shy); 3333 setTy(tx_t * shy + getTy()); 3334 3335 if (shx != 0.0 || shy != 0.0) { 3336 state3d = APPLY_3D_COMPLEX; 3337 } 3338 return; 3339 case APPLY_SCALE: 3340 setMxy(getMyy() * shx); 3341 setMyx(getMxx() * shy); 3342 3343 if (getMxy() != 0.0 || getMyx() != 0.0) { 3344 state3d = APPLY_3D_COMPLEX; 3345 } 3346 return; 3347 case APPLY_SCALE | APPLY_TRANSLATE: 3348 final double tx_st = getTx(); 3349 setMxy(getMyy() * shx); 3350 setTx(tx_st + getTy() * shx); 3351 setMyx(getMxx() * shy); 3352 setTy(tx_st * shy + getTy()); 3353 3354 if (getMxy() != 0.0 || getMyx() != 0.0) { 3355 state3d = APPLY_3D_COMPLEX; 3356 } 3357 return; 3358 case APPLY_3D_COMPLEX: 3359 3360 final double m_xx = getMxx(); 3361 final double m_xy = getMxy(); 3362 final double m_yx = getMyx(); 3363 final double t_x = getTx(); 3364 final double m_yy = getMyy(); 3365 final double m_xz = getMxz(); 3366 final double m_yz = getMyz(); 3367 final double t_y = getTy(); 3368 3369 setMxx(m_xx + m_yx * shx); 3370 setMxy(m_xy + m_yy * shx); 3371 setMxz(m_xz + m_yz * shx); 3372 setTx(t_x + t_y * shx); 3373 setMyx(m_xx * shy + m_yx); 3374 setMyy(m_xy * shy + m_yy); 3375 setMyz(m_xz * shy + m_yz); 3376 setTy(t_x * shy + t_y); 3377 3378 updateState(); 3379 return; 3380 } 3381 } 3382 3383 /* ************************************************* 3384 * Rotate * 3385 **************************************************/ 3386 3387 /** 3388 * <p> 3389 * Appends the 2D rotation to this instance. 3390 * It is equivalent to {@code append(new Rotate(angle))}. 3391 * </p><p> 3392 * The operation modifies this transform in a way that applying it to a node 3393 * has the same effect as adding two transforms to its 3394 * {@code getTransforms()} list, {@code this} transform first and the specified 3395 * rotation second. 3396 * </p><p> 3397 * From the matrix point of view, the transformation matrix of this 3398 * transform is multiplied on the right by the transformation matrix of 3399 * the specified rotation. 3400 * </p> 3401 * @param angle the angle of the rotation in degrees 3402 * @since JavaFX 8.0 3403 */ 3404 public void appendRotation(double angle) { 3405 atomicChange.start(); 3406 rotate2D(angle); 3407 atomicChange.end(); 3408 } 3409 3410 /** 3411 * <p> 3412 * Appends the 2D rotation with pivot to this instance. 3413 * It is equivalent to {@code append(new Rotate(angle, pivotX, pivotY))}. 3414 * </p><p> 3415 * The operation modifies this transform in a way that applying it to a node 3416 * has the same effect as adding two transforms to its 3417 * {@code getTransforms()} list, {@code this} transform first and the specified 3418 * rotation second. 3419 * </p><p> 3420 * From the matrix point of view, the transformation matrix of this 3421 * transform is multiplied on the right by the transformation matrix of 3422 * the specified rotation. 3423 * </p> 3424 * @param angle the angle of the rotation in degrees 3425 * @param pivotX the X coordinate of the rotation pivot point 3426 * @param pivotY the Y coordinate of the rotation pivot point 3427 * @since JavaFX 8.0 3428 */ 3429 public void appendRotation(double angle, 3430 double pivotX, double pivotY) { 3431 atomicChange.start(); 3432 if (pivotX != 0.0 || pivotY != 0.0) { 3433 translate2D(pivotX, pivotY); 3434 rotate2D(angle); 3435 translate2D(-pivotX, -pivotY); 3436 } else { 3437 rotate2D(angle); 3438 } 3439 atomicChange.end(); 3440 } 3441 3442 /** 3443 * <p> 3444 * Appends the 2D rotation with pivot to this instance. 3445 * It is equivalent to {@code append(new Rotate(angle, pivot.getX(), 3446 * pivot.getY()))}. 3447 * </p><p> 3448 * The operation modifies this transform in a way that applying it to a node 3449 * has the same effect as adding two transforms to its 3450 * {@code getTransforms()} list, {@code this} transform first and the specified 3451 * rotation second. 3452 * </p><p> 3453 * From the matrix point of view, the transformation matrix of this 3454 * transform is multiplied on the right by the transformation matrix of 3455 * the specified rotation. 3456 * </p> 3457 * @param angle the angle of the rotation in degrees 3458 * @param pivot the rotation pivot point 3459 * @throws NullPointerException if the specified {@code pivot} is null 3460 * @since JavaFX 8.0 3461 */ 3462 public void appendRotation(double angle, Point2D pivot) { 3463 appendRotation(angle, pivot.getX(), pivot.getY()); 3464 } 3465 3466 /** 3467 * <p> 3468 * Appends the rotation to this instance. 3469 * It is equivalent to {@code append(new Rotate(angle, pivotX, pivotY, 3470 * pivotZ, new Point3D(axisX, axisY, axisZ)))}. 3471 * </p><p> 3472 * The operation modifies this transform in a way that applying it to a node 3473 * has the same effect as adding two transforms to its 3474 * {@code getTransforms()} list, {@code this} transform first and the specified 3475 * rotation second. 3476 * </p><p> 3477 * From the matrix point of view, the transformation matrix of this 3478 * transform is multiplied on the right by the transformation matrix of 3479 * the specified rotation. 3480 * </p> 3481 * @param angle the angle of the rotation in degrees 3482 * @param pivotX the X coordinate of the rotation pivot point 3483 * @param pivotY the Y coordinate of the rotation pivot point 3484 * @param pivotZ the Z coordinate of the rotation pivot point 3485 * @param axisX the X coordinate magnitude of the rotation axis 3486 * @param axisY the Y coordinate magnitude of the rotation axis 3487 * @param axisZ the Z coordinate magnitude of the rotation axis 3488 * @since JavaFX 8.0 3489 */ 3490 public void appendRotation(double angle, 3491 double pivotX, double pivotY, double pivotZ, 3492 double axisX, double axisY, double axisZ) { 3493 atomicChange.start(); 3494 if (pivotX != 0.0 || pivotY != 0.0 || pivotZ != 0.0) { 3495 translate3D(pivotX, pivotY, pivotZ); 3496 rotate3D(angle, axisX, axisY, axisZ); 3497 translate3D(-pivotX, -pivotY, -pivotZ); 3498 } else { 3499 rotate3D(angle, axisX, axisY, axisZ); 3500 } 3501 atomicChange.end(); 3502 } 3503 3504 /** 3505 * <p> 3506 * Appends the rotation to this instance. 3507 * It is equivalent to {@code append(new Rotate(angle, pivotX, pivotY, 3508 * pivotZ, axis))}. 3509 * </p><p> 3510 * The operation modifies this transform in a way that applying it to a node 3511 * has the same effect as adding two transforms to its 3512 * {@code getTransforms()} list, {@code this} transform first and the specified 3513 * rotation second. 3514 * </p><p> 3515 * From the matrix point of view, the transformation matrix of this 3516 * transform is multiplied on the right by the transformation matrix of 3517 * the specified rotation. 3518 * </p> 3519 * @param angle the angle of the rotation in degrees 3520 * @param pivotX the X coordinate of the rotation pivot point 3521 * @param pivotY the Y coordinate of the rotation pivot point 3522 * @param pivotZ the Z coordinate of the rotation pivot point 3523 * @param axis the rotation axis 3524 * @throws NullPointerException if the specified {@code axis} is null 3525 * @since JavaFX 8.0 3526 */ 3527 public void appendRotation(double angle, 3528 double pivotX, double pivotY, double pivotZ, 3529 Point3D axis) { 3530 appendRotation(angle, pivotX, pivotY, pivotZ, 3531 axis.getX(), axis.getY(), axis.getZ()); 3532 } 3533 3534 /** 3535 * <p> 3536 * Appends the rotation to this instance. 3537 * It is equivalent to {@code append(new Rotate(angle, pivot.getX(), 3538 * pivot.getY(), pivot.getZ(), axis))}. 3539 * </p><p> 3540 * The operation modifies this transform in a way that applying it to a node 3541 * has the same effect as adding two transforms to its 3542 * {@code getTransforms()} list, {@code this} transform first and the specified 3543 * rotation second. 3544 * </p><p> 3545 * From the matrix point of view, the transformation matrix of this 3546 * transform is multiplied on the right by the transformation matrix of 3547 * the specified rotation. 3548 * </p> 3549 * @param angle the angle of the rotation in degrees 3550 * @param pivot the rotation pivot point 3551 * @param axis the rotation axis 3552 * @throws NullPointerException if the specified {@code pivot} 3553 * or {@code axis} is null 3554 * @since JavaFX 8.0 3555 */ 3556 public void appendRotation(double angle, Point3D pivot, Point3D axis) { 3557 appendRotation(angle, pivot.getX(), pivot.getY(), pivot.getZ(), 3558 axis.getX(), axis.getY(), axis.getZ()); 3559 } 3560 3561 /** 3562 * Implementation of the {@code appendRotation()} around an arbitrary axis. 3563 */ 3564 private void rotate3D(double angle, double axisX, double axisY, double axisZ) { 3565 if (axisX == 0.0 && axisY == 0.0) { 3566 if (axisZ > 0.0) { 3567 rotate3D(angle); 3568 } else if (axisZ < 0.0) { 3569 rotate3D(-angle); 3570 } // else rotating about zero vector - NOP 3571 return; 3572 } 3573 3574 double mag = Math.sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ); 3575 3576 if (mag == 0.0) { 3577 return; 3578 } 3579 3580 mag = 1.0 / mag; 3581 final double ax = axisX * mag; 3582 final double ay = axisY * mag; 3583 final double az = axisZ * mag; 3584 3585 final double sinTheta = Math.sin(Math.toRadians(angle)); 3586 final double cosTheta = Math.cos(Math.toRadians(angle)); 3587 final double t = 1.0 - cosTheta; 3588 3589 final double xz = ax * az; 3590 final double xy = ax * ay; 3591 final double yz = ay * az; 3592 3593 final double Txx = t * ax * ax + cosTheta; 3594 final double Txy = t * xy - sinTheta * az; 3595 final double Txz = t * xz + sinTheta * ay; 3596 3597 final double Tyx = t * xy + sinTheta * az; 3598 final double Tyy = t * ay * ay + cosTheta; 3599 final double Tyz = t * yz - sinTheta * ax; 3600 3601 final double Tzx = t * xz - sinTheta * ay; 3602 final double Tzy = t * yz + sinTheta * ax; 3603 final double Tzz = t * az * az + cosTheta; 3604 3605 switch (state3d) { 3606 default: 3607 stateError(); 3608 // cannot reach 3609 case APPLY_NON_3D: 3610 switch (state2d) { 3611 default: 3612 stateError(); 3613 // cannot reach 3614 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 3615 case APPLY_SHEAR | APPLY_SCALE: 3616 final double xx_sst = getMxx(); 3617 final double xy_sst = getMxy(); 3618 final double yx_sst = getMyx(); 3619 final double yy_sst = getMyy(); 3620 setMxx(xx_sst * Txx + xy_sst * Tyx); 3621 setMxy(xx_sst * Txy + xy_sst * Tyy); 3622 setMxz(xx_sst * Txz + xy_sst * Tyz); 3623 setMyx(yx_sst * Txx + yy_sst * Tyx); 3624 setMyy(yx_sst * Txy + yy_sst * Tyy); 3625 setMyz(yx_sst * Txz + yy_sst * Tyz); 3626 setMzx(Tzx); 3627 setMzy(Tzy); 3628 setMzz(Tzz); 3629 break; 3630 case APPLY_SHEAR | APPLY_TRANSLATE: 3631 case APPLY_SHEAR: 3632 final double xy_sht = getMxy(); 3633 final double yx_sht = getMyx(); 3634 setMxx(xy_sht * Tyx); 3635 setMxy(xy_sht * Tyy); 3636 setMxz(xy_sht * Tyz); 3637 setMyx(yx_sht * Txx); 3638 setMyy(yx_sht * Txy); 3639 setMyz(yx_sht * Txz); 3640 setMzx(Tzx); 3641 setMzy(Tzy); 3642 setMzz(Tzz); 3643 break; 3644 case APPLY_SCALE | APPLY_TRANSLATE: 3645 case APPLY_SCALE: 3646 final double xx_s = getMxx(); 3647 final double yy_s = getMyy(); 3648 setMxx(xx_s * Txx); 3649 setMxy(xx_s * Txy); 3650 setMxz(xx_s * Txz); 3651 setMyx(yy_s * Tyx); 3652 setMyy(yy_s * Tyy); 3653 setMyz(yy_s * Tyz); 3654 setMzx(Tzx); 3655 setMzy(Tzy); 3656 setMzz(Tzz); 3657 break; 3658 case APPLY_TRANSLATE: 3659 case APPLY_IDENTITY: 3660 setMxx(Txx); 3661 setMxy(Txy); 3662 setMxz(Txz); 3663 setMyx(Tyx); 3664 setMyy(Tyy); 3665 setMyz(Tyz); 3666 setMzx(Tzx); 3667 setMzy(Tzy); 3668 setMzz(Tzz); 3669 break; 3670 } 3671 break; 3672 case APPLY_TRANSLATE: 3673 setMxx(Txx); 3674 setMxy(Txy); 3675 setMxz(Txz); 3676 setMyx(Tyx); 3677 setMyy(Tyy); 3678 setMyz(Tyz); 3679 setMzx(Tzx); 3680 setMzy(Tzy); 3681 setMzz(Tzz); 3682 break; 3683 case APPLY_SCALE: 3684 case APPLY_SCALE | APPLY_TRANSLATE: 3685 final double xx_st = getMxx(); 3686 final double yy_st = getMyy(); 3687 final double zz_st = getMzz(); 3688 setMxx(xx_st * Txx); 3689 setMxy(xx_st * Txy); 3690 setMxz(xx_st * Txz); 3691 setMyx(yy_st * Tyx); 3692 setMyy(yy_st * Tyy); 3693 setMyz(yy_st * Tyz); 3694 setMzx(zz_st * Tzx); 3695 setMzy(zz_st * Tzy); 3696 setMzz(zz_st * Tzz); 3697 break; 3698 case APPLY_3D_COMPLEX: 3699 final double m_xx = getMxx(); 3700 final double m_xy = getMxy(); 3701 final double m_xz = getMxz(); 3702 final double m_yx = getMyx(); 3703 final double m_yy = getMyy(); 3704 final double m_yz = getMyz(); 3705 final double m_zx = getMzx(); 3706 final double m_zy = getMzy(); 3707 final double m_zz = getMzz(); 3708 setMxx(m_xx * Txx + m_xy * Tyx + m_xz * Tzx /* + mxt * 0.0 */); 3709 setMxy(m_xx * Txy + m_xy * Tyy + m_xz * Tzy /* + mxt * 0.0 */); 3710 setMxz(m_xx * Txz + m_xy * Tyz + m_xz * Tzz /* + mxt * 0.0 */); 3711 setMyx(m_yx * Txx + m_yy * Tyx + m_yz * Tzx /* + myt * 0.0 */); 3712 setMyy(m_yx * Txy + m_yy * Tyy + m_yz * Tzy /* + myt * 0.0 */); 3713 setMyz(m_yx * Txz + m_yy * Tyz + m_yz * Tzz /* + myt * 0.0 */); 3714 setMzx(m_zx * Txx + m_zy * Tyx + m_zz * Tzx /* + mzt * 0.0 */); 3715 setMzy(m_zx * Txy + m_zy * Tyy + m_zz * Tzy /* + mzt * 0.0 */); 3716 setMzz(m_zx * Txz + m_zy * Tyz + m_zz * Tzz /* + mzt * 0.0 */); 3717 break; 3718 } 3719 updateState(); 3720 } 3721 3722 /** 3723 * Table of 2D state changes during predictable quadrant rotations where 3724 * the shear and scaleAffine values are swapped and negated. 3725 */ 3726 private static final int rot90conversion[] = { 3727 /* IDENTITY => */ APPLY_SHEAR, 3728 /* TRANSLATE (TR) => */ APPLY_SHEAR | APPLY_TRANSLATE, 3729 /* SCALE (SC) => */ APPLY_SHEAR, 3730 /* SC | TR => */ APPLY_SHEAR | APPLY_TRANSLATE, 3731 /* SHEAR (SH) => */ APPLY_SCALE, 3732 /* SH | TR => */ APPLY_SCALE | APPLY_TRANSLATE, 3733 /* SH | SC => */ APPLY_SHEAR | APPLY_SCALE, 3734 /* SH | SC | TR => */ APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE, 3735 }; 3736 3737 /** 3738 * 2D implementation of {@code appendRotation}. 3739 * If this is a 3D transform, the call is redirected to {@code rotate3D()}. 3740 */ 3741 private void rotate2D(double theta) { 3742 if (state3d != APPLY_NON_3D) { 3743 rotate3D(theta); 3744 return; 3745 } 3746 3747 double sin = Math.sin(Math.toRadians(theta)); 3748 if (sin == 1.0) { 3749 rotate2D_90(); 3750 } else if (sin == -1.0) { 3751 rotate2D_270(); 3752 } else { 3753 double cos = Math.cos(Math.toRadians(theta)); 3754 if (cos == -1.0) { 3755 rotate2D_180(); 3756 } else if (cos != 1.0) { 3757 double M0, M1; 3758 M0 = getMxx(); 3759 M1 = getMxy(); 3760 setMxx(cos * M0 + sin * M1); 3761 setMxy(-sin * M0 + cos * M1); 3762 M0 = getMyx(); 3763 M1 = getMyy(); 3764 setMyx(cos * M0 + sin * M1); 3765 setMyy(-sin * M0 + cos * M1); 3766 updateState2D(); 3767 } 3768 } 3769 } 3770 3771 /** 3772 * 2D implementation of {@code appendRotation} for 90 degrees rotation 3773 * around Z axis. 3774 * Behaves wrong when called for a 3D transform. 3775 */ 3776 private void rotate2D_90() { 3777 double M0 = getMxx(); 3778 setMxx(getMxy()); 3779 setMxy(-M0); 3780 M0 = getMyx(); 3781 setMyx(getMyy()); 3782 setMyy(-M0); 3783 int newstate = rot90conversion[state2d]; 3784 if ((newstate & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SCALE && 3785 getMxx() == 1.0 && getMyy() == 1.0) { 3786 newstate -= APPLY_SCALE; 3787 } else if ((newstate & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SHEAR && 3788 getMxy() == 0.0 && getMyx() == 0.0) { 3789 newstate = (newstate & ~APPLY_SHEAR | APPLY_SCALE); 3790 } 3791 state2d = newstate; 3792 } 3793 3794 /** 3795 * 2D implementation of {@code appendRotation} for 180 degrees rotation 3796 * around Z axis. 3797 * Behaves wrong when called for a 3D transform. 3798 */ 3799 private void rotate2D_180() { 3800 setMxx(-getMxx()); 3801 setMyy(-getMyy()); 3802 int oldstate = state2d; 3803 if ((oldstate & (APPLY_SHEAR)) != 0) { 3804 // If there was a shear, then this rotation has no 3805 // effect on the state. 3806 setMxy(-getMxy()); 3807 setMyx(-getMyx()); 3808 } else { 3809 // No shear means the SCALE state may toggle when 3810 // m00 and m11 are negated. 3811 if (getMxx() == 1.0 && getMyy() == 1.0) { 3812 state2d = oldstate & ~APPLY_SCALE; 3813 } else { 3814 state2d = oldstate | APPLY_SCALE; 3815 } 3816 } 3817 } 3818 3819 /** 3820 * 2D implementation of {@code appendRotation} for 270 degrees rotation 3821 * around Z axis. 3822 * Behaves wrong when called for a 3D transform. 3823 */ 3824 private void rotate2D_270() { 3825 double M0 = getMxx(); 3826 setMxx(-getMxy()); 3827 setMxy(M0); 3828 M0 = getMyx(); 3829 setMyx(-getMyy()); 3830 setMyy(M0); 3831 int newstate = rot90conversion[state2d]; 3832 if ((newstate & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SCALE && 3833 getMxx() == 1.0 && getMyy() == 1.0) { 3834 newstate -= APPLY_SCALE; 3835 } else if ((newstate & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SHEAR && 3836 getMxy() == 0.0 && getMyx() == 0.0) { 3837 newstate = (newstate & ~APPLY_SHEAR | APPLY_SCALE); 3838 } 3839 state2d = newstate; 3840 } 3841 3842 /** 3843 * 3D implementation of {@code appendRotation} around Z axis. 3844 * If this is a 2D transform, the call is redirected to {@code rotate2D()}. 3845 */ 3846 private void rotate3D(double theta) { 3847 if (state3d == APPLY_NON_3D) { 3848 rotate2D(theta); 3849 return; 3850 } 3851 3852 double sin = Math.sin(Math.toRadians(theta)); 3853 if (sin == 1.0) { 3854 rotate3D_90(); 3855 } else if (sin == -1.0) { 3856 rotate3D_270(); 3857 } else { 3858 double cos = Math.cos(Math.toRadians(theta)); 3859 if (cos == -1.0) { 3860 rotate3D_180(); 3861 } else if (cos != 1.0) { 3862 double M0, M1; 3863 M0 = getMxx(); 3864 M1 = getMxy(); 3865 setMxx(cos * M0 + sin * M1); 3866 setMxy(-sin * M0 + cos * M1); 3867 M0 = getMyx(); 3868 M1 = getMyy(); 3869 setMyx(cos * M0 + sin * M1); 3870 setMyy(-sin * M0 + cos * M1); 3871 M0 = getMzx(); 3872 M1 = getMzy(); 3873 setMzx(cos * M0 + sin * M1); 3874 setMzy(-sin * M0 + cos * M1); 3875 updateState(); 3876 } 3877 } 3878 } 3879 3880 /** 3881 * 3D implementation of {@code appendRotation} for 90 degrees rotation 3882 * around Z axis. 3883 * Behaves wrong when called for a 2D transform. 3884 */ 3885 private void rotate3D_90() { 3886 double M0 = getMxx(); 3887 setMxx(getMxy()); 3888 setMxy(-M0); 3889 M0 = getMyx(); 3890 setMyx(getMyy()); 3891 setMyy(-M0); 3892 M0 = getMzx(); 3893 setMzx(getMzy()); 3894 setMzy(-M0); 3895 switch(state3d) { 3896 default: 3897 stateError(); 3898 // cannot reach 3899 case APPLY_TRANSLATE: 3900 state3d = APPLY_3D_COMPLEX; 3901 return; 3902 case APPLY_SCALE: 3903 case APPLY_SCALE | APPLY_TRANSLATE: 3904 if (getMxy() != 0.0 || getMyx() != 0.0) { 3905 state3d = APPLY_3D_COMPLEX; 3906 } 3907 return; 3908 case APPLY_3D_COMPLEX: 3909 updateState(); 3910 return; 3911 } 3912 } 3913 3914 /** 3915 * 3D implementation of {@code appendRotation} for 180 degrees rotation 3916 * around Z axis. 3917 * Behaves wrong when called for a 2D transform. 3918 */ 3919 private void rotate3D_180() { 3920 final double mxx = getMxx(); 3921 final double myy = getMyy(); 3922 setMxx(-mxx); 3923 setMyy(-myy); 3924 if (state3d == APPLY_3D_COMPLEX) { 3925 setMxy(-getMxy()); 3926 setMyx(-getMyx()); 3927 setMzx(-getMzx()); 3928 setMzy(-getMzy()); 3929 updateState(); 3930 return; 3931 } 3932 3933 if (mxx == -1.0 && myy == -1.0 && getMzz() == 1.0) { 3934 // must have been 3d because of translation, which remained 3935 state3d &= ~APPLY_SCALE; 3936 } else { 3937 state3d |= APPLY_SCALE; 3938 } 3939 } 3940 3941 /** 3942 * 3D implementation of {@code appendRotation} for 270 degrees rotation 3943 * around Z axis. 3944 * Behaves wrong when called for a 2D transform. 3945 */ 3946 private void rotate3D_270() { 3947 double M0 = getMxx(); 3948 setMxx(-getMxy()); 3949 setMxy(M0); 3950 M0 = getMyx(); 3951 setMyx(-getMyy()); 3952 setMyy(M0); 3953 M0 = getMzx(); 3954 setMzx(-getMzy()); 3955 setMzy(M0); 3956 switch(state3d) { 3957 default: 3958 stateError(); 3959 // cannot reach 3960 case APPLY_TRANSLATE: 3961 state3d = APPLY_3D_COMPLEX; 3962 return; 3963 case APPLY_SCALE: 3964 case APPLY_SCALE | APPLY_TRANSLATE: 3965 if (getMxy() != 0.0 || getMyx() != 0.0) { 3966 state3d = APPLY_3D_COMPLEX; 3967 } 3968 return; 3969 case APPLY_3D_COMPLEX: 3970 updateState(); 3971 return; 3972 } 3973 } 3974 3975 /** 3976 * <p> 3977 * Prepends the 2D rotation to this instance. 3978 * It is equivalent to {@code prepend(new Rotate(angle))}. 3979 * </p><p> 3980 * The operation modifies this transform in a way that applying it to a node 3981 * has the same effect as adding two transforms to its 3982 * {@code getTransforms()} list, the specified rotation first 3983 * and {@code this} transform second. 3984 * </p><p> 3985 * From the matrix point of view, the transformation matrix of this 3986 * transform is multiplied on the left by the transformation matrix of 3987 * the specified rotation. 3988 * </p> 3989 * @param angle the angle of the rotation in degrees 3990 * @since JavaFX 8.0 3991 */ 3992 public void prependRotation(double angle) { 3993 atomicChange.start(); 3994 preRotate2D(angle); 3995 atomicChange.end(); 3996 } 3997 3998 /** 3999 * <p> 4000 * Prepends the 2D rotation with pivot to this instance. 4001 * It is equivalent to {@code prepend(new Rotate(angle, pivotX, pivotY))}. 4002 * </p><p> 4003 * The operation modifies this transform in a way that applying it to a node 4004 * has the same effect as adding two transforms to its 4005 * {@code getTransforms()} list, the specified rotation first 4006 * and {@code this} transform second. 4007 * </p><p> 4008 * From the matrix point of view, the transformation matrix of this 4009 * transform is multiplied on the left by the transformation matrix of 4010 * the specified rotation. 4011 * </p> 4012 * @param angle the angle of the rotation in degrees 4013 * @param pivotX the X coordinate of the rotation pivot point 4014 * @param pivotY the Y coordinate of the rotation pivot point 4015 * @since JavaFX 8.0 4016 */ 4017 public void prependRotation(double angle, double pivotX, double pivotY) { 4018 atomicChange.start(); 4019 if (pivotX != 0.0 || pivotY != 0.0) { 4020 preTranslate2D(-pivotX, -pivotY); 4021 preRotate2D(angle); 4022 preTranslate2D(pivotX, pivotY); 4023 } else { 4024 preRotate2D(angle); 4025 } 4026 atomicChange.end(); 4027 } 4028 4029 /** 4030 * <p> 4031 * Prepends the 2D rotation with pivot to this instance. 4032 * It is equivalent to {@code prepend(new Rotate(angle, pivot.getX(), 4033 * pivot.getY()))}. 4034 * </p><p> 4035 * The operation modifies this transform in a way that applying it to a node 4036 * has the same effect as adding two transforms to its 4037 * {@code getTransforms()} list, the specified rotation first 4038 * and {@code this} transform second. 4039 * </p><p> 4040 * From the matrix point of view, the transformation matrix of this 4041 * transform is multiplied on the left by the transformation matrix of 4042 * the specified rotation. 4043 * </p> 4044 * @param angle the angle of the rotation in degrees 4045 * @param pivot the rotation pivot point 4046 * @throws NullPointerException if the specified {@code pivot} is null 4047 * @since JavaFX 8.0 4048 */ 4049 public void prependRotation(double angle, Point2D pivot) { 4050 prependRotation(angle, pivot.getX(), pivot.getY()); 4051 } 4052 4053 /** 4054 * <p> 4055 * Prepends the rotation to this instance. 4056 * It is equivalent to {@code prepend(new Rotate(angle, pivotX, pivotY, 4057 * pivotZ, new Point3D(axisX, axisY, axisZ)))}. 4058 * </p><p> 4059 * The operation modifies this transform in a way that applying it to a node 4060 * has the same effect as adding two transforms to its 4061 * {@code getTransforms()} list, the specified rotation first 4062 * and {@code this} transform second. 4063 * </p><p> 4064 * From the matrix point of view, the transformation matrix of this 4065 * transform is multiplied on the left by the transformation matrix of 4066 * the specified rotation. 4067 * </p> 4068 * @param angle the angle of the rotation in degrees 4069 * @param pivotX the X coordinate of the rotation pivot point 4070 * @param pivotY the Y coordinate of the rotation pivot point 4071 * @param pivotZ the Z coordinate of the rotation pivot point 4072 * @param axisX the X coordinate magnitude of the rotation axis 4073 * @param axisY the Y coordinate magnitude of the rotation axis 4074 * @param axisZ the Z coordinate magnitude of the rotation axis 4075 * @since JavaFX 8.0 4076 */ 4077 public void prependRotation(double angle, 4078 double pivotX, double pivotY, double pivotZ, 4079 double axisX, double axisY, double axisZ) { 4080 atomicChange.start(); 4081 if (pivotX != 0.0 || pivotY != 0.0 || pivotZ != 0.0) { 4082 preTranslate3D(-pivotX, -pivotY, -pivotZ); 4083 preRotate3D(angle, axisX, axisY, axisZ); 4084 preTranslate3D(pivotX, pivotY, pivotZ); 4085 } else { 4086 preRotate3D(angle, axisX, axisY, axisZ); 4087 } 4088 atomicChange.end(); 4089 } 4090 4091 /** 4092 * <p> 4093 * Prepends the rotation to this instance. 4094 * It is equivalent to {@code prepend(new Rotate(angle, pivotX, pivotY, 4095 * pivotZ, axis))}. 4096 * </p><p> 4097 * The operation modifies this transform in a way that applying it to a node 4098 * has the same effect as adding two transforms to its 4099 * {@code getTransforms()} list, the specified rotation first 4100 * and {@code this} transform second. 4101 * </p><p> 4102 * From the matrix point of view, the transformation matrix of this 4103 * transform is multiplied on the left by the transformation matrix of 4104 * the specified rotation. 4105 * </p> 4106 * @param angle the angle of the rotation in degrees 4107 * @param pivotX the X coordinate of the rotation pivot point 4108 * @param pivotY the Y coordinate of the rotation pivot point 4109 * @param pivotZ the Z coordinate of the rotation pivot point 4110 * @param axis the rotation axis 4111 * @throws NullPointerException if the specified {@code axis} is null 4112 * @since JavaFX 8.0 4113 */ 4114 public void prependRotation(double angle, 4115 double pivotX, double pivotY, double pivotZ, 4116 Point3D axis) { 4117 prependRotation(angle, pivotX, pivotY, pivotZ, 4118 axis.getX(), axis.getY(), axis.getZ()); 4119 } 4120 4121 /** 4122 * <p> 4123 * Prepends the rotation to this instance. 4124 * It is equivalent to {@code prepend(new Rotate(angle, pivot.getX(), 4125 * pivot.getY(), pivot.getZ(), axis))}. 4126 * </p><p> 4127 * The operation modifies this transform in a way that applying it to a node 4128 * has the same effect as adding two transforms to its 4129 * {@code getTransforms()} list, the specified rotation first 4130 * and {@code this} transform second. 4131 * </p><p> 4132 * From the matrix point of view, the transformation matrix of this 4133 * transform is multiplied on the left by the transformation matrix of 4134 * the specified rotation. 4135 * </p> 4136 * @param angle the angle of the rotation in degrees 4137 * @param pivot the rotation pivot point 4138 * @param axis the rotation axis 4139 * @throws NullPointerException if the specified {@code pivot} 4140 * or {@code axis} is null 4141 * @since JavaFX 8.0 4142 */ 4143 public void prependRotation(double angle, Point3D pivot, Point3D axis) { 4144 prependRotation(angle, pivot.getX(), pivot.getY(), pivot.getZ(), 4145 axis.getX(), axis.getY(), axis.getZ()); 4146 } 4147 4148 /** 4149 * Implementation of the {@code prependRotation()} around an arbitrary axis. 4150 */ 4151 private void preRotate3D(double angle, 4152 double axisX, double axisY, double axisZ) { 4153 4154 if (axisX == 0.0 && axisY == 0.0) { 4155 if (axisZ > 0.0) { 4156 preRotate3D(angle); 4157 } else if (axisZ < 0.0) { 4158 preRotate3D(-angle); 4159 } // else rotating about zero vector - NOP 4160 return; 4161 } 4162 4163 double mag = Math.sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ); 4164 4165 if (mag == 0.0) { 4166 return; 4167 } 4168 4169 mag = 1.0 / mag; 4170 final double ax = axisX * mag; 4171 final double ay = axisY * mag; 4172 final double az = axisZ * mag; 4173 4174 final double sinTheta = Math.sin(Math.toRadians(angle)); 4175 final double cosTheta = Math.cos(Math.toRadians(angle)); 4176 final double t = 1.0 - cosTheta; 4177 4178 final double xz = ax * az; 4179 final double xy = ax * ay; 4180 final double yz = ay * az; 4181 4182 final double Txx = t * ax * ax + cosTheta; 4183 final double Txy = t * xy - sinTheta * az; 4184 final double Txz = t * xz + sinTheta * ay; 4185 4186 final double Tyx = t * xy + sinTheta * az; 4187 final double Tyy = t * ay * ay + cosTheta; 4188 final double Tyz = t * yz - sinTheta * ax; 4189 4190 final double Tzx = t * xz - sinTheta * ay; 4191 final double Tzy = t * yz + sinTheta * ax; 4192 final double Tzz = t * az * az + cosTheta; 4193 4194 switch (state3d) { 4195 default: 4196 stateError(); 4197 // cannot reach 4198 case APPLY_NON_3D: 4199 switch (state2d) { 4200 default: 4201 stateError(); 4202 // cannot reach 4203 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 4204 final double xx_sst = getMxx(); 4205 final double xy_sst = getMxy(); 4206 final double tx_sst = getTx(); 4207 final double yx_sst = getMyx(); 4208 final double yy_sst = getMyy(); 4209 final double ty_sst = getTy(); 4210 setMxx(Txx * xx_sst + Txy * yx_sst); 4211 setMxy(Txx * xy_sst + Txy * yy_sst); 4212 setMxz(Txz); 4213 setTx( Txx * tx_sst + Txy * ty_sst); 4214 setMyx(Tyx * xx_sst + Tyy * yx_sst); 4215 setMyy(Tyx * xy_sst + Tyy * yy_sst); 4216 setMyz(Tyz); 4217 setTy( Tyx * tx_sst + Tyy * ty_sst); 4218 setMzx(Tzx * xx_sst + Tzy * yx_sst); 4219 setMzy(Tzx * xy_sst + Tzy * yy_sst); 4220 setMzz(Tzz); 4221 setTz( Tzx * tx_sst + Tzy * ty_sst); 4222 break; 4223 case APPLY_SHEAR | APPLY_SCALE: 4224 final double xx_ss = getMxx(); 4225 final double xy_ss = getMxy(); 4226 final double yx_ss = getMyx(); 4227 final double yy_ss = getMyy(); 4228 setMxx(Txx * xx_ss + Txy * yx_ss); 4229 setMxy(Txx * xy_ss + Txy * yy_ss); 4230 setMxz(Txz); 4231 setMyx(Tyx * xx_ss + Tyy * yx_ss); 4232 setMyy(Tyx * xy_ss + Tyy * yy_ss); 4233 setMyz(Tyz); 4234 setMzx(Tzx * xx_ss + Tzy * yx_ss); 4235 setMzy(Tzx * xy_ss + Tzy * yy_ss); 4236 setMzz(Tzz); 4237 break; 4238 case APPLY_SHEAR | APPLY_TRANSLATE: 4239 final double xy_sht = getMxy(); 4240 final double tx_sht = getTx(); 4241 final double yx_sht = getMyx(); 4242 final double ty_sht = getTy(); 4243 setMxx(Txy * yx_sht); 4244 setMxy(Txx * xy_sht); 4245 setMxz(Txz); 4246 setTx( Txx * tx_sht + Txy * ty_sht); 4247 setMyx(Tyy * yx_sht); 4248 setMyy(Tyx * xy_sht); 4249 setMyz(Tyz); 4250 setTy( Tyx * tx_sht + Tyy * ty_sht); 4251 setMzx(Tzy * yx_sht); 4252 setMzy(Tzx * xy_sht); 4253 setMzz(Tzz); 4254 setTz( Tzx * tx_sht + Tzy * ty_sht); 4255 break; 4256 case APPLY_SHEAR: 4257 final double xy_sh = getMxy(); 4258 final double yx_sh = getMyx(); 4259 setMxx(Txy * yx_sh); 4260 setMxy(Txx * xy_sh); 4261 setMxz(Txz); 4262 setMyx(Tyy * yx_sh); 4263 setMyy(Tyx * xy_sh); 4264 setMyz(Tyz); 4265 setMzx(Tzy * yx_sh); 4266 setMzy(Tzx * xy_sh); 4267 setMzz(Tzz); 4268 break; 4269 case APPLY_SCALE | APPLY_TRANSLATE: 4270 final double xx_st = getMxx(); 4271 final double tx_st = getTx(); 4272 final double yy_st = getMyy(); 4273 final double ty_st = getTy(); 4274 setMxx(Txx * xx_st); 4275 setMxy(Txy * yy_st); 4276 setMxz(Txz); 4277 setTx( Txx * tx_st + Txy * ty_st); 4278 setMyx(Tyx * xx_st); 4279 setMyy(Tyy * yy_st); 4280 setMyz(Tyz); 4281 setTy( Tyx * tx_st + Tyy * ty_st); 4282 setMzx(Tzx * xx_st); 4283 setMzy(Tzy * yy_st); 4284 setMzz(Tzz); 4285 setTz( Tzx * tx_st + Tzy * ty_st); 4286 break; 4287 case APPLY_SCALE: 4288 final double xx_s = getMxx(); 4289 final double yy_s = getMyy(); 4290 setMxx(Txx * xx_s); 4291 setMxy(Txy * yy_s); 4292 setMxz(Txz); 4293 setMyx(Tyx * xx_s); 4294 setMyy(Tyy * yy_s); 4295 setMyz(Tyz); 4296 setMzx(Tzx * xx_s); 4297 setMzy(Tzy * yy_s); 4298 setMzz(Tzz); 4299 break; 4300 case APPLY_TRANSLATE: 4301 final double tx_t = getTx(); 4302 final double ty_t = getTy(); 4303 setMxx(Txx); 4304 setMxy(Txy); 4305 setMxz(Txz); 4306 setTx( Txx * tx_t + Txy * ty_t); 4307 setMyx(Tyx); 4308 setMyy(Tyy); 4309 setMyz(Tyz); 4310 setTy( Tyx * tx_t + Tyy * ty_t); 4311 setMzx(Tzx); 4312 setMzy(Tzy); 4313 setMzz(Tzz); 4314 setTz( Tzx * tx_t + Tzy * ty_t); 4315 break; 4316 case APPLY_IDENTITY: 4317 setMxx(Txx); 4318 setMxy(Txy); 4319 setMxz(Txz); 4320 setMyx(Tyx); 4321 setMyy(Tyy); 4322 setMyz(Tyz); 4323 setMzx(Tzx); 4324 setMzy(Tzy); 4325 setMzz(Tzz); 4326 break; 4327 } 4328 break; 4329 case APPLY_TRANSLATE: 4330 final double tx_t = getTx(); 4331 final double ty_t = getTy(); 4332 final double tz_t = getTz(); 4333 setMxx(Txx); 4334 setMxy(Txy); 4335 setMxz(Txz); 4336 setMyx(Tyx); 4337 setMyy(Tyy); 4338 setMyz(Tyz); 4339 setMzx(Tzx); 4340 setMzy(Tzy); 4341 setMzz(Tzz); 4342 setTx( Txx * tx_t + Txy * ty_t + Txz * tz_t); 4343 setTy( Tyx * tx_t + Tyy * ty_t + Tyz * tz_t); 4344 setTz( Tzx * tx_t + Tzy * ty_t + Tzz * tz_t); 4345 break; 4346 case APPLY_SCALE: 4347 final double xx_s = getMxx(); 4348 final double yy_s = getMyy(); 4349 final double zz_s = getMzz(); 4350 setMxx(Txx * xx_s); 4351 setMxy(Txy * yy_s); 4352 setMxz(Txz * zz_s); 4353 setMyx(Tyx * xx_s); 4354 setMyy(Tyy * yy_s); 4355 setMyz(Tyz * zz_s); 4356 setMzx(Tzx * xx_s); 4357 setMzy(Tzy * yy_s); 4358 setMzz(Tzz * zz_s); 4359 break; 4360 case APPLY_SCALE | APPLY_TRANSLATE: 4361 final double xx_st = getMxx(); 4362 final double tx_st = getTx(); 4363 final double yy_st = getMyy(); 4364 final double ty_st = getTy(); 4365 final double zz_st = getMzz(); 4366 final double tz_st = getTz(); 4367 setMxx(Txx * xx_st); 4368 setMxy(Txy * yy_st); 4369 setMxz(Txz * zz_st); 4370 setTx( Txx * tx_st + Txy * ty_st + Txz * tz_st); 4371 setMyx(Tyx * xx_st); 4372 setMyy(Tyy * yy_st); 4373 setMyz(Tyz * zz_st); 4374 setTy( Tyx * tx_st + Tyy * ty_st + Tyz * tz_st); 4375 setMzx(Tzx * xx_st); 4376 setMzy(Tzy * yy_st); 4377 setMzz(Tzz * zz_st); 4378 setTz( Tzx * tx_st + Tzy * ty_st + Tzz * tz_st); 4379 break; 4380 case APPLY_3D_COMPLEX: 4381 final double m_xx = getMxx(); 4382 final double m_xy = getMxy(); 4383 final double m_xz = getMxz(); 4384 final double t_x = getTx(); 4385 final double m_yx = getMyx(); 4386 final double m_yy = getMyy(); 4387 final double m_yz = getMyz(); 4388 final double t_y = getTy(); 4389 final double m_zx = getMzx(); 4390 final double m_zy = getMzy(); 4391 final double m_zz = getMzz(); 4392 final double t_z = getTz(); 4393 setMxx(Txx * m_xx + Txy * m_yx + Txz * m_zx /* + Ttx * 0.0 */); 4394 setMxy(Txx * m_xy + Txy * m_yy + Txz * m_zy /* + Ttx * 0.0 */); 4395 setMxz(Txx * m_xz + Txy * m_yz + Txz * m_zz /* + Ttx * 0.0 */); 4396 setTx( Txx * t_x + Txy * t_y + Txz * t_z /* + Ttx * 0.0 */); 4397 setMyx(Tyx * m_xx + Tyy * m_yx + Tyz * m_zx /* + Tty * 0.0 */); 4398 setMyy(Tyx * m_xy + Tyy * m_yy + Tyz * m_zy /* + Tty * 0.0 */); 4399 setMyz(Tyx * m_xz + Tyy * m_yz + Tyz * m_zz /* + Tty * 0.0 */); 4400 setTy( Tyx * t_x + Tyy * t_y + Tyz * t_z /* + Tty * 0.0 */); 4401 setMzx(Tzx * m_xx + Tzy * m_yx + Tzz * m_zx /* + Ttz * 0.0 */); 4402 setMzy(Tzx * m_xy + Tzy * m_yy + Tzz * m_zy /* + Ttz * 0.0 */); 4403 setMzz(Tzx * m_xz + Tzy * m_yz + Tzz * m_zz /* + Ttz * 0.0 */); 4404 setTz( Tzx * t_x + Tzy * t_y + Tzz * t_z /* + Ttz * 0.0 */); 4405 break; 4406 } 4407 4408 updateState(); 4409 } 4410 4411 /** 4412 * 2D implementation of {@code prependRotation}. 4413 * If this is a 3D transform, the call is redirected to {@code preRotate3D()}. 4414 */ 4415 private void preRotate2D(double theta) { 4416 4417 if (state3d != APPLY_NON_3D) { 4418 preRotate3D(theta); 4419 return; 4420 } 4421 4422 double sin = Math.sin(Math.toRadians(theta)); 4423 if (sin == 1.0) { 4424 preRotate2D_90(); 4425 } else if (sin == -1.0) { 4426 preRotate2D_270(); 4427 } else { 4428 double cos = Math.cos(Math.toRadians(theta)); 4429 if (cos == -1.0) { 4430 preRotate2D_180(); 4431 } else if (cos != 1.0) { 4432 double M0, M1; 4433 M0 = getMxx(); 4434 M1 = getMyx(); 4435 setMxx(cos * M0 - sin * M1); 4436 setMyx(sin * M0 + cos * M1); 4437 M0 = getMxy(); 4438 M1 = getMyy(); 4439 setMxy(cos * M0 - sin * M1); 4440 setMyy(sin * M0 + cos * M1); 4441 M0 = getTx(); 4442 M1 = getTy(); 4443 setTx(cos * M0 - sin * M1); 4444 setTy(sin * M0 + cos * M1); 4445 updateState2D(); 4446 } 4447 } 4448 } 4449 4450 /** 4451 * 2D implementation of {@code prependRotation} for 90 degrees rotation 4452 * around Z axis. 4453 * Behaves wrong when called for a 3D transform. 4454 */ 4455 private void preRotate2D_90() { 4456 double M0 = getMxx(); 4457 setMxx(-getMyx()); 4458 setMyx(M0); 4459 M0 = getMxy(); 4460 setMxy(-getMyy()); 4461 setMyy(M0); 4462 M0 = getTx(); 4463 setTx(-getTy()); 4464 setTy(M0); 4465 4466 int newstate = rot90conversion[state2d]; 4467 if ((newstate & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SCALE && 4468 getMxx() == 1.0 && getMyy() == 1.0) { 4469 newstate -= APPLY_SCALE; 4470 } else if ((newstate & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SHEAR && 4471 getMxy() == 0.0 && getMyx() == 0.0) { 4472 newstate = (newstate & ~APPLY_SHEAR | APPLY_SCALE); 4473 } 4474 state2d = newstate; 4475 } 4476 4477 /** 4478 * 2D implementation of {@code prependRotation} for 180 degrees rotation 4479 * around Z axis. 4480 * Behaves wrong when called for a 3D transform. 4481 */ 4482 private void preRotate2D_180() { 4483 setMxx(-getMxx()); 4484 setMxy(-getMxy()); 4485 setTx(-getTx()); 4486 setMyx(-getMyx()); 4487 setMyy(-getMyy()); 4488 setTy(-getTy()); 4489 4490 if ((state2d & APPLY_SHEAR) != 0) { 4491 if (getMxx() == 0.0 && getMyy() == 0.0) { 4492 state2d &= ~APPLY_SCALE; 4493 } else { 4494 state2d |= APPLY_SCALE; 4495 } 4496 } else { 4497 if (getMxx() == 1.0 && getMyy() == 1.0) { 4498 state2d &= ~APPLY_SCALE; 4499 } else { 4500 state2d |= APPLY_SCALE; 4501 } 4502 } 4503 } 4504 4505 /** 4506 * 2D implementation of {@code prependRotation} for 270 degrees rotation 4507 * around Z axis. 4508 * Behaves wrong when called for a 3D transform. 4509 */ 4510 private void preRotate2D_270() { 4511 double M0 = getMxx(); 4512 setMxx(getMyx()); 4513 setMyx(-M0); 4514 M0 = getMxy(); 4515 setMxy(getMyy()); 4516 setMyy(-M0); 4517 M0 = getTx(); 4518 setTx(getTy()); 4519 setTy(-M0); 4520 4521 int newstate = rot90conversion[state2d]; 4522 if ((newstate & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SCALE && 4523 getMxx() == 1.0 && getMyy() == 1.0) { 4524 newstate -= APPLY_SCALE; 4525 } else if ((newstate & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SHEAR && 4526 getMxy() == 0.0 && getMyx() == 0.0) { 4527 newstate = (newstate & ~APPLY_SHEAR | APPLY_SCALE); 4528 } 4529 state2d = newstate; 4530 } 4531 4532 /** 4533 * 3D implementation of {@code prependRotation} around Z axis. 4534 * If this is a 2D transform, the call is redirected to {@code preRotate2D()}. 4535 */ 4536 private void preRotate3D(double theta) { 4537 if (state3d == APPLY_NON_3D) { 4538 preRotate2D(theta); 4539 return; 4540 } 4541 4542 double sin = Math.sin(Math.toRadians(theta)); 4543 if (sin == 1.0) { 4544 preRotate3D_90(); 4545 } else if (sin == -1.0) { 4546 preRotate3D_270(); 4547 } else { 4548 double cos = Math.cos(Math.toRadians(theta)); 4549 if (cos == -1.0) { 4550 preRotate3D_180(); 4551 } else if (cos != 1.0) { 4552 double M0, M1; 4553 M0 = getMxx(); 4554 M1 = getMyx(); 4555 setMxx(cos * M0 - sin * M1); 4556 setMyx(sin * M0 + cos * M1); 4557 M0 = getMxy(); 4558 M1 = getMyy(); 4559 setMxy(cos * M0 - sin * M1); 4560 setMyy(sin * M0 + cos * M1); 4561 M0 = getMxz(); 4562 M1 = getMyz(); 4563 setMxz(cos * M0 - sin * M1); 4564 setMyz(sin * M0 + cos * M1); 4565 M0 = getTx(); 4566 M1 = getTy(); 4567 setTx(cos * M0 - sin * M1); 4568 setTy(sin * M0 + cos * M1); 4569 updateState(); 4570 } 4571 } 4572 } 4573 4574 /** 4575 * 3D implementation of {@code prependRotation} for 90 degrees rotation 4576 * around Z axis. 4577 * Behaves wrong when called for a 2D transform. 4578 */ 4579 private void preRotate3D_90() { 4580 double M0 = getMxx(); 4581 setMxx(-getMyx()); 4582 setMyx(M0); 4583 M0 = getMxy(); 4584 setMxy(-getMyy()); 4585 setMyy(M0); 4586 M0 = getMxz(); 4587 setMxz(-getMyz()); 4588 setMyz(M0); 4589 M0 = getTx(); 4590 setTx(-getTy()); 4591 setTy(M0); 4592 4593 switch(state3d) { 4594 default: 4595 stateError(); 4596 // cannot reach 4597 case APPLY_TRANSLATE: 4598 state3d = APPLY_3D_COMPLEX; 4599 return; 4600 case APPLY_SCALE: 4601 case APPLY_SCALE | APPLY_TRANSLATE: 4602 if (getMxy() != 0.0 || getMyx() != 0.0) { 4603 state3d = APPLY_3D_COMPLEX; 4604 } 4605 return; 4606 case APPLY_3D_COMPLEX: 4607 updateState(); 4608 return; 4609 } 4610 } 4611 4612 /** 4613 * 3D implementation of {@code prependRotation} for 180 degrees rotation 4614 * around Z axis. 4615 * Behaves wrong when called for a 2D transform. 4616 */ 4617 private void preRotate3D_180() { 4618 final double mxx = getMxx(); 4619 final double myy = getMyy(); 4620 setMxx(-mxx); 4621 setMyy(-myy); 4622 setTx(-getTx()); 4623 setTy(-getTy()); 4624 4625 if (state3d == APPLY_3D_COMPLEX) { 4626 setMxy(-getMxy()); 4627 setMxz(-getMxz()); 4628 setMyx(-getMyx()); 4629 setMyz(-getMyz()); 4630 updateState(); 4631 return; 4632 } 4633 4634 if (mxx == -1.0 && myy == -1.0 && getMzz() == 1.0) { 4635 // must have been 3d because of translation, which remained 4636 state3d &= ~APPLY_SCALE; 4637 } else { 4638 state3d |= APPLY_SCALE; 4639 } 4640 } 4641 4642 /** 4643 * 3D implementation of {@code prependRotation} for 270 degrees rotation 4644 * around Z axis. 4645 * Behaves wrong when called for a 2D transform. 4646 */ 4647 private void preRotate3D_270() { 4648 double M0 = getMxx(); 4649 setMxx(getMyx()); 4650 setMyx(-M0); 4651 M0 = getMxy(); 4652 setMxy(getMyy()); 4653 setMyy(-M0); 4654 M0 = getMxz(); 4655 setMxz(getMyz()); 4656 setMyz(-M0); 4657 M0 = getTx(); 4658 setTx(getTy()); 4659 setTy(-M0); 4660 4661 switch(state3d) { 4662 default: 4663 stateError(); 4664 // cannot reach 4665 case APPLY_TRANSLATE: 4666 state3d = APPLY_3D_COMPLEX; 4667 return; 4668 case APPLY_SCALE: 4669 case APPLY_SCALE | APPLY_TRANSLATE: 4670 if (getMxy() != 0.0 || getMyx() != 0.0) { 4671 state3d = APPLY_3D_COMPLEX; 4672 } 4673 return; 4674 case APPLY_3D_COMPLEX: 4675 updateState(); 4676 return; 4677 } 4678 } 4679 4680 /* ************************************************************************* 4681 * * 4682 * Transform, Inverse Transform * 4683 * * 4684 **************************************************************************/ 4685 4686 @Override 4687 public Point2D transform(double x, double y) { 4688 ensureCanTransform2DPoint(); 4689 4690 switch (state2d) { 4691 default: 4692 stateError(); 4693 // cannot reach 4694 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 4695 return new Point2D( 4696 getMxx() * x + getMxy() * y + getTx(), 4697 getMyx() * x + getMyy() * y + getTy()); 4698 case APPLY_SHEAR | APPLY_SCALE: 4699 return new Point2D( 4700 getMxx() * x + getMxy() * y, 4701 getMyx() * x + getMyy() * y); 4702 case APPLY_SHEAR | APPLY_TRANSLATE: 4703 return new Point2D( 4704 getMxy() * y + getTx(), 4705 getMyx() * x + getTy()); 4706 case APPLY_SHEAR: 4707 return new Point2D(getMxy() * y, getMyx() * x); 4708 case APPLY_SCALE | APPLY_TRANSLATE: 4709 return new Point2D( 4710 getMxx() * x + getTx(), 4711 getMyy() * y + getTy()); 4712 case APPLY_SCALE: 4713 return new Point2D(getMxx() * x, getMyy() * y); 4714 case APPLY_TRANSLATE: 4715 return new Point2D(x + getTx(), y + getTy()); 4716 case APPLY_IDENTITY: 4717 return new Point2D(x, y); 4718 } 4719 } 4720 4721 @Override 4722 public Point3D transform(double x, double y, double z) { 4723 switch (state3d) { 4724 default: 4725 stateError(); 4726 // cannot reach 4727 case APPLY_NON_3D: 4728 switch (state2d) { 4729 default: 4730 stateError(); 4731 // cannot reach 4732 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 4733 return new Point3D( 4734 getMxx() * x + getMxy() * y + getTx(), 4735 getMyx() * x + getMyy() * y + getTy(), z); 4736 case APPLY_SHEAR | APPLY_SCALE: 4737 return new Point3D( 4738 getMxx() * x + getMxy() * y, 4739 getMyx() * x + getMyy() * y, z); 4740 case APPLY_SHEAR | APPLY_TRANSLATE: 4741 return new Point3D( 4742 getMxy() * y + getTx(), getMyx() * x + getTy(), 4743 z); 4744 case APPLY_SHEAR: 4745 return new Point3D(getMxy() * y, getMyx() * x, z); 4746 case APPLY_SCALE | APPLY_TRANSLATE: 4747 return new Point3D( 4748 getMxx() * x + getTx(), getMyy() * y + getTy(), 4749 z); 4750 case APPLY_SCALE: 4751 return new Point3D(getMxx() * x, getMyy() * y, z); 4752 case APPLY_TRANSLATE: 4753 return new Point3D(x + getTx(), y + getTy(), z); 4754 case APPLY_IDENTITY: 4755 return new Point3D(x, y, z); 4756 } 4757 case APPLY_TRANSLATE: 4758 return new Point3D(x + getTx(), y + getTy(), z + getTz()); 4759 case APPLY_SCALE: 4760 return new Point3D(getMxx() * x, getMyy() * y, getMzz() * z); 4761 case APPLY_SCALE | APPLY_TRANSLATE: 4762 return new Point3D( 4763 getMxx() * x + getTx(), 4764 getMyy() * y + getTy(), 4765 getMzz() * z + getTz()); 4766 case APPLY_3D_COMPLEX: 4767 return new Point3D( 4768 getMxx() * x + getMxy() * y + getMxz() * z + getTx(), 4769 getMyx() * x + getMyy() * y + getMyz() * z + getTy(), 4770 getMzx() * x + getMzy() * y + getMzz() * z + getTz()); 4771 } 4772 } 4773 4774 @Override 4775 void transform2DPointsImpl(double[] srcPts, int srcOff, 4776 double[] dstPts, int dstOff, int numPts) { 4777 4778 double mxx, mxy, tx, myx, myy, ty; 4779 4780 switch (state2d) { 4781 default: 4782 stateError(); 4783 // cannot reach 4784 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 4785 mxx = getMxx(); mxy = getMxy(); tx = getTx(); 4786 myx = getMyx(); myy = getMyy(); ty = getTy(); 4787 while (--numPts >= 0) { 4788 final double x = srcPts[srcOff++]; 4789 final double y = srcPts[srcOff++]; 4790 dstPts[dstOff++] = mxx * x + mxy * y + tx; 4791 dstPts[dstOff++] = myx * x + myy * y + ty; 4792 } 4793 return; 4794 case APPLY_SHEAR | APPLY_SCALE: 4795 mxx = getMxx(); mxy = getMxy(); 4796 myx = getMyx(); myy = getMyy(); 4797 while (--numPts >= 0) { 4798 final double x = srcPts[srcOff++]; 4799 final double y = srcPts[srcOff++]; 4800 dstPts[dstOff++] = mxx * x + mxy * y; 4801 dstPts[dstOff++] = myx * x + myy * y; 4802 } 4803 return; 4804 case APPLY_SHEAR | APPLY_TRANSLATE: 4805 mxy = getMxy(); tx = getTx(); 4806 myx = getMyx(); ty = getTy(); 4807 while (--numPts >= 0) { 4808 final double x = srcPts[srcOff++]; 4809 dstPts[dstOff++] = mxy * srcPts[srcOff++] + tx; 4810 dstPts[dstOff++] = myx * x + ty; 4811 } 4812 return; 4813 case APPLY_SHEAR: 4814 mxy = getMxy(); 4815 myx = getMyx(); 4816 while (--numPts >= 0) { 4817 final double x = srcPts[srcOff++]; 4818 dstPts[dstOff++] = mxy * srcPts[srcOff++]; 4819 dstPts[dstOff++] = myx * x; 4820 } 4821 return; 4822 case APPLY_SCALE | APPLY_TRANSLATE: 4823 mxx = getMxx(); tx = getTx(); 4824 myy = getMyy(); ty = getTy(); 4825 while (--numPts >= 0) { 4826 dstPts[dstOff++] = mxx * srcPts[srcOff++] + tx; 4827 dstPts[dstOff++] = myy * srcPts[srcOff++] + ty; 4828 } 4829 return; 4830 case APPLY_SCALE: 4831 mxx = getMxx(); 4832 myy = getMyy(); 4833 while (--numPts >= 0) { 4834 dstPts[dstOff++] = mxx * srcPts[srcOff++]; 4835 dstPts[dstOff++] = myy * srcPts[srcOff++]; 4836 } 4837 return; 4838 case APPLY_TRANSLATE: 4839 tx = getTx(); 4840 ty = getTy(); 4841 while (--numPts >= 0) { 4842 dstPts[dstOff++] = srcPts[srcOff++] + tx; 4843 dstPts[dstOff++] = srcPts[srcOff++] + ty; 4844 } 4845 return; 4846 case APPLY_IDENTITY: 4847 if (srcPts != dstPts || srcOff != dstOff) { 4848 System.arraycopy(srcPts, srcOff, dstPts, dstOff, 4849 numPts * 2); 4850 } 4851 return; 4852 } 4853 } 4854 4855 @Override 4856 void transform3DPointsImpl(double[] srcPts, int srcOff, 4857 double[] dstPts, int dstOff, int numPts) { 4858 4859 double mxx, mxy, tx, myx, myy, ty, mzz, tz; 4860 4861 switch(state3d) { 4862 default: 4863 stateError(); 4864 // cannot reach 4865 case APPLY_NON_3D: 4866 switch (state2d) { 4867 default: 4868 stateError(); 4869 // cannot reach 4870 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 4871 mxx = getMxx(); mxy = getMxy(); tx = getTx(); 4872 myx = getMyx(); myy = getMyy(); ty = getTy(); 4873 while (--numPts >= 0) { 4874 final double x = srcPts[srcOff++]; 4875 final double y = srcPts[srcOff++]; 4876 dstPts[dstOff++] = mxx * x + mxy * y + tx; 4877 dstPts[dstOff++] = myx * x + myy * y + ty; 4878 dstPts[dstOff++] = srcPts[srcOff++]; 4879 } 4880 return; 4881 case APPLY_SHEAR | APPLY_SCALE: 4882 mxx = getMxx(); mxy = getMxy(); 4883 myx = getMyx(); myy = getMyy(); 4884 while (--numPts >= 0) { 4885 final double x = srcPts[srcOff++]; 4886 final double y = srcPts[srcOff++]; 4887 dstPts[dstOff++] = mxx * x + mxy * y; 4888 dstPts[dstOff++] = myx * x + myy * y; 4889 dstPts[dstOff++] = srcPts[srcOff++]; 4890 } 4891 return; 4892 case APPLY_SHEAR | APPLY_TRANSLATE: 4893 mxy = getMxy(); tx = getTx(); 4894 myx = getMyx(); ty = getTy(); 4895 while (--numPts >= 0) { 4896 final double x = srcPts[srcOff++]; 4897 dstPts[dstOff++] = mxy * srcPts[srcOff++] + tx; 4898 dstPts[dstOff++] = myx * x + ty; 4899 dstPts[dstOff++] = srcPts[srcOff++]; 4900 } 4901 return; 4902 case APPLY_SHEAR: 4903 mxy = getMxy(); 4904 myx = getMyx(); 4905 while (--numPts >= 0) { 4906 final double x = srcPts[srcOff++]; 4907 dstPts[dstOff++] = mxy * srcPts[srcOff++]; 4908 dstPts[dstOff++] = myx * x; 4909 dstPts[dstOff++] = srcPts[srcOff++]; 4910 } 4911 return; 4912 case APPLY_SCALE | APPLY_TRANSLATE: 4913 mxx = getMxx(); tx = getTx(); 4914 myy = getMyy(); ty = getTy(); 4915 while (--numPts >= 0) { 4916 dstPts[dstOff++] = mxx * srcPts[srcOff++] + tx; 4917 dstPts[dstOff++] = myy * srcPts[srcOff++] + ty; 4918 dstPts[dstOff++] = srcPts[srcOff++]; 4919 } 4920 return; 4921 case APPLY_SCALE: 4922 mxx = getMxx(); 4923 myy = getMyy(); 4924 while (--numPts >= 0) { 4925 dstPts[dstOff++] = mxx * srcPts[srcOff++]; 4926 dstPts[dstOff++] = myy * srcPts[srcOff++]; 4927 dstPts[dstOff++] = srcPts[srcOff++]; 4928 } 4929 return; 4930 case APPLY_TRANSLATE: 4931 tx = getTx(); 4932 ty = getTy(); 4933 while (--numPts >= 0) { 4934 dstPts[dstOff++] = srcPts[srcOff++] + tx; 4935 dstPts[dstOff++] = srcPts[srcOff++] + ty; 4936 dstPts[dstOff++] = srcPts[srcOff++]; 4937 } 4938 return; 4939 case APPLY_IDENTITY: 4940 if (srcPts != dstPts || srcOff != dstOff) { 4941 System.arraycopy(srcPts, srcOff, dstPts, dstOff, 4942 numPts * 3); 4943 } 4944 return; 4945 } 4946 // cannot reach 4947 case APPLY_TRANSLATE: 4948 tx = getTx(); 4949 ty = getTy(); 4950 tz = getTz(); 4951 while (--numPts >= 0) { 4952 dstPts[dstOff++] = srcPts[srcOff++] + tx; 4953 dstPts[dstOff++] = srcPts[srcOff++] + ty; 4954 dstPts[dstOff++] = srcPts[srcOff++] + tz; 4955 } 4956 return; 4957 case APPLY_SCALE: 4958 mxx = getMxx(); 4959 myy = getMyy(); 4960 mzz = getMzz(); 4961 while (--numPts >= 0) { 4962 dstPts[dstOff++] = mxx * srcPts[srcOff++]; 4963 dstPts[dstOff++] = myy * srcPts[srcOff++]; 4964 dstPts[dstOff++] = mzz * srcPts[srcOff++]; 4965 } 4966 return; 4967 case APPLY_SCALE | APPLY_TRANSLATE: 4968 mxx = getMxx(); tx = getTx(); 4969 myy = getMyy(); ty = getTy(); 4970 mzz = getMzz(); tz = getTz(); 4971 while (--numPts >= 0) { 4972 dstPts[dstOff++] = mxx * srcPts[srcOff++] + tx; 4973 dstPts[dstOff++] = myy * srcPts[srcOff++] + ty; 4974 dstPts[dstOff++] = mzz * srcPts[srcOff++] + tz; 4975 } 4976 return; 4977 case APPLY_3D_COMPLEX: 4978 mxx = getMxx(); 4979 mxy = getMxy(); 4980 double mxz = getMxz(); 4981 tx = getTx(); 4982 myx = getMyx(); 4983 myy = getMyy(); 4984 double myz = getMyz(); 4985 ty = getTy(); 4986 double mzx = getMzx(); 4987 double mzy = getMzy(); 4988 mzz = getMzz(); 4989 tz = getTz(); 4990 4991 while (--numPts >= 0) { 4992 final double x = srcPts[srcOff++]; 4993 final double y = srcPts[srcOff++]; 4994 final double z = srcPts[srcOff++]; 4995 4996 dstPts[dstOff++] = mxx * x + mxy * y + mxz * z + tx; 4997 dstPts[dstOff++] = myx * x + myy * y + myz * z + ty; 4998 dstPts[dstOff++] = mzx * x + mzy * y + mzz * z + tz; 4999 } 5000 return; 5001 } 5002 } 5003 5004 @Override 5005 public Point2D deltaTransform(double x, double y) { 5006 ensureCanTransform2DPoint(); 5007 5008 switch (state2d) { 5009 default: 5010 stateError(); 5011 // cannot reach 5012 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 5013 case APPLY_SHEAR | APPLY_SCALE: 5014 return new Point2D( 5015 getMxx() * x + getMxy() * y, 5016 getMyx() * x + getMyy() * y); 5017 case APPLY_SHEAR | APPLY_TRANSLATE: 5018 case APPLY_SHEAR: 5019 return new Point2D(getMxy() * y, getMyx() * x); 5020 case APPLY_SCALE | APPLY_TRANSLATE: 5021 case APPLY_SCALE: 5022 return new Point2D(getMxx() * x, getMyy() * y); 5023 case APPLY_TRANSLATE: 5024 case APPLY_IDENTITY: 5025 return new Point2D(x, y); 5026 } 5027 } 5028 5029 @Override 5030 public Point3D deltaTransform(double x, double y, double z) { 5031 switch (state3d) { 5032 default: 5033 stateError(); 5034 // cannot reach 5035 case APPLY_NON_3D: 5036 switch (state2d) { 5037 default: 5038 stateError(); 5039 // cannot reach 5040 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: 5041 case APPLY_SHEAR | APPLY_SCALE: 5042 return new Point3D( 5043 getMxx() * x + getMxy() * y, 5044 getMyx() * x + getMyy() * y, z); 5045 case APPLY_SHEAR | APPLY_TRANSLATE: 5046 case APPLY_SHEAR: 5047 return new Point3D(getMxy() * y, getMyx() * x, z); 5048 case APPLY_SCALE | APPLY_TRANSLATE: 5049 case APPLY_SCALE: 5050 return new Point3D(getMxx() * x, getMyy() * y, z); 5051 case APPLY_TRANSLATE: 5052 case APPLY_IDENTITY: 5053 return new Point3D(x, y, z); 5054 } 5055 case APPLY_TRANSLATE: 5056 return new Point3D(x, y, z); 5057 case APPLY_SCALE: 5058 case APPLY_SCALE | APPLY_TRANSLATE: 5059 return new Point3D(getMxx() * x, getMyy() * y, getMzz() * z); 5060 case APPLY_3D_COMPLEX: 5061 return new Point3D( 5062 getMxx() * x + getMxy() * y + getMxz() * z, 5063 getMyx() * x + getMyy() * y + getMyz() * z, 5064 getMzx() * x + getMzy() * y + getMzz() * z); 5065 } 5066 } 5067 5068 @Override 5069 public Point2D inverseTransform(double x, double y) 5070 throws NonInvertibleTransformException { 5071 ensureCanTransform2DPoint(); 5072 5073 switch (state2d) { 5074 default: 5075 return super.inverseTransform(x, y); 5076 case APPLY_SHEAR | APPLY_TRANSLATE: 5077 final double mxy_st = getMxy(); 5078 final double myx_st = getMyx(); 5079 if (mxy_st == 0.0 || myx_st == 0.0) { 5080 throw new NonInvertibleTransformException("Determinant is 0"); 5081 } 5082 return new Point2D( 5083 (1.0 / myx_st) * y - getTy() / myx_st, 5084 (1.0 / mxy_st) * x - getTx() / mxy_st); 5085 case APPLY_SHEAR: 5086 final double mxy_s = getMxy(); 5087 final double myx_s = getMyx(); 5088 if (mxy_s == 0.0 || myx_s == 0.0) { 5089 throw new NonInvertibleTransformException("Determinant is 0"); 5090 } 5091 return new Point2D((1.0 / myx_s) * y, (1.0 / mxy_s) * x); 5092 case APPLY_SCALE | APPLY_TRANSLATE: 5093 final double mxx_st = getMxx(); 5094 final double myy_st = getMyy(); 5095 if (mxx_st == 0.0 || myy_st == 0.0) { 5096 throw new NonInvertibleTransformException("Determinant is 0"); 5097 } 5098 return new Point2D( 5099 (1.0 / mxx_st) * x - getTx() / mxx_st, 5100 (1.0 / myy_st) * y - getTy() / myy_st); 5101 case APPLY_SCALE: 5102 final double mxx_s = getMxx(); 5103 final double myy_s = getMyy(); 5104 if (mxx_s == 0.0 || myy_s == 0.0) { 5105 throw new NonInvertibleTransformException("Determinant is 0"); 5106 } 5107 return new Point2D((1.0 / mxx_s) * x, (1.0 / myy_s) * y); 5108 case APPLY_TRANSLATE: 5109 return new Point2D(x - getTx(), y - getTy()); 5110 case APPLY_IDENTITY: 5111 return new Point2D(x, y); 5112 } 5113 } 5114 5115 @Override 5116 public Point3D inverseTransform(double x, double y, double z) 5117 throws NonInvertibleTransformException { 5118 switch(state3d) { 5119 default: 5120 stateError(); 5121 // cannot reach 5122 case APPLY_NON_3D: 5123 switch (state2d) { 5124 default: 5125 return super.inverseTransform(x, y, z); 5126 case APPLY_SHEAR | APPLY_TRANSLATE: 5127 final double mxy_st = getMxy(); 5128 final double myx_st = getMyx(); 5129 if (mxy_st == 0.0 || myx_st == 0.0) { 5130 throw new NonInvertibleTransformException( 5131 "Determinant is 0"); 5132 } 5133 return new Point3D( 5134 (1.0 / myx_st) * y - getTy() / myx_st, 5135 (1.0 / mxy_st) * x - getTx() / mxy_st, z); 5136 case APPLY_SHEAR: 5137 final double mxy_s = getMxy(); 5138 final double myx_s = getMyx(); 5139 if (mxy_s == 0.0 || myx_s == 0.0) { 5140 throw new NonInvertibleTransformException( 5141 "Determinant is 0"); 5142 } 5143 return new Point3D( 5144 (1.0 / myx_s) * y, 5145 (1.0 / mxy_s) * x, z); 5146 case APPLY_SCALE | APPLY_TRANSLATE: 5147 final double mxx_st = getMxx(); 5148 final double myy_st = getMyy(); 5149 if (mxx_st == 0.0 || myy_st == 0.0) { 5150 throw new NonInvertibleTransformException( 5151 "Determinant is 0"); 5152 } 5153 return new Point3D( 5154 (1.0 / mxx_st) * x - getTx() / mxx_st, 5155 (1.0 / myy_st) * y - getTy() / myy_st, z); 5156 case APPLY_SCALE: 5157 final double mxx_s = getMxx(); 5158 final double myy_s = getMyy(); 5159 if (mxx_s == 0.0 || myy_s == 0.0) { 5160 throw new NonInvertibleTransformException( 5161 "Determinant is 0"); 5162 } 5163 return new Point3D((1.0 / mxx_s) * x, (1.0 / myy_s) * y, z); 5164 case APPLY_TRANSLATE: 5165 return new Point3D(x - getTx(), y - getTy(), z); 5166 case APPLY_IDENTITY: 5167 return new Point3D(x, y, z); 5168 } 5169 case APPLY_TRANSLATE: 5170 return new Point3D(x - getTx(), y - getTy(), z - getTz()); 5171 case APPLY_SCALE: 5172 final double mxx_s = getMxx(); 5173 final double myy_s = getMyy(); 5174 final double mzz_s = getMzz(); 5175 if (mxx_s == 0.0 || myy_s == 0.0 || mzz_s == 0.0) { 5176 throw new NonInvertibleTransformException("Determinant is 0"); 5177 } 5178 return new Point3D( 5179 (1.0 / mxx_s) * x, 5180 (1.0 / myy_s) * y, 5181 (1.0 / mzz_s) * z); 5182 case APPLY_SCALE | APPLY_TRANSLATE: 5183 final double mxx_st = getMxx(); 5184 final double myy_st = getMyy(); 5185 final double mzz_st = getMzz(); 5186 if (mxx_st == 0.0 || myy_st == 0.0 || mzz_st == 0.0) { 5187 throw new NonInvertibleTransformException("Determinant is 0"); 5188 } 5189 return new Point3D( 5190 (1.0 / mxx_st) * x - getTx() / mxx_st, 5191 (1.0 / myy_st) * y - getTy() / myy_st, 5192 (1.0 / mzz_st) * z - getTz() / mzz_st); 5193 case APPLY_3D_COMPLEX: 5194 return super.inverseTransform(x, y, z); 5195 } 5196 } 5197 5198 @Override 5199 void inverseTransform2DPointsImpl(double[] srcPts, int srcOff, 5200 double[] dstPts, int dstOff, int numPts) 5201 throws NonInvertibleTransformException { 5202 5203 double mxx, mxy, tx, myx, myy, ty, tmp; 5204 5205 switch (state2d) { 5206 default: 5207 super.inverseTransform2DPointsImpl(srcPts, srcOff, 5208 dstPts, dstOff, numPts); 5209 return; 5210 5211 case APPLY_SHEAR | APPLY_TRANSLATE: 5212 mxy = getMxy(); tx = getTx(); 5213 myx = getMyx(); ty = getTy(); 5214 if (mxy == 0.0 || myx == 0.0) { 5215 throw new NonInvertibleTransformException("Determinant is 0"); 5216 } 5217 5218 tmp = tx; 5219 tx = -ty / myx; 5220 ty = -tmp / mxy; 5221 5222 tmp = myx; 5223 myx = 1.0 / mxy; 5224 mxy = 1.0 / tmp; 5225 5226 while (--numPts >= 0) { 5227 final double x = srcPts[srcOff++]; 5228 dstPts[dstOff++] = mxy * srcPts[srcOff++] + tx; 5229 dstPts[dstOff++] = myx * x + ty; 5230 } 5231 return; 5232 case APPLY_SHEAR: 5233 mxy = getMxy(); 5234 myx = getMyx(); 5235 if (mxy == 0.0 || myx == 0.0) { 5236 throw new NonInvertibleTransformException("Determinant is 0"); 5237 } 5238 5239 tmp = myx; 5240 myx = 1.0 / mxy; 5241 mxy = 1.0 / tmp; 5242 5243 while (--numPts >= 0) { 5244 final double x = srcPts[srcOff++]; 5245 dstPts[dstOff++] = mxy * srcPts[srcOff++]; 5246 dstPts[dstOff++] = myx * x; 5247 } 5248 return; 5249 case APPLY_SCALE | APPLY_TRANSLATE: 5250 mxx = getMxx(); tx = getTx(); 5251 myy = getMyy(); ty = getTy(); 5252 if (mxx == 0.0 || myy == 0.0) { 5253 throw new NonInvertibleTransformException("Determinant is 0"); 5254 } 5255 5256 tx = -tx / mxx; 5257 ty = -ty / myy; 5258 mxx = 1.0 / mxx; 5259 myy = 1.0 / myy; 5260 5261 while (--numPts >= 0) { 5262 dstPts[dstOff++] = mxx * srcPts[srcOff++] + tx; 5263 dstPts[dstOff++] = myy * srcPts[srcOff++] + ty; 5264 } 5265 return; 5266 case APPLY_SCALE: 5267 mxx = getMxx(); 5268 myy = getMyy(); 5269 if (mxx == 0.0 || myy == 0.0) { 5270 throw new NonInvertibleTransformException("Determinant is 0"); 5271 } 5272 5273 mxx = 1.0 / mxx; 5274 myy = 1.0 / myy; 5275 5276 while (--numPts >= 0) { 5277 dstPts[dstOff++] = mxx * srcPts[srcOff++]; 5278 dstPts[dstOff++] = myy * srcPts[srcOff++]; 5279 } 5280 return; 5281 case APPLY_TRANSLATE: 5282 tx = getTx(); 5283 ty = getTy(); 5284 while (--numPts >= 0) { 5285 dstPts[dstOff++] = srcPts[srcOff++] - tx; 5286 dstPts[dstOff++] = srcPts[srcOff++] - ty; 5287 } 5288 return; 5289 case APPLY_IDENTITY: 5290 if (srcPts != dstPts || srcOff != dstOff) { 5291 System.arraycopy(srcPts, srcOff, dstPts, dstOff, 5292 numPts * 2); 5293 } 5294 return; 5295 } 5296 } 5297 5298 @Override 5299 void inverseTransform3DPointsImpl(double[] srcPts, int srcOff, 5300 double[] dstPts, int dstOff, int numPts) 5301 throws NonInvertibleTransformException { 5302 5303 double mxx, mxy, tx, myx, myy, ty, mzz, tz, tmp; 5304 5305 switch (state3d) { 5306 default: 5307 stateError(); 5308 // cannot reach 5309 case APPLY_NON_3D: 5310 switch (state2d) { 5311 default: 5312 super.inverseTransform3DPointsImpl(srcPts, srcOff, 5313 dstPts, dstOff, numPts); 5314 return; 5315 5316 case APPLY_SHEAR | APPLY_TRANSLATE: 5317 mxy = getMxy(); tx = getTx(); 5318 myx = getMyx(); ty = getTy(); 5319 if (mxy == 0.0 || myx == 0.0) { 5320 throw new NonInvertibleTransformException( 5321 "Determinant is 0"); 5322 } 5323 5324 tmp = tx; 5325 tx = -ty / myx; 5326 ty = -tmp / mxy; 5327 5328 tmp = myx; 5329 myx = 1.0 / mxy; 5330 mxy = 1.0 / tmp; 5331 5332 while (--numPts >= 0) { 5333 final double x = srcPts[srcOff++]; 5334 dstPts[dstOff++] = mxy * srcPts[srcOff++] + tx; 5335 dstPts[dstOff++] = myx * x + ty; 5336 dstPts[dstOff++] = srcPts[srcOff++]; 5337 } 5338 return; 5339 case APPLY_SHEAR: 5340 mxy = getMxy(); 5341 myx = getMyx(); 5342 if (mxy == 0.0 || myx == 0.0) { 5343 throw new NonInvertibleTransformException( 5344 "Determinant is 0"); 5345 } 5346 5347 tmp = myx; 5348 myx = 1.0 / mxy; 5349 mxy = 1.0 / tmp; 5350 5351 while (--numPts >= 0) { 5352 final double x = srcPts[srcOff++]; 5353 dstPts[dstOff++] = mxy * srcPts[srcOff++]; 5354 dstPts[dstOff++] = myx * x; 5355 dstPts[dstOff++] = srcPts[srcOff++]; 5356 } 5357 return; 5358 case APPLY_SCALE | APPLY_TRANSLATE: 5359 mxx = getMxx(); tx = getTx(); 5360 myy = getMyy(); ty = getTy(); 5361 if (mxx == 0.0 || myy == 0.0) { 5362 throw new NonInvertibleTransformException( 5363 "Determinant is 0"); 5364 } 5365 5366 tx = -tx / mxx; 5367 ty = -ty / myy; 5368 mxx = 1.0 / mxx; 5369 myy = 1.0 / myy; 5370 5371 while (--numPts >= 0) { 5372 dstPts[dstOff++] = mxx * srcPts[srcOff++] + tx; 5373 dstPts[dstOff++] = myy * srcPts[srcOff++] + ty; 5374 dstPts[dstOff++] = srcPts[srcOff++]; 5375 } 5376 return; 5377 case APPLY_SCALE: 5378 mxx = getMxx(); 5379 myy = getMyy(); 5380 if (mxx == 0.0 || myy == 0.0) { 5381 throw new NonInvertibleTransformException( 5382 "Determinant is 0"); 5383 } 5384 5385 mxx = 1.0 / mxx; 5386 myy = 1.0 / myy; 5387 5388 while (--numPts >= 0) { 5389 dstPts[dstOff++] = mxx * srcPts[srcOff++]; 5390 dstPts[dstOff++] = myy * srcPts[srcOff++]; 5391 dstPts[dstOff++] = srcPts[srcOff++]; 5392 } 5393 return; 5394 case APPLY_TRANSLATE: 5395 tx = getTx(); 5396 ty = getTy(); 5397 while (--numPts >= 0) { 5398 dstPts[dstOff++] = srcPts[srcOff++] - tx; 5399 dstPts[dstOff++] = srcPts[srcOff++] - ty; 5400 dstPts[dstOff++] = srcPts[srcOff++]; 5401 } 5402 return; 5403 case APPLY_IDENTITY: 5404 if (srcPts != dstPts || srcOff != dstOff) { 5405 System.arraycopy(srcPts, srcOff, dstPts, dstOff, 5406 numPts * 3); 5407 } 5408 return; 5409 } 5410 // cannot reach 5411 case APPLY_TRANSLATE: 5412 tx = getTx(); 5413 ty = getTy(); 5414 tz = getTz(); 5415 while (--numPts >= 0) { 5416 dstPts[dstOff++] = srcPts[srcOff++] - tx; 5417 dstPts[dstOff++] = srcPts[srcOff++] - ty; 5418 dstPts[dstOff++] = srcPts[srcOff++] - tz; 5419 } 5420 return; 5421 case APPLY_SCALE: 5422 mxx = getMxx(); 5423 myy = getMyy(); 5424 mzz = getMzz(); 5425 if (mxx == 0.0 || myy == 0.0 | mzz == 0.0) { 5426 throw new NonInvertibleTransformException("Determinant is 0"); 5427 } 5428 5429 mxx = 1.0 / mxx; 5430 myy = 1.0 / myy; 5431 mzz = 1.0 / mzz; 5432 5433 while (--numPts >= 0) { 5434 dstPts[dstOff++] = mxx * srcPts[srcOff++]; 5435 dstPts[dstOff++] = myy * srcPts[srcOff++]; 5436 dstPts[dstOff++] = mzz * srcPts[srcOff++]; 5437 } 5438 return; 5439 case APPLY_SCALE | APPLY_TRANSLATE: 5440 mxx = getMxx(); tx = getTx(); 5441 myy = getMyy(); ty = getTy(); 5442 mzz = getMzz(); tz = getTz(); 5443 if (mxx == 0.0 || myy == 0.0 || mzz == 0.0) { 5444 throw new NonInvertibleTransformException("Determinant is 0"); 5445 } 5446 5447 tx = -tx / mxx; 5448 ty = -ty / myy; 5449 tz = -tz / mzz; 5450 mxx = 1.0 / mxx; 5451 myy = 1.0 / myy; 5452 mzz = 1.0 / mzz; 5453 5454 while (--numPts >= 0) { 5455 dstPts[dstOff++] = mxx * srcPts[srcOff++] + tx; 5456 dstPts[dstOff++] = myy * srcPts[srcOff++] + ty; 5457 dstPts[dstOff++] = mzz * srcPts[srcOff++] + tz; 5458 } 5459 return; 5460 case APPLY_3D_COMPLEX: 5461 super.inverseTransform3DPointsImpl(srcPts, srcOff, 5462 dstPts, dstOff, numPts); 5463 return; 5464 } 5465 } 5466 5467 @Override 5468 public Point2D inverseDeltaTransform(double x, double y) 5469 throws NonInvertibleTransformException { 5470 ensureCanTransform2DPoint(); 5471 5472 switch (state2d) { 5473 default: 5474 return super.inverseDeltaTransform(x, y); 5475 case APPLY_SHEAR | APPLY_TRANSLATE: 5476 case APPLY_SHEAR: 5477 final double mxy_s = getMxy(); 5478 final double myx_s = getMyx(); 5479 if (mxy_s == 0.0 || myx_s == 0.0) { 5480 throw new NonInvertibleTransformException("Determinant is 0"); 5481 } 5482 return new Point2D((1.0 / myx_s) * y, (1.0 / mxy_s) * x); 5483 case APPLY_SCALE | APPLY_TRANSLATE: 5484 case APPLY_SCALE: 5485 final double mxx_s = getMxx(); 5486 final double myy_s = getMyy(); 5487 if (mxx_s == 0.0 || myy_s == 0.0) { 5488 throw new NonInvertibleTransformException("Determinant is 0"); 5489 } 5490 return new Point2D((1.0 / mxx_s) * x, (1.0 / myy_s) * y); 5491 case APPLY_TRANSLATE: 5492 case APPLY_IDENTITY: 5493 return new Point2D(x, y); 5494 } 5495 } 5496 5497 @Override 5498 public Point3D inverseDeltaTransform(double x, double y, double z) 5499 throws NonInvertibleTransformException { 5500 switch(state3d) { 5501 default: 5502 stateError(); 5503 // cannot reach 5504 case APPLY_NON_3D: 5505 switch (state2d) { 5506 default: 5507 return super.inverseDeltaTransform(x, y, z); 5508 case APPLY_SHEAR | APPLY_TRANSLATE: 5509 case APPLY_SHEAR: 5510 final double mxy_s = getMxy(); 5511 final double myx_s = getMyx(); 5512 if (mxy_s == 0.0 || myx_s == 0.0) { 5513 throw new NonInvertibleTransformException( 5514 "Determinant is 0"); 5515 } 5516 return new Point3D( 5517 (1.0 / myx_s) * y, 5518 (1.0 / mxy_s) * x, z); 5519 case APPLY_SCALE | APPLY_TRANSLATE: 5520 case APPLY_SCALE: 5521 final double mxx_s = getMxx(); 5522 final double myy_s = getMyy(); 5523 if (mxx_s == 0.0 || myy_s == 0.0) { 5524 throw new NonInvertibleTransformException( 5525 "Determinant is 0"); 5526 } 5527 return new Point3D( 5528 (1.0 / mxx_s) * x, 5529 (1.0 / myy_s) * y, z); 5530 case APPLY_TRANSLATE: 5531 case APPLY_IDENTITY: 5532 return new Point3D(x, y, z); 5533 } 5534 5535 case APPLY_TRANSLATE: 5536 return new Point3D(x, y, z); 5537 case APPLY_SCALE | APPLY_TRANSLATE: 5538 case APPLY_SCALE: 5539 final double mxx_s = getMxx(); 5540 final double myy_s = getMyy(); 5541 final double mzz_s = getMzz(); 5542 if (mxx_s == 0.0 || myy_s == 0.0 || mzz_s == 0.0) { 5543 throw new NonInvertibleTransformException("Determinant is 0"); 5544 } 5545 return new Point3D( 5546 (1.0 / mxx_s) * x, 5547 (1.0 / myy_s) * y, 5548 (1.0 / mzz_s) * z); 5549 case APPLY_3D_COMPLEX: 5550 return super.inverseDeltaTransform(x, y, z); 5551 } 5552 } 5553 5554 /* ************************************************************************* 5555 * * 5556 * Other API * 5557 * * 5558 **************************************************************************/ 5559 5560 /** 5561 * Returns a string representation of this {@code Affine} object. 5562 * @return a string representation of this {@code Affine} object. 5563 */ 5564 @Override 5565 public String toString() { 5566 final StringBuilder sb = new StringBuilder("Affine [\n"); 5567 5568 sb.append("\t").append(getMxx()); 5569 sb.append(", ").append(getMxy()); 5570 sb.append(", ").append(getMxz()); 5571 sb.append(", ").append(getTx()); 5572 sb.append('\n'); 5573 sb.append("\t").append(getMyx()); 5574 sb.append(", ").append(getMyy()); 5575 sb.append(", ").append(getMyz()); 5576 sb.append(", ").append(getTy()); 5577 sb.append('\n'); 5578 sb.append("\t").append(getMzx()); 5579 sb.append(", ").append(getMzy()); 5580 sb.append(", ").append(getMzz()); 5581 sb.append(", ").append(getTz()); 5582 5583 return sb.append("\n]").toString(); 5584 } 5585 5586 /* ************************************************************************* 5587 * * 5588 * Internal implementation stuff * 5589 * * 5590 **************************************************************************/ 5591 5592 /** 5593 * Manually recalculates the state of the transform when the matrix 5594 * changes too much to predict the effects on the state. 5595 * The following tables specify what the various settings of the 5596 * state fields say about the values of the corresponding matrix 5597 * element fields. 5598 * 5599 * <h4>state3d:</h4> 5600 * <pre> 5601 * SCALE TRANSLATE OTHER ELEMENTS 5602 * mxx, myy, mzz tx, ty, tz all remaining 5603 * 5604 * TRANSLATE (TR) 1.0 not all 0.0 0.0 5605 * SCALE (SC) not all 1.0 0.0 0.0 5606 * TR | SC not all 1.0 not all 0.0 0.0 5607 * 3D_COMPLEX any any not all 0.0 5608 * NON_3D: mxz, myz, mzx, mzy, tz are 0.0, mzz is 1.0, for the rest 5609 * see state2d 5610 * </pre> 5611 * 5612 * <h4>state2d:</h4> 5613 * Contains meaningful value only if state3d == APPLY_NON_3D. 5614 * Note that the rules governing the SCALE fields are slightly 5615 * different depending on whether the SHEAR flag is also set. 5616 * <pre> 5617 * SCALE SHEAR TRANSLATE 5618 * mxx/myy mxy/myx tx/ty 5619 * 5620 * IDENTITY 1.0 0.0 0.0 5621 * TRANSLATE (TR) 1.0 0.0 not both 0.0 5622 * SCALE (SC) not both 1.0 0.0 0.0 5623 * TR | SC not both 1.0 0.0 not both 0.0 5624 * SHEAR (SH) 0.0 not both 0.0 0.0 5625 * TR | SH 0.0 not both 0.0 not both 0.0 5626 * SC | SH not both 0.0 not both 0.0 0.0 5627 * TR | SC | SH not both 0.0 not both 0.0 not both 0.0 5628 * </pre> 5629 */ 5630 private void updateState() { 5631 updateState2D(); 5632 5633 state3d = APPLY_NON_3D; 5634 5635 if (getMxz() != 0.0 || 5636 getMyz() != 0.0 || 5637 getMzx() != 0.0 || 5638 getMzy() != 0.0) 5639 { 5640 state3d = APPLY_3D_COMPLEX; 5641 } else { 5642 if ((state2d & APPLY_SHEAR) == 0) { 5643 if (getTz() != 0.0) { 5644 state3d |= APPLY_TRANSLATE; 5645 } 5646 if (getMzz() != 1.0) { 5647 state3d |= APPLY_SCALE; 5648 } 5649 if (state3d != APPLY_NON_3D) { 5650 state3d |= (state2d & (APPLY_SCALE | APPLY_TRANSLATE)); 5651 } 5652 } else { 5653 if (getMzz() != 1.0 || getTz() != 0.0) { 5654 state3d = APPLY_3D_COMPLEX; 5655 } 5656 } 5657 } 5658 } 5659 5660 /** 5661 * 2D part of {@code updateState()}. It is sufficient to call this method 5662 * when we know this this a 2D transform and the operation was 2D-only 5663 * so it could not switch the transform to 3D. 5664 */ 5665 private void updateState2D() { 5666 if (getMxy() == 0.0 && getMyx() == 0.0) { 5667 if (getMxx() == 1.0 && getMyy() == 1.0) { 5668 if (getTx() == 0.0 && getTy() == 0.0) { 5669 state2d = APPLY_IDENTITY; 5670 } else { 5671 state2d = APPLY_TRANSLATE; 5672 } 5673 } else { 5674 if (getTx() == 0.0 && getTy() == 0.0) { 5675 state2d = APPLY_SCALE; 5676 } else { 5677 state2d = (APPLY_SCALE | APPLY_TRANSLATE); 5678 } 5679 } 5680 } else { 5681 if (getMxx() == 0.0 && getMyy() == 0.0) { 5682 if (getTx() == 0.0 && getTy() == 0.0) { 5683 state2d = APPLY_SHEAR; 5684 } else { 5685 state2d = (APPLY_SHEAR | APPLY_TRANSLATE); 5686 } 5687 } else { 5688 if (getTx() == 0.0 && getTy() == 0.0) { 5689 state2d = (APPLY_SHEAR | APPLY_SCALE); 5690 } else { 5691 state2d = (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE); 5692 } 5693 } 5694 } 5695 } 5696 5697 /** 5698 * Convenience method used internally to throw exceptions when 5699 * a case was forgotten in a switch statement. 5700 */ 5701 private static void stateError() { 5702 throw new InternalError("missing case in a switch"); 5703 } 5704 5705 @Override 5706 void apply(final Affine3D trans) { 5707 trans.concatenate(getMxx(), getMxy(), getMxz(), getTx(), 5708 getMyx(), getMyy(), getMyz(), getTy(), 5709 getMzx(), getMzy(), getMzz(), getTz()); 5710 } 5711 5712 @Override 5713 BaseTransform derive(final BaseTransform trans) { 5714 switch(state3d) { 5715 default: 5716 stateError(); 5717 // cannot reach 5718 case APPLY_NON_3D: 5719 switch(state2d) { 5720 case APPLY_IDENTITY: 5721 return trans; 5722 case APPLY_TRANSLATE: 5723 return trans.deriveWithTranslation(getTx(), getTy()); 5724 case APPLY_SCALE: 5725 return trans.deriveWithScale(getMxx(), getMyy(), 1.0); 5726 case APPLY_SCALE | APPLY_TRANSLATE: 5727 // fall through 5728 default: 5729 return trans.deriveWithConcatenation( 5730 getMxx(), getMyx(), 5731 getMxy(), getMyy(), 5732 getTx(), getTy()); 5733 } 5734 case APPLY_TRANSLATE: 5735 return trans.deriveWithTranslation(getTx(), getTy(), getTz()); 5736 case APPLY_SCALE: 5737 return trans.deriveWithScale(getMxx(), getMyy(), getMzz()); 5738 case APPLY_SCALE | APPLY_TRANSLATE: 5739 // fall through 5740 case APPLY_3D_COMPLEX: 5741 return trans.deriveWithConcatenation( 5742 getMxx(), getMxy(), getMxz(), getTx(), 5743 getMyx(), getMyy(), getMyz(), getTy(), 5744 getMzx(), getMzy(), getMzz(), getTz()); 5745 } 5746 } 5747 5748 /** 5749 * Keeps track of the atomic changes of more elements. 5750 * Don't forget to end or cancel a running atomic operation 5751 * when an exception is to be thrown during one. 5752 */ 5753 private class AffineAtomicChange { 5754 private boolean running = false; 5755 5756 private void start() { 5757 if (running) { 5758 throw new InternalError("Affine internal error: " 5759 + "trying to run inner atomic operation"); 5760 } 5761 if (mxx != null) mxx.preProcessAtomicChange(); 5762 if (mxy != null) mxy.preProcessAtomicChange(); 5763 if (mxz != null) mxz.preProcessAtomicChange(); 5764 if (tx != null) tx.preProcessAtomicChange(); 5765 if (myx != null) myx.preProcessAtomicChange(); 5766 if (myy != null) myy.preProcessAtomicChange(); 5767 if (myz != null) myz.preProcessAtomicChange(); 5768 if (ty != null) ty.preProcessAtomicChange(); 5769 if (mzx != null) mzx.preProcessAtomicChange(); 5770 if (mzy != null) mzy.preProcessAtomicChange(); 5771 if (mzz != null) mzz.preProcessAtomicChange(); 5772 if (tz != null) tz.preProcessAtomicChange(); 5773 running = true; 5774 } 5775 5776 private void end() { 5777 running = false; 5778 transformChanged(); 5779 if (mxx != null) mxx.postProcessAtomicChange(); 5780 if (mxy != null) mxy.postProcessAtomicChange(); 5781 if (mxz != null) mxz.postProcessAtomicChange(); 5782 if (tx != null) tx.postProcessAtomicChange(); 5783 if (myx != null) myx.postProcessAtomicChange(); 5784 if (myy != null) myy.postProcessAtomicChange(); 5785 if (myz != null) myz.postProcessAtomicChange(); 5786 if (ty != null) ty.postProcessAtomicChange(); 5787 if (mzx != null) mzx.postProcessAtomicChange(); 5788 if (mzy != null) mzy.postProcessAtomicChange(); 5789 if (mzz != null) mzz.postProcessAtomicChange(); 5790 if (tz != null) tz.postProcessAtomicChange(); 5791 } 5792 5793 private void cancel() { 5794 running = false; 5795 } 5796 5797 private boolean runs() { 5798 return running; 5799 } 5800 } 5801 5802 /** 5803 * Used only by tests to check the 2d matrix state 5804 */ 5805 int getState2d() { 5806 return state2d; 5807 } 5808 5809 /** 5810 * Used only by tests to check the 3d matrix state 5811 */ 5812 int getState3d() { 5813 return state3d; 5814 } 5815 5816 /** 5817 * Used only by tests to check the atomic operation state 5818 */ 5819 boolean atomicChangeRuns() { 5820 return atomicChange.runs(); 5821 } 5822 }