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