1 /* 2 * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.transform; 27 28 import javafx.beans.property.DoubleProperty; 29 import javafx.beans.property.DoublePropertyBase; 30 31 import com.sun.javafx.geom.transform.Affine3D; 32 import com.sun.javafx.geom.transform.BaseTransform; 33 import javafx.geometry.Point2D; 34 import javafx.geometry.Point3D; 35 36 37 /** 38 * This class represents an {@code Affine} object that scales coordinates 39 * by the specified factors. The matrix representing the scaling transformation 40 * around a pivot point {@code (pivotX, pivotY, pivotZ)} with scaling factors 41 * {@code x}, {@code y} and {@code z} is as follows: 42 * <pre> 43 * [ x 0 0 (1-x)*pivotX ] 44 * [ 0 y 0 (1-y)*pivotY ] 45 * [ 0 0 z (1-z)*pivotZ ] 46 * </pre> 47 * @since JavaFX 2.0 48 */ 49 public class Scale extends Transform { 50 /** 51 * Creates a default Scale (identity). 52 */ 53 public Scale() { 54 } 55 56 /** 57 * Creates a two-dimensional Scale. 58 * The pivot point is set to (0,0) 59 * @param x the factor by which coordinates are scaled along the X axis 60 * @param y the factor by which coordinates are scaled along the Y axis 61 */ 62 public Scale(double x, double y) { 63 setX(x); 64 setY(y); 65 } 66 67 /** 68 * Creates a two-dimensional Scale with pivot. 69 * @param x the factor by which coordinates are scaled along the X axis 70 * @param y the factor by which coordinates are scaled along the Y axis 71 * @param pivotX the X coordinate about which point the scale occurs 72 * @param pivotY the Y coordinate about which point the scale occurs 73 */ 74 public Scale(double x, double y, double pivotX, double pivotY) { 75 this(x, y); 76 setPivotX(pivotX); 77 setPivotY(pivotY); 78 } 79 80 /** 81 * Creates a three-dimensional Scale. 82 * The pivot point is set to (0,0,0) 83 * @param x the factor by which coordinates are scaled along the X axis 84 * @param y the factor by which coordinates are scaled along the Y axis 85 * @param z the factor by which coordinates are scaled along the Z axis 86 */ 87 public Scale(double x, double y, double z) { 88 this(x, y); 89 setZ(z); 90 } 91 92 /** 93 * Creates a three-dimensional Scale with pivot. 94 * @param x the factor by which coordinates are scaled along the X axis 95 * @param y the factor by which coordinates are scaled along the Y axis 96 * @param z the factor by which coordinates are scaled along the Z axis 97 * @param pivotX the X coordinate about which point the scale occurs 98 * @param pivotY the Y coordinate about which point the scale occurs 99 * @param pivotZ the Z coordinate about which point the scale occurs 100 */ 101 public Scale(double x, double y, double z, double pivotX, double pivotY, double pivotZ) { 102 this(x, y, pivotX, pivotY); 103 setZ(z); 104 setPivotZ(pivotZ); 105 } 106 107 /** 108 * Defines the factor by which coordinates are scaled 109 * along the X axis direction. The default value is {@code 1.0}. 110 */ 111 private DoubleProperty x; 112 113 114 public final void setX(double value) { 115 xProperty().set(value); 116 } 117 118 public final double getX() { 119 return x == null ? 1.0F : x.get(); 120 } 121 122 public final DoubleProperty xProperty() { 123 if (x == null) { 124 x = new DoublePropertyBase(1.0F) { 125 126 @Override 127 public void invalidated() { 128 transformChanged(); 129 } 130 131 @Override 132 public Object getBean() { 133 return Scale.this; 134 } 135 136 @Override 137 public String getName() { 138 return "x"; 139 } 140 }; 141 } 142 return x; 143 } 144 145 /** 146 * Defines the factor by which coordinates are scaled 147 * along the Y axis direction. The default value is {@code 1.0}. 148 */ 149 private DoubleProperty y; 150 151 152 public final void setY(double value) { 153 yProperty().set(value); 154 } 155 156 public final double getY() { 157 return y == null ? 1.0F : y.get(); 158 } 159 160 public final DoubleProperty yProperty() { 161 if (y == null) { 162 y = new DoublePropertyBase(1.0F) { 163 164 @Override 165 public void invalidated() { 166 transformChanged(); 167 } 168 169 @Override 170 public Object getBean() { 171 return Scale.this; 172 } 173 174 @Override 175 public String getName() { 176 return "y"; 177 } 178 }; 179 } 180 return y; 181 } 182 183 /** 184 * Defines the factor by which coordinates are scaled 185 * along the Z axis direction. The default value is {@code 1.0}. 186 */ 187 private DoubleProperty z; 188 189 190 public final void setZ(double value) { 191 zProperty().set(value); 192 } 193 194 public final double getZ() { 195 return z == null ? 1.0F : z.get(); 196 } 197 198 public final DoubleProperty zProperty() { 199 if (z == null) { 200 z = new DoublePropertyBase(1.0F) { 201 202 @Override 203 public void invalidated() { 204 transformChanged(); 205 } 206 207 @Override 208 public Object getBean() { 209 return Scale.this; 210 } 211 212 @Override 213 public String getName() { 214 return "z"; 215 } 216 }; 217 } 218 return z; 219 } 220 221 /** 222 * Defines the X coordinate about which point the scale occurs. 223 * 224 * @defaultValue 0.0 225 */ 226 private DoubleProperty pivotX; 227 228 229 public final void setPivotX(double value) { 230 pivotXProperty().set(value); 231 } 232 233 public final double getPivotX() { 234 return pivotX == null ? 0.0 : pivotX.get(); 235 } 236 237 public final DoubleProperty pivotXProperty() { 238 if (pivotX == null) { 239 pivotX = new DoublePropertyBase() { 240 241 @Override 242 public void invalidated() { 243 transformChanged(); 244 } 245 246 @Override 247 public Object getBean() { 248 return Scale.this; 249 } 250 251 @Override 252 public String getName() { 253 return "pivotX"; 254 } 255 }; 256 } 257 return pivotX; 258 } 259 260 /** 261 * Defines the Y coordinate about which point the scale occurs. 262 * 263 * @defaultValue 0.0 264 */ 265 private DoubleProperty pivotY; 266 267 268 public final void setPivotY(double value) { 269 pivotYProperty().set(value); 270 } 271 272 public final double getPivotY() { 273 return pivotY == null ? 0.0 : pivotY.get(); 274 } 275 276 public final DoubleProperty pivotYProperty() { 277 if (pivotY == null) { 278 pivotY = new DoublePropertyBase() { 279 280 @Override 281 public void invalidated() { 282 transformChanged(); 283 } 284 285 @Override 286 public Object getBean() { 287 return Scale.this; 288 } 289 290 @Override 291 public String getName() { 292 return "pivotY"; 293 } 294 }; 295 } 296 return pivotY; 297 } 298 299 /** 300 * Defines the Z coordinate about which point the scale occurs. 301 * 302 * @defaultValue 0.0 303 */ 304 private DoubleProperty pivotZ; 305 306 307 public final void setPivotZ(double value) { 308 pivotZProperty().set(value); 309 } 310 311 public final double getPivotZ() { 312 return pivotZ == null ? 0.0 : pivotZ.get(); 313 } 314 315 public final DoubleProperty pivotZProperty() { 316 if (pivotZ == null) { 317 pivotZ = new DoublePropertyBase() { 318 319 @Override 320 public void invalidated() { 321 transformChanged(); 322 } 323 324 @Override 325 public Object getBean() { 326 return Scale.this; 327 } 328 329 @Override 330 public String getName() { 331 return "pivotZ"; 332 } 333 }; 334 } 335 return pivotZ; 336 } 337 338 /* ************************************************************************* 339 * * 340 * Element getters * 341 * * 342 **************************************************************************/ 343 344 @Override 345 public double getMxx() { 346 return getX(); 347 } 348 349 @Override 350 public double getMyy() { 351 return getY(); 352 } 353 354 @Override 355 public double getMzz() { 356 return getZ(); 357 } 358 359 @Override 360 public double getTx() { 361 return (1-getX()) * getPivotX(); 362 } 363 364 @Override 365 public double getTy() { 366 return (1-getY()) * getPivotY(); 367 } 368 369 @Override 370 public double getTz() { 371 return (1-getZ()) * getPivotZ(); 372 } 373 374 /* ************************************************************************* 375 * * 376 * State getters * 377 * * 378 **************************************************************************/ 379 380 @Override 381 boolean computeIs2D() { 382 return getZ() == 1.0; 383 } 384 385 @Override 386 boolean computeIsIdentity() { 387 return getX() == 1.0 && getY() == 1.0 && getZ() == 1.0; 388 } 389 390 /* ************************************************************************* 391 * * 392 * Array getters * 393 * * 394 **************************************************************************/ 395 396 @Override 397 void fill2DArray(double[] array) { 398 final double sx = getX(); 399 final double sy = getY(); 400 401 array[0] = sx; 402 array[1] = 0.0; 403 array[2] = (1-sx) * getPivotX(); 404 array[3] = 0.0; 405 array[4] = sy; 406 array[5] = (1-sy) * getPivotY(); 407 } 408 409 @Override 410 void fill3DArray(double[] array) { 411 final double sx = getX(); 412 final double sy = getY(); 413 final double sz = getZ(); 414 415 array[0] = sx; 416 array[1] = 0.0; 417 array[2] = 0.0; 418 array[3] = (1-sx) * getPivotX(); 419 array[4] = 0.0; 420 array[5] = sy; 421 array[6] = 0.0; 422 array[7] = (1-sy) * getPivotY(); 423 array[8] = 0.0; 424 array[9] = 0.0; 425 array[10] = sz; 426 array[11] = (1-sz) * getPivotZ(); 427 } 428 429 /* ************************************************************************* 430 * * 431 * Transform creators * 432 * * 433 **************************************************************************/ 434 435 @Override 436 public Transform createConcatenation(Transform transform) { 437 final double sx = getX(); 438 final double sy = getY(); 439 final double sz = getZ(); 440 441 if (transform instanceof Scale) { 442 final Scale other = (Scale) transform; 443 if (other.getPivotX() == getPivotX() 444 && other.getPivotY() == getPivotY() 445 && other.getPivotZ() == getPivotZ()) { 446 return new Scale( 447 sx * other.getX(), 448 sy * other.getY(), 449 sz * other.getZ(), 450 getPivotX(), getPivotY(), getPivotZ()); 451 } 452 } 453 454 if (transform instanceof Translate) { 455 final Translate t = (Translate) transform; 456 457 final double tx = t.getX(); 458 final double ty = t.getY(); 459 final double tz = t.getZ(); 460 461 if ((tx == 0.0 || (sx != 1.0 && sx != 0.0)) && 462 (ty == 0.0 || (sy != 1.0 && sy != 0.0)) && 463 (tz == 0.0 || (sz != 1.0 && sz != 0.0))) { 464 return new Scale( 465 sx, sy, sz, 466 (sx != 1.0 ? sx * tx / (1 - sx) : 0) + getPivotX(), 467 (sy != 1.0 ? sy * ty / (1 - sy) : 0) + getPivotY(), 468 (sz != 1.0 ? sz * tz / (1 - sz) : 0) + getPivotZ()); 469 } 470 } 471 472 if (transform instanceof Affine) { 473 Affine a = (Affine) transform.clone(); 474 a.prepend(this); 475 return a; 476 } 477 478 final double txx = transform.getMxx(); 479 final double txy = transform.getMxy(); 480 final double txz = transform.getMxz(); 481 final double ttx = transform.getTx(); 482 final double tyx = transform.getMyx(); 483 final double tyy = transform.getMyy(); 484 final double tyz = transform.getMyz(); 485 final double tty = transform.getTy(); 486 final double tzx = transform.getMzx(); 487 final double tzy = transform.getMzy(); 488 final double tzz = transform.getMzz(); 489 final double ttz = transform.getTz(); 490 return new Affine( 491 sx * txx, sx * txy, sx * txz, sx * ttx + (1 - sx) * getPivotX(), 492 sy * tyx, sy * tyy, sy * tyz, sy * tty + (1 - sy) * getPivotY(), 493 sz * tzx, sz * tzy, sz * tzz, sz * ttz + (1 - sz) * getPivotZ()); 494 } 495 496 @Override 497 public Scale createInverse() throws NonInvertibleTransformException { 498 final double sx = getX(); 499 final double sy = getY(); 500 final double sz = getZ(); 501 502 if (sx == 0.0 || sy == 0.0 || sz == 0.0) { 503 throw new NonInvertibleTransformException( 504 "Zero scale is not invertible"); 505 } 506 507 return new Scale(1.0 / sx, 1.0 / sy, 1.0 / sz, 508 getPivotX(), getPivotY(), getPivotZ()); 509 } 510 511 @Override 512 public Scale clone() { 513 return new Scale(getX(), getY(), getZ(), 514 getPivotX(), getPivotY(), getPivotZ()); 515 } 516 517 /* ************************************************************************* 518 * * 519 * Transform, Inverse Transform * 520 * * 521 **************************************************************************/ 522 523 @Override 524 public Point2D transform(double x, double y) { 525 ensureCanTransform2DPoint(); 526 527 final double mxx = getX(); 528 final double myy = getY(); 529 530 return new Point2D( 531 mxx * x + (1 - mxx) * getPivotX(), 532 myy * y + (1 - myy) * getPivotY()); 533 } 534 535 @Override 536 public Point3D transform(double x, double y, double z) { 537 538 final double mxx = getX(); 539 final double myy = getY(); 540 final double mzz = getZ(); 541 542 return new Point3D( 543 mxx * x + (1 - mxx) * getPivotX(), 544 myy * y + (1 - myy) * getPivotY(), 545 mzz * z + (1 - mzz) * getPivotZ()); 546 } 547 548 @Override 549 void transform2DPointsImpl(double[] srcPts, int srcOff, 550 double[] dstPts, int dstOff, int numPts) { 551 final double xx = getX(); 552 final double yy = getY(); 553 final double px = getPivotX(); 554 final double py = getPivotY(); 555 556 while (--numPts >= 0) { 557 final double x = srcPts[srcOff++]; 558 final double y = srcPts[srcOff++]; 559 560 dstPts[dstOff++] = xx * x + (1 - xx) * px; 561 dstPts[dstOff++] = yy * y + (1 - yy) * py; 562 } 563 } 564 565 @Override 566 void transform3DPointsImpl(double[] srcPts, int srcOff, 567 double[] dstPts, int dstOff, int numPts) { 568 final double xx = getX(); 569 final double yy = getY(); 570 final double zz = getZ(); 571 final double px = getPivotX(); 572 final double py = getPivotY(); 573 final double pz = getPivotZ(); 574 575 while (--numPts >= 0) { 576 dstPts[dstOff++] = xx * srcPts[srcOff++] + (1 - xx) * px; 577 dstPts[dstOff++] = yy * srcPts[srcOff++] + (1 - yy) * py; 578 dstPts[dstOff++] = zz * srcPts[srcOff++] + (1 - zz) * pz; 579 } 580 } 581 582 @Override 583 public Point2D deltaTransform(double x, double y) { 584 ensureCanTransform2DPoint(); 585 586 return new Point2D( 587 getX() * x, 588 getY() * y); 589 } 590 591 @Override 592 public Point3D deltaTransform(double x, double y, double z) { 593 return new Point3D( 594 getX() * x, 595 getY() * y, 596 getZ() * z); 597 } 598 599 @Override 600 public Point2D inverseTransform(double x, double y) 601 throws NonInvertibleTransformException { 602 ensureCanTransform2DPoint(); 603 604 final double sx = getX(); 605 final double sy = getY(); 606 607 if (sx == 0.0 || sy == 0.0) { 608 throw new NonInvertibleTransformException( 609 "Zero scale is not invertible"); 610 } 611 612 final double mxx = 1.0 / sx; 613 final double myy = 1.0 / sy; 614 615 return new Point2D( 616 mxx * x + (1 - mxx) * getPivotX(), 617 myy * y + (1 - myy) * getPivotY()); 618 } 619 620 @Override 621 public Point3D inverseTransform(double x, double y, double z) 622 throws NonInvertibleTransformException { 623 final double sx = getX(); 624 final double sy = getY(); 625 final double sz = getZ(); 626 627 if (sx == 0.0 || sy == 0.0 || sz == 0.0) { 628 throw new NonInvertibleTransformException( 629 "Zero scale is not invertible"); 630 } 631 632 final double mxx = 1.0 / sx; 633 final double myy = 1.0 / sy; 634 final double mzz = 1.0 / sz; 635 636 return new Point3D( 637 mxx * x + (1 - mxx) * getPivotX(), 638 myy * y + (1 - myy) * getPivotY(), 639 mzz * z + (1 - mzz) * getPivotZ()); 640 } 641 642 @Override 643 void inverseTransform2DPointsImpl(double[] srcPts, int srcOff, 644 double[] dstPts, int dstOff, int numPts) 645 throws NonInvertibleTransformException { 646 final double sx = getX(); 647 final double sy = getY(); 648 649 if (sx == 0.0 || sy == 0.0) { 650 throw new NonInvertibleTransformException( 651 "Zero scale is not invertible"); 652 } 653 654 final double xx = 1.0 / sx; 655 final double yy = 1.0 / sy; 656 final double px = getPivotX(); 657 final double py = getPivotY(); 658 659 while (--numPts >= 0) { 660 dstPts[dstOff++] = xx * srcPts[srcOff++] + (1 - xx) * px; 661 dstPts[dstOff++] = yy * srcPts[srcOff++] + (1 - yy) * py; 662 } 663 } 664 665 @Override 666 void inverseTransform3DPointsImpl(double[] srcPts, int srcOff, 667 double[] dstPts, int dstOff, int numPts) 668 throws NonInvertibleTransformException { 669 670 final double sx = getX(); 671 final double sy = getY(); 672 final double sz = getZ(); 673 674 if (sx == 0.0 || sy == 0.0 || sz == 0.0) { 675 throw new NonInvertibleTransformException( 676 "Zero scale is not invertible"); 677 } 678 679 final double xx = 1.0 / sx; 680 final double yy = 1.0 / sy; 681 final double zz = 1.0 / sz; 682 final double px = getPivotX(); 683 final double py = getPivotY(); 684 final double pz = getPivotZ(); 685 686 while (--numPts >= 0) { 687 dstPts[dstOff++] = xx * srcPts[srcOff++] + (1 - xx) * px; 688 dstPts[dstOff++] = yy * srcPts[srcOff++] + (1 - yy) * py; 689 dstPts[dstOff++] = zz * srcPts[srcOff++] + (1 - zz) * pz; 690 } 691 } 692 693 @Override 694 public Point2D inverseDeltaTransform(double x, double y) 695 throws NonInvertibleTransformException { 696 ensureCanTransform2DPoint(); 697 698 final double sx = getX(); 699 final double sy = getY(); 700 701 if (sx == 0.0 || sy == 0.0) { 702 throw new NonInvertibleTransformException( 703 "Zero scale is not invertible"); 704 } 705 706 return new Point2D( 707 (1.0 / sx) * x, 708 (1.0 / sy) * y); 709 } 710 711 @Override 712 public Point3D inverseDeltaTransform(double x, double y, double z) 713 throws NonInvertibleTransformException { 714 715 final double sx = getX(); 716 final double sy = getY(); 717 final double sz = getZ(); 718 719 if (sx == 0.0 || sy == 0.0 || sz == 0.0) { 720 throw new NonInvertibleTransformException( 721 "Zero scale is not invertible"); 722 } 723 724 return new Point3D( 725 (1.0 / sx) * x, 726 (1.0 / sy) * y, 727 (1.0 / sz) * z); 728 } 729 730 /* ************************************************************************* 731 * * 732 * Other API * 733 * * 734 **************************************************************************/ 735 736 /** 737 * Returns a string representation of this {@code Scale} object. 738 * @return a string representation of this {@code Scale} object. 739 */ 740 @Override 741 public String toString() { 742 final StringBuilder sb = new StringBuilder("Scale ["); 743 744 sb.append("x=").append(getX()); 745 sb.append(", y=").append(getY()); 746 sb.append(", z=").append(getZ()); 747 sb.append(", pivotX=").append(getPivotX()); 748 sb.append(", pivotY=").append(getPivotY()); 749 sb.append(", pivotZ=").append(getPivotZ()); 750 751 return sb.append("]").toString(); 752 } 753 754 /* ************************************************************************* 755 * * 756 * Internal implementation stuff * 757 * * 758 **************************************************************************/ 759 760 @Override 761 void apply(final Affine3D trans) { 762 if (getPivotX() != 0 || getPivotY() != 0 || getPivotZ() != 0) { 763 trans.translate(getPivotX(), getPivotY(), getPivotZ()); 764 trans.scale(getX(), getY(), getZ()); 765 trans.translate(-getPivotX(), -getPivotY(), -getPivotZ()); 766 } else { 767 trans.scale(getX(), getY(), getZ()); 768 } 769 } 770 771 @Override 772 BaseTransform derive(BaseTransform trans) { 773 if (isIdentity()) { 774 return trans; 775 } 776 if (getPivotX() != 0 || getPivotY() != 0 || getPivotZ() != 0) { 777 trans = trans.deriveWithTranslation(getPivotX(), getPivotY(), getPivotZ()); 778 trans = trans.deriveWithScale(getX(), getY(), getZ()); 779 return trans.deriveWithTranslation(-getPivotX(), -getPivotY(), -getPivotZ()); 780 } else { 781 return trans.deriveWithScale(getX(), getY(), getZ()); 782 } 783 } 784 785 @Override 786 void validate() { 787 getX(); getPivotX(); 788 getY(); getPivotY(); 789 getZ(); getPivotZ(); 790 } 791 792 @Override 793 void appendTo(Affine a) { 794 a.appendScale(getX(), getY(), getZ(), 795 getPivotX(), getPivotY(), getPivotZ()); 796 } 797 798 @Override 799 void prependTo(Affine a) { 800 a.prependScale(getX(), getY(), getZ(), 801 getPivotX(), getPivotY(), getPivotZ()); 802 } 803 }