Created
May 25, 2016 14:58
-
-
Save alexjlockwood/8ca9228e861222866c666513ed401a71 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ArcToBezier { | |
/** | |
* Usage: | |
* | |
* javac ArcToBezier.java | |
* java ArcToBezier x0 y0 x1 x2 rx ry theta large-arc-flag sweep-flag | |
* | |
* where (x0, y0) are the starting points of the arc, and the rest of the | |
* arguments correspond to the values in the elliptical arc curve command: | |
* | |
* A rx ry theta large-arc-flag sweep-flag x1 y1 | |
*/ | |
public static void main(String[] args) { | |
if (args.length != 9) { | |
printUsage(); | |
return; | |
} | |
final float[] floatArgs; | |
try { | |
floatArgs = toFloatArray(args); | |
} catch (NumberFormatException e) { | |
printUsage(); | |
return; | |
} | |
printArc(floatArgs[0], floatArgs[1], | |
floatArgs[7], floatArgs[8], floatArgs[2], floatArgs[3], | |
floatArgs[4], floatArgs[5] != 0, floatArgs[6] != 0); | |
} | |
private static void printUsage() { | |
System.out.println("Usage:\n\n" | |
+ "javac ArcToBezier.java\n" | |
+ "java ArcToBezier x0 y0 x1 x2 rx ry theta large-arc-flag sweep-flag\n\n" + "where (x0, y0) are the starting points of the arc, and the rest of the\n" | |
+ "arguments correspond to the values in the elliptical arc curve command:\n\n" | |
+ "A rx ry theta large-arc-flag sweep-flag x1 y1"); | |
} | |
private static float[] toFloatArray(String[] stringArray) { | |
final float[] floatArray = new float[stringArray.length]; | |
for (int i = 0; i < floatArray.length; i++) { | |
floatArray[i] = Float.parseFloat(stringArray[i]); | |
} | |
return floatArray; | |
} | |
private static void printArc( | |
float x0, | |
float y0, | |
float x1, | |
float y1, | |
float a, | |
float b, | |
float theta, | |
boolean isMoreThanHalf, | |
boolean isPositiveArc) { | |
/* Convert rotation angle from degrees to radians */ | |
double thetaD = Math.toRadians(theta); | |
/* Pre-compute rotation matrix entries */ | |
double cosTheta = Math.cos(thetaD); | |
double sinTheta = Math.sin(thetaD); | |
/* Transform (x0, y0) and (x1, y1) into unit space */ | |
/* using (inverse) rotation, followed by (inverse) scale */ | |
double x0p = (x0 * cosTheta + y0 * sinTheta) / a; | |
double y0p = (-x0 * sinTheta + y0 * cosTheta) / b; | |
double x1p = (x1 * cosTheta + y1 * sinTheta) / a; | |
double y1p = (-x1 * sinTheta + y1 * cosTheta) / b; | |
/* Compute differences and averages */ | |
double dx = x0p - x1p; | |
double dy = y0p - y1p; | |
double xm = (x0p + x1p) / 2; | |
double ym = (y0p + y1p) / 2; | |
/* Solve for intersecting unit circles */ | |
double dsq = dx * dx + dy * dy; | |
if (dsq == 0.0) { | |
return; /* Points are coincident */ | |
} | |
double disc = 1.0 / dsq - 1.0 / 4.0; | |
if (disc < 0.0) { | |
float adjust = (float) (Math.sqrt(dsq) / 1.99999); | |
printArc(x0, y0, x1, y1, a * adjust, | |
b * adjust, theta, isMoreThanHalf, isPositiveArc); | |
return; /* Points are too far apart */ | |
} | |
double s = Math.sqrt(disc); | |
double sdx = s * dx; | |
double sdy = s * dy; | |
double cx; | |
double cy; | |
if (isMoreThanHalf == isPositiveArc) { | |
cx = xm - sdy; | |
cy = ym + sdx; | |
} else { | |
cx = xm + sdy; | |
cy = ym - sdx; | |
} | |
double eta0 = Math.atan2((y0p - cy), (x0p - cx)); | |
double eta1 = Math.atan2((y1p - cy), (x1p - cx)); | |
double sweep = (eta1 - eta0); | |
if (isPositiveArc != (sweep >= 0)) { | |
if (sweep > 0) { | |
sweep -= 2 * Math.PI; | |
} else { | |
sweep += 2 * Math.PI; | |
} | |
} | |
cx *= a; | |
cy *= b; | |
double tcx = cx; | |
cx = cx * cosTheta - cy * sinTheta; | |
cy = tcx * sinTheta + cy * cosTheta; | |
arcToBezier(cx, cy, a, b, x0, y0, thetaD, eta0, sweep); | |
} | |
/** | |
* Converts an arc to cubic Bezier segments and records them in p. | |
* | |
* @param cx The x coordinate center of the ellipse | |
* @param cy The y coordinate center of the ellipse | |
* @param a The radius of the ellipse in the horizontal direction | |
* @param b The radius of the ellipse in the vertical direction | |
* @param e1x E(eta1) x coordinate of the starting point of the arc | |
* @param e1y E(eta2) y coordinate of the starting point of the arc | |
* @param theta The angle that the ellipse bounding rectangle makes with horizontal plane | |
* @param start The start angle of the arc on the ellipse | |
* @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse | |
*/ | |
private static void arcToBezier( | |
double cx, | |
double cy, | |
double a, | |
double b, | |
double e1x, | |
double e1y, | |
double theta, | |
double start, | |
double sweep) { | |
// Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html | |
// and http://www.spaceroots.org/documents/ellipse/node22.html | |
// Maximum of 45 degrees per cubic Bezier segment | |
int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI)); | |
double eta1 = start; | |
double cosTheta = Math.cos(theta); | |
double sinTheta = Math.sin(theta); | |
double cosEta1 = Math.cos(eta1); | |
double sinEta1 = Math.sin(eta1); | |
double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1); | |
double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1); | |
double anglePerSegment = sweep / numSegments; | |
for (int i = 0; i < numSegments; i++) { | |
double eta2 = eta1 + anglePerSegment; | |
double sinEta2 = Math.sin(eta2); | |
double cosEta2 = Math.cos(eta2); | |
double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2); | |
double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2); | |
double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2; | |
double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2; | |
double tanDiff2 = Math.tan((eta2 - eta1) / 2); | |
double alpha = | |
Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3; | |
double q1x = e1x + alpha * ep1x; | |
double q1y = e1y + alpha * ep1y; | |
double q2x = e2x - alpha * ep2x; | |
double q2y = e2y - alpha * ep2y; | |
System.out.println(String.format("C %.2f %.2f %.2f %.2f %.2f %.2f", q1x, q1y, q2x, q2y, e2x, e2y)); | |
eta1 = eta2; | |
e1x = e2x; | |
e1y = e2y; | |
ep1x = ep2x; | |
ep1y = ep2y; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment