javafx.scene.shape.ArcTo Maven / Gradle / Ivy
/*
* 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 {@link Path} and
* {@link 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 {@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.
*
*
*
*
*
* 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:
*
*
*
*
* @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.
*/
public ArcTo() {
ArcToHelper.initHelper(this);
}
/**
* 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
*/
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
*/
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
*/
private DoubleProperty xAxisRotation;
/**
* 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.
* @return the x-axis rotation in degrees.
*/
public final double getXAxisRotation() {
return xAxisRotation == null ? 0.0 : xAxisRotation.get();
}
/**
* 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
*/
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
*/
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
*/
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
*/
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 {@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();
}
}