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