Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.scene.transform;
027
028import javafx.beans.property.DoubleProperty;
029import javafx.beans.property.DoublePropertyBase;
030import javafx.beans.property.ObjectProperty;
031import javafx.beans.property.ObjectPropertyBase;
032import javafx.geometry.Point3D;
033
034import com.sun.javafx.geom.transform.Affine3D;
035import javafx.geometry.Point2D;
036
037
038/**
039 * This class represents an {@code Affine} object that rotates coordinates
040 * around an anchor point. This operation is equivalent to translating the
041 * coordinates so that the anchor point is at the origin (S1), then rotating them
042 * about the new origin (S2), and finally translating so that the
043 * intermediate origin is restored to the coordinates of the original
044 * anchor point (S3).
045 * <p/>
046 * For example, the matrix representing the returned transform of
047 *    new Rotate (theta, x, y, z) around the Z-axis
048 *
049 * is :
050 * <pre>
051 *              [   cos(theta)    -sin(theta)   0    x-x*cos+y*sin  ]
052 *              [   sin(theta)     cos(theta)   0    y-x*sin-y*cos  ]
053 *              [      0               0        1          z        ]
054 * </pre>
055 * <p>
056 * For example, to rotate a text 30 degrees around the Z-axis at
057 * anchor point of (50,30):
058 * <pre><code>
059 * Text text = new Text("This is a test");
060 * text.setX(10);
061 * text.setY(50);
062 * text.setFont(new Font(20));
063 *
064 * text.getTransforms().add(new Rotate(30, 50, 30));
065 * </code></pre>
066 * </p>
067 */
068
069public class Rotate extends Transform {
070
071    /**
072     * Specifies the X-axis as the axis of rotation.
073     */
074    public static final Point3D X_AXIS = new Point3D(1,0,0);
075
076    /**
077     * Specifies the Y-axis as the axis of rotation.
078     */
079    public static final Point3D Y_AXIS = new Point3D(0,1,0);
080
081    /**
082     * Specifies the Z-axis as the axis of rotation.
083     */
084    public static final Point3D Z_AXIS = new Point3D(0,0,1);
085
086    /**
087     * Avoids lot of repeated computation.
088     * @see #MatrixCache
089     */
090    private MatrixCache cache;
091
092    /**
093     * Avoids lot of repeated computation.
094     * @see #MatrixCache
095     */
096    private MatrixCache inverseCache;
097
098    /**
099     * Creates a default Rotate transform (identity).
100     */
101    public Rotate() {
102    }
103
104    /**
105     * Creates a two-dimensional Rotate transform.
106     * @param angle the angle of rotation measured in degrees
107     */
108    public Rotate(double angle) {
109        setAngle(angle);
110    }
111
112    /**
113     * Creates a three-dimensional Rotate transform.
114     * @param angle the angle of rotation measured in degrees
115     * @param axis the axis of rotation 
116     */
117    public Rotate(double angle, Point3D axis) {
118        setAngle(angle);
119        setAxis(axis);
120    }
121
122    /**
123     * Creates a two-dimensional Rotate transform with pivot.
124     * @param angle the angle of rotation measured in degrees
125     * @param pivotX the X coordinate of the rotation pivot point
126     * @param pivotY the Y coordinate of the rotation pivot point
127     */
128    public Rotate(double angle, double pivotX, double pivotY) {
129        setAngle(angle);
130        setPivotX(pivotX);
131        setPivotY(pivotY);
132    }
133
134    /**
135     * Creates a simple Rotate transform with three-dimensional pivot.
136     * @param angle the angle of rotation measured in degrees
137     * @param pivotX the X coordinate of the rotation pivot point
138     * @param pivotY the Y coordinate of the rotation pivot point
139     * @param pivotZ the Z coordinate of the rotation pivot point
140     */
141    public Rotate(double angle, double pivotX, double pivotY, double pivotZ) {
142        this(angle, pivotX, pivotY);
143        setPivotZ(pivotZ);
144    }
145
146    /**
147     * Creates a three-dimensional Rotate transform with pivot.
148     * @param angle the angle of rotation measured in degrees
149     * @param pivotX the X coordinate of the rotation pivot point
150     * @param pivotY the Y coordinate of the rotation pivot point
151     * @param pivotZ the Z coordinate of the rotation pivot point
152     * @param axis the axis of rotation 
153     */
154    public Rotate(double angle, double pivotX, double pivotY, double pivotZ, Point3D axis) {
155        this(angle, pivotX, pivotY);
156        setPivotZ(pivotZ);
157        setAxis(axis);
158    }
159
160    /**
161     * Defines the angle of rotation measured in degrees.
162     */
163    private DoubleProperty angle;
164
165
166    public final void setAngle(double value) {
167        angleProperty().set(value);
168    }
169
170    public final double getAngle() {
171        return angle == null ? 0.0 : angle.get();
172    }
173
174    public final DoubleProperty angleProperty() {
175        if (angle == null) {
176            angle = new DoublePropertyBase() {
177
178                @Override
179                public void invalidated() {
180                    transformChanged();
181                }
182
183                @Override
184                public Object getBean() {
185                    return Rotate.this;
186                }
187
188                @Override
189                public String getName() {
190                    return "angle";
191                }
192            };
193        }
194        return angle;
195    }
196
197    /**
198     * Defines the X coordinate of the rotation pivot point.
199     */
200    private DoubleProperty pivotX;
201
202
203    public final void setPivotX(double value) {
204        pivotXProperty().set(value);
205    }
206
207    public final double getPivotX() {
208        return pivotX == null ? 0.0 : pivotX.get();
209    }
210
211    public final DoubleProperty pivotXProperty() {
212        if (pivotX == null) {
213            pivotX = new DoublePropertyBase() {
214
215                @Override
216                public void invalidated() {
217                    transformChanged();
218                }
219
220                @Override
221                public Object getBean() {
222                    return Rotate.this;
223                }
224
225                @Override
226                public String getName() {
227                    return "pivotX";
228                }
229            };
230        }
231        return pivotX;
232    }
233
234    /**
235     * Defines the Y coordinate of the rotation pivot point.
236     */
237    private DoubleProperty pivotY;
238
239
240    public final void setPivotY(double value) {
241        pivotYProperty().set(value);
242    }
243
244    public final double getPivotY() {
245        return pivotY == null ? 0.0 : pivotY.get();
246    }
247
248    public final DoubleProperty pivotYProperty() {
249        if (pivotY == null) {
250            pivotY = new DoublePropertyBase() {
251
252                @Override
253                public void invalidated() {
254                    transformChanged();
255                }
256
257                @Override
258                public Object getBean() {
259                    return Rotate.this;
260                }
261
262                @Override
263                public String getName() {
264                    return "pivotY";
265                }
266            };
267        }
268        return pivotY;
269    }
270
271    /**
272     * Defines the Z coordinate of the rotation pivot point.
273     */
274    private DoubleProperty pivotZ;
275
276
277    public final void setPivotZ(double value) {
278        pivotZProperty().set(value);
279    }
280
281    public final double getPivotZ() {
282        return pivotZ == null ? 0.0 : pivotZ.get();
283    }
284
285    public final DoubleProperty pivotZProperty() {
286        if (pivotZ == null) {
287            pivotZ = new DoublePropertyBase() {
288
289                @Override
290                public void invalidated() {
291                    transformChanged();
292                }
293
294                @Override
295                public Object getBean() {
296                    return Rotate.this;
297                }
298
299                @Override
300                public String getName() {
301                    return "pivotZ";
302                }
303            };
304        }
305        return pivotZ;
306    }
307
308    /**
309     * Defines the axis of rotation at the pivot point.
310     */
311    private ObjectProperty<Point3D> axis;
312
313
314    public final void setAxis(Point3D value) {
315        axisProperty().set(value);
316    }
317
318    public final Point3D getAxis() {
319        return axis == null ? Z_AXIS : axis.get();
320    }
321
322    public final ObjectProperty<Point3D> axisProperty() {
323        if (axis == null) {
324            axis = new ObjectPropertyBase<Point3D>(Z_AXIS) {
325
326                @Override
327                public void invalidated() {
328                    transformChanged();
329                }
330
331                @Override
332                public Object getBean() {
333                    return Rotate.this;
334                }
335
336                @Override
337                public String getName() {
338                    return "axis";
339                }
340            };
341        }
342        return axis;
343    }
344
345    /* *************************************************************************
346     *                                                                         *
347     *                         Element getters                                 *
348     *                                                                         *
349     **************************************************************************/
350
351    @Override
352    public double getMxx() {
353        updateCache();
354        return cache.mxx;
355    }
356
357    @Override
358    public double getMxy() {
359        updateCache();
360        return cache.mxy;
361    }
362
363    @Override
364    public double getMxz() {
365        updateCache();
366        return cache.mxz;
367    }
368
369    @Override
370    public double getTx() {
371        updateCache();
372        return cache.tx;
373    }
374
375    @Override
376    public double getMyx() {
377        updateCache();
378        return cache.myx;
379    }
380
381    @Override
382    public double getMyy() {
383        updateCache();
384        return cache.myy;
385    }
386
387    @Override
388    public double getMyz() {
389        updateCache();
390        return cache.myz;
391    }
392
393    @Override
394    public double getTy() {
395        updateCache();
396        return cache.ty;
397    }
398
399    @Override
400    public double getMzx() {
401        updateCache();
402        return cache.mzx;
403    }
404
405    @Override
406    public double getMzy() {
407        updateCache();
408        return cache.mzy;
409    }
410
411    @Override
412    public double getMzz() {
413        updateCache();
414        return cache.mzz;
415    }
416
417    @Override
418    public double getTz() {
419        updateCache();
420        return cache.tz;
421    }
422
423    /* *************************************************************************
424     *                                                                         *
425     *                           State getters                                 *
426     *                                                                         *
427     **************************************************************************/
428
429    @Override
430    boolean computeIs2D() {
431        final Point3D a = getAxis();
432        return (a.getX() == 0.0 && a.getY() == 0.0) || getAngle() == 0;
433    }
434
435    @Override
436    boolean computeIsIdentity() {
437        if (getAngle() == 0.0) {
438            return true;
439        }
440
441        final Point3D a = getAxis();
442        return a.getX() == 0 && a.getY() == 0 && a.getZ() == 0.0;
443    }
444
445    /* *************************************************************************
446     *                                                                         *
447     *                           Array getters                                 *
448     *                                                                         *
449     **************************************************************************/
450
451    @Override
452    void fill2DArray(double[] array) {
453        updateCache();
454        array[0] = cache.mxx;
455        array[1] = cache.mxy;
456        array[2] = cache.tx;
457        array[3] = cache.myx;
458        array[4] = cache.myy;
459        array[5] = cache.ty;
460    }
461
462    @Override
463    void fill3DArray(double[] array) {
464        updateCache();
465        array[0] = cache.mxx;
466        array[1] = cache.mxy;
467        array[2] = cache.mxz;
468        array[3] = cache.tx;
469        array[4] = cache.myx;
470        array[5] = cache.myy;
471        array[6] = cache.myz;
472        array[7] = cache.ty;
473        array[8] = cache.mzx;
474        array[9] = cache.mzy;
475        array[10] = cache.mzz;
476        array[11] = cache.tz;
477        return;
478    }
479
480    /* *************************************************************************
481     *                                                                         *
482     *                         Transform creators                              *
483     *                                                                         *
484     **************************************************************************/
485
486    @Override
487    public Transform createConcatenation(Transform transform) {
488        if (transform instanceof Rotate) {
489            Rotate r = (Rotate) transform;
490            final double px = getPivotX();
491            final double py = getPivotY();
492            final double pz = getPivotZ();
493
494            if ((r.getAxis() == getAxis() ||
495                        r.getAxis().normalize().equals(getAxis().normalize())) &&
496                    px == r.getPivotX() &&
497                    py == r.getPivotY() &&
498                    pz == r.getPivotZ()) {
499                return new Rotate(getAngle() + r.getAngle(), px, py, pz, getAxis());
500            }
501        }
502
503        if (transform instanceof Affine) {
504            Affine a = (Affine) transform.clone();
505            a.prepend(this);
506            return a;
507        }
508
509        return super.createConcatenation(transform);
510    }
511
512    @Override
513    public Transform createInverse() throws NonInvertibleTransformException {
514        return new Rotate(-getAngle(), getPivotX(), getPivotY(), getPivotZ(),
515                getAxis());
516    }
517
518    @Override
519    public Rotate clone() {
520        return new Rotate(getAngle(), getPivotX(), getPivotY(), getPivotZ(),
521                getAxis());
522    }
523
524    /* *************************************************************************
525     *                                                                         *
526     *                     Transform, Inverse Transform                        *
527     *                                                                         *
528     **************************************************************************/
529
530    @Override
531    public Point2D transform(double x, double y) {
532        ensureCanTransform2DPoint();
533
534        updateCache();
535
536        return new Point2D(
537            cache.mxx * x + cache.mxy * y + cache.tx,
538            cache.myx * x + cache.myy * y + cache.ty);
539    }
540
541    @Override
542    public Point3D transform(double x, double y, double z) {
543        updateCache();
544
545        return new Point3D(
546            cache.mxx * x + cache.mxy * y + cache.mxz * z + cache.tx,
547            cache.myx * x + cache.myy * y + cache.myz * z + cache.ty,
548            cache.mzx * x + cache.mzy * y + cache.mzz * z + cache.tz);
549    }
550
551    @Override
552    void transform2DPointsImpl(double[] srcPts, int srcOff,
553            double[] dstPts, int dstOff, int numPts) {
554        updateCache();
555
556        while (--numPts >= 0) {
557            final double x = srcPts[srcOff++];
558            final double y = srcPts[srcOff++];
559
560            dstPts[dstOff++] = cache.mxx * x + cache.mxy * y + cache.tx;
561            dstPts[dstOff++] = cache.myx * x + cache.myy * y + cache.ty;
562        }
563    }
564
565    @Override
566    void transform3DPointsImpl(double[] srcPts, int srcOff,
567            double[] dstPts, int dstOff, int numPts) {
568
569        updateCache();
570
571        while (--numPts >= 0) {
572            final double x = srcPts[srcOff++];
573            final double y = srcPts[srcOff++];
574            final double z = srcPts[srcOff++];
575
576            dstPts[dstOff++] = cache.mxx * x + cache.mxy * y + cache.mxz * z + cache.tx;
577            dstPts[dstOff++] = cache.myx * x + cache.myy * y + cache.myz * z + cache.ty;
578            dstPts[dstOff++] = cache.mzx * x + cache.mzy * y + cache.mzz * z + cache.tz;
579        }
580    }
581
582    @Override
583    public Point2D deltaTransform(double x, double y) {
584        ensureCanTransform2DPoint();
585
586        updateCache();
587
588        return new Point2D(
589            cache.mxx * x + cache.mxy * y,
590            cache.myx * x + cache.myy * y);
591    }
592
593    @Override
594    public Point3D deltaTransform(double x, double y, double z) {
595        updateCache();
596
597        return new Point3D(
598            cache.mxx * x + cache.mxy * y + cache.mxz * z,
599            cache.myx * x + cache.myy * y + cache.myz * z,
600            cache.mzx * x + cache.mzy * y + cache.mzz * z);
601    }
602
603    @Override
604    public Point2D inverseTransform(double x, double y) {
605        ensureCanTransform2DPoint();
606
607        updateInverseCache();
608
609        return new Point2D(
610            inverseCache.mxx * x + inverseCache.mxy * y + inverseCache.tx,
611            inverseCache.myx * x + inverseCache.myy * y + inverseCache.ty);
612    }
613
614    @Override
615    public Point3D inverseTransform(double x, double y, double z) {
616        updateInverseCache();
617
618        return new Point3D(
619            inverseCache.mxx * x + inverseCache.mxy * y + inverseCache.mxz * z
620                + inverseCache.tx,
621            inverseCache.myx * x + inverseCache.myy * y + inverseCache.myz * z
622                + inverseCache.ty,
623            inverseCache.mzx * x + inverseCache.mzy * y + inverseCache.mzz * z
624                + inverseCache.tz);
625    }
626
627    @Override
628    void inverseTransform2DPointsImpl(double[] srcPts, int srcOff,
629            double[] dstPts, int dstOff, int numPts) {
630        updateInverseCache();
631
632        while (--numPts >= 0) {
633            final double x = srcPts[srcOff++];
634            final double y = srcPts[srcOff++];
635
636            dstPts[dstOff++] = inverseCache.mxx * x + inverseCache.mxy * y
637                    + inverseCache.tx;
638            dstPts[dstOff++] = inverseCache.myx * x + inverseCache.myy * y
639                    + inverseCache.ty;
640        }
641    }
642
643    @Override
644    void inverseTransform3DPointsImpl(double[] srcPts, int srcOff,
645            double[] dstPts, int dstOff, int numPts) {
646
647        updateInverseCache();
648
649        while (--numPts >= 0) {
650            final double x = srcPts[srcOff++];
651            final double y = srcPts[srcOff++];
652            final double z = srcPts[srcOff++];
653
654            dstPts[dstOff++] = inverseCache.mxx * x + inverseCache.mxy * y
655                    + inverseCache.mxz * z + inverseCache.tx;
656            dstPts[dstOff++] = inverseCache.myx * x + inverseCache.myy * y
657                    + inverseCache.myz * z + inverseCache.ty;
658            dstPts[dstOff++] = inverseCache.mzx * x + inverseCache.mzy * y
659                    + inverseCache.mzz * z + inverseCache.tz;
660        }
661    }
662
663    @Override
664    public Point2D inverseDeltaTransform(double x, double y) {
665        ensureCanTransform2DPoint();
666
667        updateInverseCache();
668
669        return new Point2D(
670            inverseCache.mxx * x + inverseCache.mxy * y,
671            inverseCache.myx * x + inverseCache.myy * y);
672    }
673
674    @Override
675    public Point3D inverseDeltaTransform(double x, double y, double z) {
676        updateInverseCache();
677
678        return new Point3D(
679            inverseCache.mxx * x + inverseCache.mxy * y + inverseCache.mxz * z,
680            inverseCache.myx * x + inverseCache.myy * y + inverseCache.myz * z,
681            inverseCache.mzx * x + inverseCache.mzy * y + inverseCache.mzz * z);
682    }
683
684    /* *************************************************************************
685     *                                                                         *
686     *                               Other API                                 *
687     *                                                                         *
688     **************************************************************************/
689
690    /**
691     * Returns a string representation of this {@code Rotate} object.
692     * @return a string representation of this {@code Rotate} object.
693     */
694    @Override
695    public String toString() {
696        final StringBuilder sb = new StringBuilder("Rotate [");
697
698        sb.append("angle=").append(getAngle());
699        sb.append(", pivotX=").append(getPivotX());
700        sb.append(", pivotY=").append(getPivotY());
701        sb.append(", pivotZ=").append(getPivotZ());
702        sb.append(", axis=").append(getAxis());
703
704        return sb.append("]").toString();
705    }
706
707    /* *************************************************************************
708     *                                                                         *
709     *                    Internal implementation stuff                        *
710     *                                                                         *
711     **************************************************************************/
712
713    /**
714     * @treatAsPrivate implementation detail
715     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
716     */
717    @Deprecated
718    @Override
719    public void impl_apply(final Affine3D trans) {
720        double localPivotX = getPivotX();
721        double localPivotY = getPivotY();
722        double localPivotZ = getPivotZ();
723        double localAngle = getAngle();
724
725        if (localPivotX != 0 || localPivotY != 0 || localPivotZ != 0) {
726            trans.translate(localPivotX, localPivotY, localPivotZ);
727            trans.rotate(Math.toRadians(localAngle),
728                         getAxis().getX(),getAxis().getY(), getAxis().getZ());
729            trans.translate(-localPivotX, -localPivotY, -localPivotZ);
730        } else {
731            trans.rotate(Math.toRadians(localAngle),
732                         getAxis().getX(), getAxis().getY(), getAxis().getZ());
733        }
734    }
735
736    @Override
737    void validate() {
738        getAxis();
739        getAngle();
740        getPivotX();
741        getPivotY();
742        getPivotZ();
743    }
744
745    @Override
746    protected void transformChanged() {
747        if (cache != null) {
748            cache.invalidate();
749        }
750        super.transformChanged();
751    }
752
753    @Override
754    void appendTo(Affine a) {
755        a.appendRotation(getAngle(), getPivotX(), getPivotY(), getPivotZ(),
756                getAxis());
757    }
758
759    @Override
760    void prependTo(Affine a) {
761        a.prependRotation(getAngle(), getPivotX(), getPivotY(), getPivotZ(),
762                getAxis());
763    }
764
765    /**
766     * Updates the matrix cache
767     */
768    private void updateCache() {
769        if (cache == null) {
770            cache = new MatrixCache();
771        }
772
773        if (!cache.valid) {
774            cache.update(getAngle(), getAxis(),
775                    getPivotX(), getPivotY(), getPivotZ());
776        }
777    }
778
779    /**
780     * Updates the inverse matrix cache
781     */
782    private void updateInverseCache() {
783        if (inverseCache == null) {
784            inverseCache = new MatrixCache();
785        }
786
787        if (!inverseCache.valid) {
788            inverseCache.update(-getAngle(), getAxis(),
789                    getPivotX(), getPivotY(), getPivotZ());
790        }
791    }
792
793    /**
794     * Matrix cache. Computing single transformation matrix elements for
795     * a general rotation is quite expensive. Also each of those partial
796     * computations need some common operations to be made (compute sin
797     * and cos, normalize axis). Therefore with the direct element computations
798     * if all the getters for the elements are called to get the matrix,
799     * the result is slow.
800     *
801     * If a matrix element is asked for, we can reasonably anticipate that
802     * some other elements will be asked for as well. So when any element
803     * needs to be computed, we compute the entire matrix, cache it,
804     * and use the stored values until the transform changes.
805     */
806    private static class MatrixCache {
807        boolean valid = false;
808        boolean is3D = false;
809
810        double mxx, mxy, mxz, tx,
811               myx, myy, myz, ty,
812               mzx, mzy, mzz, tz;
813
814        public MatrixCache() {
815            // to have the 3D part right when using 2D-only
816            mzz = 1.0;
817        }
818
819        public void update(double angle, Point3D axis,
820                double px, double py, double pz) {
821
822            final double rads = Math.toRadians(angle);
823            final double sin = Math.sin(rads);
824            final double cos = Math.cos(rads);
825
826            if (axis == Z_AXIS ||
827                    (axis.getX() == 0.0 &&
828                     axis.getY() == 0.0 && 
829                     axis.getZ() > 0.0)) {
830                // 2D case
831                mxx = cos;
832                mxy = -sin;
833                tx = px * (1 - cos) + py * sin;
834                myx = sin;
835                myy = cos;
836                ty = py * (1 - cos) - px * sin;
837
838                if (is3D) {
839                    // Was 3D, needs to set the 3D values
840                    mxz = 0.0;
841                    myz = 0.0;
842                    mzx = 0.0;
843                    mzy = 0.0;
844                    mzz = 1.0;
845                    tz = 0.0;
846                    is3D = false;
847                }
848                valid = true;
849                return;
850            }
851            // 3D case
852            is3D = true;
853
854            double axisX, axisY, axisZ;
855
856            if (axis == X_AXIS || axis == Y_AXIS || axis == Z_AXIS) {
857                axisX = axis.getX();
858                axisY = axis.getY();
859                axisZ = axis.getZ();
860            } else {
861                // normalize
862                final double mag = Math.sqrt(axis.getX() * axis.getX() +
863                        axis.getY() * axis.getY() + axis.getZ() * axis.getZ());
864
865                if (mag == 0.0) {
866                    mxx = 1; mxy = 0; mxz = 0; tx = 0;
867                    myx = 0; myy = 1; myz = 0; ty = 0;
868                    mzx = 0; mzy = 0; mzz = 1; tz = 0;
869                    valid = true;
870                    return;
871                } else {
872                    axisX = axis.getX() / mag;
873                    axisY = axis.getY() / mag;
874                    axisZ = axis.getZ() / mag;
875                }
876            }
877
878            mxx = cos + axisX * axisX * (1 - cos);
879            mxy = axisX * axisY * (1 - cos) - axisZ * sin;
880            mxz = axisX * axisZ * (1 - cos) + axisY * sin;
881            tx = px * (1 - mxx) - py * mxy - pz * mxz;
882
883            myx = axisY * axisX * (1 - cos) + axisZ * sin;
884            myy = cos + axisY * axisY * (1 - cos);
885            myz = axisY * axisZ * (1 - cos) - axisX * sin;
886            ty = py * (1 - myy) - px * myx - pz * myz;
887
888            mzx = axisZ * axisX * (1 - cos) - axisY * sin;
889            mzy = axisZ * axisY * (1 - cos) + axisX * sin;
890            mzz = cos + axisZ * axisZ * (1 - cos);
891            tz = pz * (1 - mzz) - px * mzx - py * mzy;
892
893            valid = true;
894        }
895
896        public void invalidate() {
897            valid = false;
898        }
899    }
900}