Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2010, 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.shape;
027
028import javafx.beans.property.BooleanProperty;
029import javafx.beans.property.BooleanPropertyBase;
030import javafx.beans.property.DoubleProperty;
031import javafx.beans.property.DoublePropertyBase;
032
033import com.sun.javafx.geom.Arc2D;
034import com.sun.javafx.geom.Path2D;
035import com.sun.javafx.geom.PathIterator;
036import com.sun.javafx.geom.transform.BaseTransform;
037import com.sun.javafx.sg.PGPath;
038import javafx.scene.paint.Paint;
039
040// PENDING_DOC_REVIEW
041/**
042 * A path element that forms an arc from the previous coordinates
043 * to the specified x and y coordinates using the specified radius.
044 *
045 * <p>For more information on path elements see the {@link Path} and
046 * {@link PathElement} classes.
047 *
048 * <p>Example:
049 *
050<PRE>
051import javafx.scene.shape.*;
052
053Path path = new Path();
054
055MoveTo moveTo = new MoveTo();
056moveTo.setX(0.0);
057moveTo.setY(0.0);
058
059ArcTo arcTo = new ArcTo();
060arcTo.setX(50.0);
061arcTo.setY(50.0);
062arcTo.setRadiusX(50.0);
063arcTo.setRadiusY(50.0);
064
065path.getElements().add(moveTo);
066path.getElements().add(arcTo);
067</PRE>
068 * 
069 * <p>
070 * Following image demonstrates {@code radiusX}, {@code radiusY} and
071 * {@code xAxisRotation} parameters: 
072 * {@code radiusX} is the horizontal radius of the full ellipse of which this arc is 
073 * a partial section, {@code radiusY} is its vertical radius. 
074 * {@code xAxisRotation} defines the rotation of the ellipse in degrees.
075 * </p>
076 * <p>
077 * <img src="doc-files/arcto.png"/>
078 * </p>
079 * <p>
080 * In most cases, there are four options of how to draw an arc from
081 * starting point to given end coordinates. They can be distinguished by  
082 * {@code largeArcFlag} and {@code sweepFlag} parameters.
083 * {@code largeArcFlag == true} means that the arc greater than 180 degrees will
084 * be drawn. {@code sweepFlag == true} means that the arc will be drawn
085 * in the positive angle direction - i.e. the angle in the
086 * ellipse formula will increase from {@code [fromX, fromY]} to {@code [x,y]}.
087 * Following images demonstrate this behavior:
088 * </p>
089 * <p>
090 * <img src="doc-files/arcto-flags.png"/>
091 * </p>
092 */
093public class ArcTo extends PathElement {
094
095
096    /**
097     * Creates an empty instance of ArcTo.
098     */
099    public ArcTo() {
100    }
101
102    /**
103     * Creates a new instance of ArcTo.
104     * @param radiusX horizontal radius of the arc
105     * @param radiusY vertical radius of the arc
106     * @param xAxisRotation the x-axis rotation in degrees
107     * @param x horizontal position of the arc end point
108     * @param y vertical position of the arc end point
109     * @param largeArcFlag large arg flag: determines which arc to use (large/small)
110     * @param sweepFlag sweep flag: determines which arc to use (direction)
111     */
112    public ArcTo(double radiusX, double radiusY, double xAxisRotation,
113        double x, double y, boolean largeArcFlag, boolean sweepFlag)
114    {
115        setRadiusX(radiusX);
116        setRadiusY(radiusY);
117        setXAxisRotation(xAxisRotation);
118        setX(x);
119        setY(y);
120        setLargeArcFlag(largeArcFlag);
121        setSweepFlag(sweepFlag);
122    }
123
124    /**
125     * The horizontal radius to use for the arc.
126     *
127     * @defaultValue 0.0
128     */
129    private DoubleProperty radiusX = new DoublePropertyBase() {
130
131        @Override
132        public void invalidated() {
133            u();
134        }
135
136        @Override
137        public Object getBean() {
138            return ArcTo.this;
139        }
140
141        @Override
142        public String getName() {
143            return "radiusX";
144        }
145    };
146
147    public final void setRadiusX(double value) {
148        radiusX.set(value);
149    }
150
151    public final double getRadiusX() {
152        return radiusX.get();
153    }
154
155    public final DoubleProperty radiusXProperty() {
156        return radiusX;
157    }
158
159    /**
160     * The vertical radius to use for the arc.
161     *
162     * @defaultValue 0.0
163     */
164    private DoubleProperty radiusY = new DoublePropertyBase() {
165
166        @Override
167        public void invalidated() {
168            u();
169        }
170
171        @Override
172        public Object getBean() {
173            return ArcTo.this;
174        }
175
176        @Override
177        public String getName() {
178            return "radiusY";
179        }
180    };
181
182    public final void setRadiusY(double value) {
183        radiusY.set(value);
184    }
185
186    public final double getRadiusY() {
187        return radiusY.get();
188    }
189
190    public final DoubleProperty radiusYProperty() {
191        return radiusY;
192    }
193
194    /**
195     * The x-axis rotation in degrees.
196     *
197     * @defaultValue 0.0
198     */
199    private DoubleProperty xAxisRotation;
200
201    /**
202     * Sets the x-axis rotation in degrees.
203     * @param value the x-axis rotation in degrees.
204     */
205    public final void setXAxisRotation(double value) {
206        if (xAxisRotation != null || value != 0.0) {
207            XAxisRotationProperty().set(value);
208        }
209    }
210
211    /**
212     * Gets the x-axis rotation in degrees.
213     * @return the x-axis rotation in degrees.
214     */
215    public final double getXAxisRotation() {
216        return xAxisRotation == null ? 0.0 : xAxisRotation.get();
217    }
218
219    /**
220     * The x-axis rotation in degrees.
221     * @return The XAxisRotation property
222     */
223    public final DoubleProperty XAxisRotationProperty() {
224        if (xAxisRotation == null) {
225            xAxisRotation = new DoublePropertyBase() {
226
227                @Override
228                public void invalidated() {
229                    u();
230                }
231
232                @Override
233                public Object getBean() {
234                    return ArcTo.this;
235                }
236
237                @Override
238                public String getName() {
239                    return "XAxisRotation";
240                }
241            };
242        }
243        return xAxisRotation;
244    }
245
246    /**
247     * The large arc flag.
248     *
249     * @defaultValue false
250     */
251    private BooleanProperty largeArcFlag;
252
253    public final void setLargeArcFlag(boolean value) {
254        if (largeArcFlag != null || value != false) {
255            largeArcFlagProperty().set(value);
256        }
257    }
258
259    public final boolean isLargeArcFlag() {
260        return largeArcFlag == null ? false : largeArcFlag.get();
261    }
262
263    public final BooleanProperty largeArcFlagProperty() {
264        if (largeArcFlag == null) {
265            largeArcFlag = new BooleanPropertyBase() {
266
267                @Override
268                public void invalidated() {
269                    u();
270                }
271
272                @Override
273                public Object getBean() {
274                    return ArcTo.this;
275                }
276
277                @Override
278                public String getName() {
279                    return "largeArcFlag";
280                }
281            };
282        }
283        return largeArcFlag;
284    }
285
286    /**
287     * The sweep flag
288     *
289     * @defaultValue false
290     */
291    private BooleanProperty sweepFlag;
292
293    public final void setSweepFlag(boolean value) {
294        if (sweepFlag != null || value != false) {
295            sweepFlagProperty().set(value);
296        }
297    }
298
299    public final boolean isSweepFlag() {
300        return sweepFlag == null ? false : sweepFlag.get();
301    }
302
303    public final BooleanProperty sweepFlagProperty() {
304        if (sweepFlag == null) {
305            sweepFlag = new BooleanPropertyBase() {
306
307                @Override
308                public void invalidated() {
309                    u();
310                }
311
312                @Override
313                public Object getBean() {
314                    return ArcTo.this;
315                }
316
317                @Override
318                public String getName() {
319                    return "sweepFlag";
320                }
321            };
322        }
323        return sweepFlag;
324    }
325
326    /**
327     * The x coordinate to arc to.
328     *
329     * @defaultValue 0.0
330     */
331    private DoubleProperty x;
332
333
334
335    public final void setX(double value) {
336        if (x != null || value != 0.0) {
337            xProperty().set(value);
338        }
339    }
340
341    public final double getX() {
342        return x == null ? 0.0 : x.get();
343    }
344
345    public final DoubleProperty xProperty() {
346        if (x == null) {
347            x = new DoublePropertyBase() {
348
349                @Override
350                public void invalidated() {
351                    u();
352                }
353
354                @Override
355                public Object getBean() {
356                    return ArcTo.this;
357                }
358
359                @Override
360                public String getName() {
361                    return "x";
362                }
363            };
364        }
365        return x;
366    }
367
368    /**
369     * The y coordinate to arc to.
370     *
371     * @defaultValue 0.0
372     */
373    private DoubleProperty y;
374
375
376
377    public final void setY(double value) {
378        if (y != null || value != 0.0) {
379            yProperty().set(value);
380        }
381    }
382
383    public final double getY() {
384        return y == null ? 0.0 : y.get();
385    }
386
387    public final DoubleProperty yProperty() {
388        if (y == null) {
389            y = new DoublePropertyBase() {
390
391                @Override
392                public void invalidated() {
393                    u();
394                }
395
396                @Override
397                public Object getBean() {
398                    return ArcTo.this;
399                }
400
401                @Override
402                public String getName() {
403                    return "y";
404                }
405            };
406        }
407        return y;
408    }
409
410    @Override
411    void addTo(PGPath pgPath) {
412        addArcTo(pgPath, null, pgPath.getCurrentX(), pgPath.getCurrentY());
413    }
414
415    /**
416     * @treatAsPrivate implementation detail
417     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
418     */
419    @Deprecated
420    @Override public void impl_addTo(Path2D path) {
421        addArcTo(null, path, path.getCurrentX(), path.getCurrentY());
422    }
423
424    // This function is nearly identical to the one written for the
425    // original port of the F3 graphics/UI library:
426    // javafx.ui.canvas.ArcTo#addTo
427    private void addArcTo(PGPath pgPath, Path2D path,
428                          final double x0, final double y0)
429    {
430        double localX = getX();
431        double localY = getY();
432        boolean localSweepFlag = isSweepFlag();
433        boolean localLargeArcFlag = isLargeArcFlag();
434
435        // Determine target "to" position
436        final double xto = (isAbsolute()) ? localX : localX + x0;
437        final double yto = (isAbsolute()) ? localY : localY + y0;
438        // Compute the half distance between the current and the final point
439        final double dx2 = (x0 - xto) / 2.0;
440        final double dy2 = (y0 - yto) / 2.0;
441        // Convert angle from degrees to radians
442        final double xAxisRotationR = Math.toRadians(getXAxisRotation());
443        final double cosAngle = Math.cos(xAxisRotationR);
444        final double sinAngle = Math.sin(xAxisRotationR);
445
446        //
447        // Step 1 : Compute (x1, y1)
448        //
449        final double x1 = ( cosAngle * dx2 + sinAngle * dy2);
450        final double y1 = (-sinAngle * dx2 + cosAngle * dy2);
451        // Ensure radii are large enough
452        double rx = Math.abs(getRadiusX());
453        double ry = Math.abs(getRadiusY());
454        double Prx = rx * rx;
455        double Pry = ry * ry;
456        final double Px1 = x1 * x1;
457        final double Py1 = y1 * y1;
458        // check that radii are large enough
459        final double radiiCheck = Px1/Prx + Py1/Pry;
460        if (radiiCheck > 1.0) {
461            rx = Math.sqrt(radiiCheck) * rx;
462            ry = Math.sqrt(radiiCheck) * ry;
463            if (rx == rx && ry == ry) {/* not NANs */} else {
464                if (pgPath == null) {
465                    path.lineTo((float) xto, (float) yto);
466                } else {
467                    pgPath.addLineTo((float) xto, (float) yto);
468                }
469                return;
470            }
471            Prx = rx * rx;
472            Pry = ry * ry;
473        }
474
475        //
476        // Step 2 : Compute (cx1, cy1)
477        //
478        double sign = ((localLargeArcFlag == localSweepFlag) ? -1.0 : 1.0);
479        double sq = ((Prx*Pry)-(Prx*Py1)-(Pry*Px1)) / ((Prx*Py1)+(Pry*Px1));
480        sq = (sq < 0.0) ? 0.0 : sq;
481        final double coef = (sign * Math.sqrt(sq));
482        final double cx1 = coef * ((rx * y1) / ry);
483        final double cy1 = coef * -((ry * x1) / rx);
484
485        //
486        // Step 3 : Compute (cx, cy) from (cx1, cy1)
487        //
488        final double sx2 = (x0 + xto) / 2.0;
489        final double sy2 = (y0 + yto) / 2.0;
490        final double cx = sx2 + (cosAngle * cx1 - sinAngle * cy1);
491        final double cy = sy2 + (sinAngle * cx1 + cosAngle * cy1);
492
493        //
494        // Step 4 : Compute the angleStart (angle1) and the angleExtent (dangle)
495        //
496        final double ux = (x1 - cx1) / rx;
497        final double uy = (y1 - cy1) / ry;
498        final double vx = (-x1 - cx1) / rx;
499        final double vy = (-y1 - cy1) / ry;
500        // Compute the angle start
501        double n = Math.sqrt((ux * ux) + (uy * uy));
502        double p = ux; // (1 * ux) + (0 * uy)
503        sign = ((uy < 0.0) ? -1.0 : 1.0);
504        double angleStart = Math.toDegrees(sign * Math.acos(p / n));
505
506        // Compute the angle extent
507        n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
508        p = ux * vx + uy * vy;
509        sign = ((ux * vy - uy * vx < 0.0) ? -1.0 : 1.0);
510        double angleExtent = Math.toDegrees(sign * Math.acos(p / n));
511        if (!localSweepFlag && (angleExtent > 0)) {
512            angleExtent -= 360.0;
513        } else if (localSweepFlag && (angleExtent < 0)) {
514            angleExtent += 360.0;
515        }
516        angleExtent = angleExtent % 360;
517        angleStart = angleStart % 360;
518
519        //
520        // We can now build the resulting Arc2D
521        //
522        final float arcX = (float) (cx - rx);
523        final float arcY = (float) (cy - ry);
524        final float arcW = (float) (rx * 2.0);
525        final float arcH = (float) (ry * 2.0);
526        final float arcStart = (float) -angleStart;
527        final float arcExtent = (float) -angleExtent;
528
529        if (pgPath == null) {
530            final Arc2D arc =
531                new Arc2D(arcX, arcY, arcW, arcH,
532                          arcStart, arcExtent, Arc2D.OPEN);
533            BaseTransform xform = (xAxisRotationR == 0) ? null :
534                BaseTransform.getRotateInstance(xAxisRotationR, cx, cy);
535            PathIterator pi = arc.getPathIterator(xform);
536            // RT-8926, append(true) converts the initial moveTo into a
537            // lineTo which can generate huge miter joins if the segment
538            // is small enough.  So, we manually skip it here instead.
539            pi.next();
540            path.append(pi, true);
541        } else {
542            pgPath.addArcTo(arcX, arcY, arcW, arcH,
543                            arcStart, arcExtent, (float) xAxisRotationR);
544        }
545    }
546
547    /**
548     * Returns a string representation of this {@code ArcTo} object.
549     * @return a string representation of this {@code ArcTo} object.
550     */
551    @Override
552    public String toString() {
553        final StringBuilder sb = new StringBuilder("ArcTo[");
554
555        sb.append("x=").append(getX());
556        sb.append(", y=").append(getY());
557        sb.append(", radiusX=").append(getRadiusX());
558        sb.append(", radiusY=").append(getRadiusY());
559        sb.append(", xAxisRotation=").append(getXAxisRotation());
560
561        if (isLargeArcFlag()) {
562            sb.append(", lartArcFlag");
563        }
564
565        if (isSweepFlag()) {
566            sb.append(", sweepFlag");
567        }
568
569        return sb.append("]").toString();
570    }
571}