/*
* Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.scene.shape;
import com.sun.javafx.geom.Arc2D;
import com.sun.javafx.geom.Path2D;
import com.sun.javafx.geom.PathIterator;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.javafx.scene.shape.ArcToHelper;
import com.sun.javafx.sg.prism.NGPath;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.BooleanPropertyBase;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.DoublePropertyBase;
// PENDING_DOC_REVIEW
A path element that forms an arc from the previous coordinates
to the specified x and y coordinates using the specified radius.
For more information on path elements see the Path
and PathElement
classes.
Example:
import javafx.scene.shape.*;
Path path = new Path();
MoveTo moveTo = new MoveTo();
moveTo.setX(0.0);
moveTo.setY(0.0);
ArcTo arcTo = new ArcTo();
arcTo.setX(50.0);
arcTo.setY(50.0);
arcTo.setRadiusX(50.0);
arcTo.setRadiusY(50.0);
path.getElements().add(moveTo);
path.getElements().add(arcTo);
Following image demonstrates radiusX
, radiusY
and xAxisRotation
parameters: radiusX
is the horizontal radius of the full ellipse of which this arc is a partial section, radiusY
is its vertical radius. xAxisRotation
defines the rotation of the ellipse in degrees.
In most cases, there are four options of how to draw an arc from starting point to given end coordinates. They can be distinguished by largeArcFlag
and sweepFlag
parameters. largeArcFlag == true
means that the arc greater than 180 degrees will be drawn. sweepFlag == true
means that the arc will be drawn in the positive angle direction - i.e. the angle in the ellipse formula will increase from [fromX, fromY]
to [x,y]
. Following images demonstrate this behavior:
Since: JavaFX 2.0
/**
* A path element that forms an arc from the previous coordinates
* to the specified x and y coordinates using the specified radius.
*
* <p>For more information on path elements see the {@link Path} and
* {@link PathElement} classes.
*
* <p>Example:
*
<PRE>
import javafx.scene.shape.*;
Path path = new Path();
MoveTo moveTo = new MoveTo();
moveTo.setX(0.0);
moveTo.setY(0.0);
ArcTo arcTo = new ArcTo();
arcTo.setX(50.0);
arcTo.setY(50.0);
arcTo.setRadiusX(50.0);
arcTo.setRadiusY(50.0);
path.getElements().add(moveTo);
path.getElements().add(arcTo);
</PRE>
*
* <p>
* Following image demonstrates {@code radiusX}, {@code radiusY} and
* {@code xAxisRotation} parameters:
* {@code radiusX} is the horizontal radius of the full ellipse of which this arc is
* a partial section, {@code radiusY} is its vertical radius.
* {@code xAxisRotation} defines the rotation of the ellipse in degrees.
* </p>
* <p>
* <img src="doc-files/arcto.png" alt="A visual rendering of ArcTo shape">
* </p>
* <p>
* In most cases, there are four options of how to draw an arc from
* starting point to given end coordinates. They can be distinguished by
* {@code largeArcFlag} and {@code sweepFlag} parameters.
* {@code largeArcFlag == true} means that the arc greater than 180 degrees will
* be drawn. {@code sweepFlag == true} means that the arc will be drawn
* in the positive angle direction - i.e. the angle in the
* ellipse formula will increase from {@code [fromX, fromY]} to {@code [x,y]}.
* Following images demonstrate this behavior:
* </p>
* <p>
* <img src="doc-files/arcto-flags.png" alt="A visual rendering of ArcTo shape
* with setting to its different properties">
* </p>
* @since JavaFX 2.0
*/
public class ArcTo extends PathElement {
static {
ArcToHelper.setArcToAccessor(new ArcToHelper.ArcToAccessor() {
@Override
public void doAddTo(PathElement pathElement, Path2D path) {
((ArcTo) pathElement).doAddTo(path);
}
});
}
Creates an empty instance of ArcTo.
/**
* Creates an empty instance of ArcTo.
*/
public ArcTo() {
ArcToHelper.initHelper(this);
}
Creates a new instance of ArcTo.
Params: - radiusX – horizontal radius of the arc
- radiusY – vertical radius of the arc
- xAxisRotation – the x-axis rotation in degrees
- x – horizontal position of the arc end point
- y – vertical position of the arc end point
- largeArcFlag – large arg flag: determines which arc to use (large/small)
- sweepFlag – sweep flag: determines which arc to use (direction)
/**
* Creates a new instance of ArcTo.
* @param radiusX horizontal radius of the arc
* @param radiusY vertical radius of the arc
* @param xAxisRotation the x-axis rotation in degrees
* @param x horizontal position of the arc end point
* @param y vertical position of the arc end point
* @param largeArcFlag large arg flag: determines which arc to use (large/small)
* @param sweepFlag sweep flag: determines which arc to use (direction)
*/
public ArcTo(double radiusX, double radiusY, double xAxisRotation,
double x, double y, boolean largeArcFlag, boolean sweepFlag)
{
setRadiusX(radiusX);
setRadiusY(radiusY);
setXAxisRotation(xAxisRotation);
setX(x);
setY(y);
setLargeArcFlag(largeArcFlag);
setSweepFlag(sweepFlag);
ArcToHelper.initHelper(this);
}
The horizontal radius to use for the arc.
@defaultValue 0.0
/**
* The horizontal radius to use for the arc.
*
* @defaultValue 0.0
*/
private DoubleProperty radiusX = new DoublePropertyBase() {
@Override
public void invalidated() {
u();
}
@Override
public Object getBean() {
return ArcTo.this;
}
@Override
public String getName() {
return "radiusX";
}
};
public final void setRadiusX(double value) {
radiusX.set(value);
}
public final double getRadiusX() {
return radiusX.get();
}
public final DoubleProperty radiusXProperty() {
return radiusX;
}
The vertical radius to use for the arc.
@defaultValue 0.0
/**
* The vertical radius to use for the arc.
*
* @defaultValue 0.0
*/
private DoubleProperty radiusY = new DoublePropertyBase() {
@Override
public void invalidated() {
u();
}
@Override
public Object getBean() {
return ArcTo.this;
}
@Override
public String getName() {
return "radiusY";
}
};
public final void setRadiusY(double value) {
radiusY.set(value);
}
public final double getRadiusY() {
return radiusY.get();
}
public final DoubleProperty radiusYProperty() {
return radiusY;
}
The x-axis rotation in degrees.
@defaultValue 0.0
/**
* The x-axis rotation in degrees.
*
* @defaultValue 0.0
*/
private DoubleProperty xAxisRotation;
Sets the x-axis rotation in degrees.
Params: - value – the x-axis rotation in degrees.
/**
* Sets the x-axis rotation in degrees.
* @param value the x-axis rotation in degrees.
*/
public final void setXAxisRotation(double value) {
if (xAxisRotation != null || value != 0.0) {
XAxisRotationProperty().set(value);
}
}
Gets the x-axis rotation in degrees.
Returns: the x-axis rotation in degrees.
/**
* Gets the x-axis rotation in degrees.
* @return the x-axis rotation in degrees.
*/
public final double getXAxisRotation() {
return xAxisRotation == null ? 0.0 : xAxisRotation.get();
}
The x-axis rotation in degrees.
Returns: The XAxisRotation property
/**
* The x-axis rotation in degrees.
* @return The XAxisRotation property
*/
public final DoubleProperty XAxisRotationProperty() {
if (xAxisRotation == null) {
xAxisRotation = new DoublePropertyBase() {
@Override
public void invalidated() {
u();
}
@Override
public Object getBean() {
return ArcTo.this;
}
@Override
public String getName() {
return "XAxisRotation";
}
};
}
return xAxisRotation;
}
The large arc flag.
@defaultValue false
/**
* The large arc flag.
*
* @defaultValue false
*/
private BooleanProperty largeArcFlag;
public final void setLargeArcFlag(boolean value) {
if (largeArcFlag != null || value != false) {
largeArcFlagProperty().set(value);
}
}
public final boolean isLargeArcFlag() {
return largeArcFlag == null ? false : largeArcFlag.get();
}
public final BooleanProperty largeArcFlagProperty() {
if (largeArcFlag == null) {
largeArcFlag = new BooleanPropertyBase() {
@Override
public void invalidated() {
u();
}
@Override
public Object getBean() {
return ArcTo.this;
}
@Override
public String getName() {
return "largeArcFlag";
}
};
}
return largeArcFlag;
}
The sweep flag
@defaultValue false
/**
* The sweep flag
*
* @defaultValue false
*/
private BooleanProperty sweepFlag;
public final void setSweepFlag(boolean value) {
if (sweepFlag != null || value != false) {
sweepFlagProperty().set(value);
}
}
public final boolean isSweepFlag() {
return sweepFlag == null ? false : sweepFlag.get();
}
public final BooleanProperty sweepFlagProperty() {
if (sweepFlag == null) {
sweepFlag = new BooleanPropertyBase() {
@Override
public void invalidated() {
u();
}
@Override
public Object getBean() {
return ArcTo.this;
}
@Override
public String getName() {
return "sweepFlag";
}
};
}
return sweepFlag;
}
The x coordinate to arc to.
@defaultValue 0.0
/**
* The x coordinate to arc to.
*
* @defaultValue 0.0
*/
private DoubleProperty x;
public final void setX(double value) {
if (x != null || value != 0.0) {
xProperty().set(value);
}
}
public final double getX() {
return x == null ? 0.0 : x.get();
}
public final DoubleProperty xProperty() {
if (x == null) {
x = new DoublePropertyBase() {
@Override
public void invalidated() {
u();
}
@Override
public Object getBean() {
return ArcTo.this;
}
@Override
public String getName() {
return "x";
}
};
}
return x;
}
The y coordinate to arc to.
@defaultValue 0.0
/**
* The y coordinate to arc to.
*
* @defaultValue 0.0
*/
private DoubleProperty y;
public final void setY(double value) {
if (y != null || value != 0.0) {
yProperty().set(value);
}
}
public final double getY() {
return y == null ? 0.0 : y.get();
}
public final DoubleProperty yProperty() {
if (y == null) {
y = new DoublePropertyBase() {
@Override
public void invalidated() {
u();
}
@Override
public Object getBean() {
return ArcTo.this;
}
@Override
public String getName() {
return "y";
}
};
}
return y;
}
@Override
void addTo(NGPath pgPath) {
addArcTo(pgPath, null, pgPath.getCurrentX(), pgPath.getCurrentY());
}
/*
* Note: This method MUST only be called via its accessor method.
*/
private void doAddTo(Path2D path) {
addArcTo(null, path, path.getCurrentX(), path.getCurrentY());
}
// This function is nearly identical to the one written for the
// original port of the F3 graphics/UI library:
// javafx.ui.canvas.ArcTo#addTo
private void addArcTo(NGPath pgPath, Path2D path,
final double x0, final double y0)
{
double localX = getX();
double localY = getY();
boolean localSweepFlag = isSweepFlag();
boolean localLargeArcFlag = isLargeArcFlag();
// Determine target "to" position
final double xto = (isAbsolute()) ? localX : localX + x0;
final double yto = (isAbsolute()) ? localY : localY + y0;
// Compute the half distance between the current and the final point
final double dx2 = (x0 - xto) / 2.0;
final double dy2 = (y0 - yto) / 2.0;
// Convert angle from degrees to radians
final double xAxisRotationR = Math.toRadians(getXAxisRotation());
final double cosAngle = Math.cos(xAxisRotationR);
final double sinAngle = Math.sin(xAxisRotationR);
//
// Step 1 : Compute (x1, y1)
//
final double x1 = ( cosAngle * dx2 + sinAngle * dy2);
final double y1 = (-sinAngle * dx2 + cosAngle * dy2);
// Ensure radii are large enough
double rx = Math.abs(getRadiusX());
double ry = Math.abs(getRadiusY());
double Prx = rx * rx;
double Pry = ry * ry;
final double Px1 = x1 * x1;
final double Py1 = y1 * y1;
// check that radii are large enough
final double radiiCheck = Px1/Prx + Py1/Pry;
if (radiiCheck > 1.0) {
rx = Math.sqrt(radiiCheck) * rx;
ry = Math.sqrt(radiiCheck) * ry;
if (rx == rx && ry == ry) {/* not NANs */} else {
if (pgPath == null) {
path.lineTo((float) xto, (float) yto);
} else {
pgPath.addLineTo((float) xto, (float) yto);
}
return;
}
Prx = rx * rx;
Pry = ry * ry;
}
//
// Step 2 : Compute (cx1, cy1)
//
double sign = ((localLargeArcFlag == localSweepFlag) ? -1.0 : 1.0);
double sq = ((Prx*Pry)-(Prx*Py1)-(Pry*Px1)) / ((Prx*Py1)+(Pry*Px1));
sq = (sq < 0.0) ? 0.0 : sq;
final double coef = (sign * Math.sqrt(sq));
final double cx1 = coef * ((rx * y1) / ry);
final double cy1 = coef * -((ry * x1) / rx);
//
// Step 3 : Compute (cx, cy) from (cx1, cy1)
//
final double sx2 = (x0 + xto) / 2.0;
final double sy2 = (y0 + yto) / 2.0;
final double cx = sx2 + (cosAngle * cx1 - sinAngle * cy1);
final double cy = sy2 + (sinAngle * cx1 + cosAngle * cy1);
//
// Step 4 : Compute the angleStart (angle1) and the angleExtent (dangle)
//
final double ux = (x1 - cx1) / rx;
final double uy = (y1 - cy1) / ry;
final double vx = (-x1 - cx1) / rx;
final double vy = (-y1 - cy1) / ry;
// Compute the angle start
double n = Math.sqrt((ux * ux) + (uy * uy));
double p = ux; // (1 * ux) + (0 * uy)
sign = ((uy < 0.0) ? -1.0 : 1.0);
double angleStart = Math.toDegrees(sign * Math.acos(p / n));
// Compute the angle extent
n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
p = ux * vx + uy * vy;
sign = ((ux * vy - uy * vx < 0.0) ? -1.0 : 1.0);
double angleExtent = Math.toDegrees(sign * Math.acos(p / n));
if (!localSweepFlag && (angleExtent > 0)) {
angleExtent -= 360.0;
} else if (localSweepFlag && (angleExtent < 0)) {
angleExtent += 360.0;
}
angleExtent = angleExtent % 360;
angleStart = angleStart % 360;
//
// We can now build the resulting Arc2D
//
final float arcX = (float) (cx - rx);
final float arcY = (float) (cy - ry);
final float arcW = (float) (rx * 2.0);
final float arcH = (float) (ry * 2.0);
final float arcStart = (float) -angleStart;
final float arcExtent = (float) -angleExtent;
if (pgPath == null) {
final Arc2D arc =
new Arc2D(arcX, arcY, arcW, arcH,
arcStart, arcExtent, Arc2D.OPEN);
BaseTransform xform = (xAxisRotationR == 0) ? null :
BaseTransform.getRotateInstance(xAxisRotationR, cx, cy);
PathIterator pi = arc.getPathIterator(xform);
// RT-8926, append(true) converts the initial moveTo into a
// lineTo which can generate huge miter joins if the segment
// is small enough. So, we manually skip it here instead.
pi.next();
path.append(pi, true);
} else {
pgPath.addArcTo(arcX, arcY, arcW, arcH,
arcStart, arcExtent, (float) xAxisRotationR);
}
}
Returns a string representation of this ArcTo
object. Returns: a string representation of this ArcTo
object.
/**
* Returns a string representation of this {@code ArcTo} object.
* @return a string representation of this {@code ArcTo} object.
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("ArcTo[");
sb.append("x=").append(getX());
sb.append(", y=").append(getY());
sb.append(", radiusX=").append(getRadiusX());
sb.append(", radiusY=").append(getRadiusY());
sb.append(", xAxisRotation=").append(getXAxisRotation());
if (isLargeArcFlag()) {
sb.append(", lartArcFlag");
}
if (isSweepFlag()) {
sb.append(", sweepFlag");
}
return sb.append("]").toString();
}
}