|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.awt.geom; |
|
|
|
import java.awt.geom.Rectangle2D; |
|
import java.awt.geom.PathIterator; |
|
import java.awt.geom.QuadCurve2D; |
|
import java.util.Vector; |
|
|
|
final class Order2 extends Curve { |
|
private double x0; |
|
private double y0; |
|
private double cx0; |
|
private double cy0; |
|
private double x1; |
|
private double y1; |
|
private double xmin; |
|
private double xmax; |
|
|
|
private double xcoeff0; |
|
private double xcoeff1; |
|
private double xcoeff2; |
|
private double ycoeff0; |
|
private double ycoeff1; |
|
private double ycoeff2; |
|
|
|
public static void insert(Vector curves, double tmp[], |
|
double x0, double y0, |
|
double cx0, double cy0, |
|
double x1, double y1, |
|
int direction) |
|
{ |
|
int numparams = getHorizontalParams(y0, cy0, y1, tmp); |
|
if (numparams == 0) { |
|
// We are using addInstance here to avoid inserting horisontal |
|
|
|
addInstance(curves, x0, y0, cx0, cy0, x1, y1, direction); |
|
return; |
|
} |
|
|
|
double t = tmp[0]; |
|
tmp[0] = x0; tmp[1] = y0; |
|
tmp[2] = cx0; tmp[3] = cy0; |
|
tmp[4] = x1; tmp[5] = y1; |
|
split(tmp, 0, t); |
|
int i0 = (direction == INCREASING)? 0 : 4; |
|
int i1 = 4 - i0; |
|
addInstance(curves, tmp[i0], tmp[i0 + 1], tmp[i0 + 2], tmp[i0 + 3], |
|
tmp[i0 + 4], tmp[i0 + 5], direction); |
|
addInstance(curves, tmp[i1], tmp[i1 + 1], tmp[i1 + 2], tmp[i1 + 3], |
|
tmp[i1 + 4], tmp[i1 + 5], direction); |
|
} |
|
|
|
public static void addInstance(Vector curves, |
|
double x0, double y0, |
|
double cx0, double cy0, |
|
double x1, double y1, |
|
int direction) { |
|
if (y0 > y1) { |
|
curves.add(new Order2(x1, y1, cx0, cy0, x0, y0, -direction)); |
|
} else if (y1 > y0) { |
|
curves.add(new Order2(x0, y0, cx0, cy0, x1, y1, direction)); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static int getHorizontalParams(double c0, double cp, double c1, |
|
double ret[]) { |
|
if (c0 <= cp && cp <= c1) { |
|
return 0; |
|
} |
|
c0 -= cp; |
|
c1 -= cp; |
|
double denom = c0 + c1; |
|
|
|
if (denom == 0) { |
|
return 0; |
|
} |
|
double t = c0 / denom; |
|
|
|
if (t <= 0 || t >= 1) { |
|
return 0; |
|
} |
|
ret[0] = t; |
|
return 1; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static void split(double coords[], int pos, double t) { |
|
double x0, y0, cx, cy, x1, y1; |
|
coords[pos+8] = x1 = coords[pos+4]; |
|
coords[pos+9] = y1 = coords[pos+5]; |
|
cx = coords[pos+2]; |
|
cy = coords[pos+3]; |
|
x1 = cx + (x1 - cx) * t; |
|
y1 = cy + (y1 - cy) * t; |
|
x0 = coords[pos+0]; |
|
y0 = coords[pos+1]; |
|
x0 = x0 + (cx - x0) * t; |
|
y0 = y0 + (cy - y0) * t; |
|
cx = x0 + (x1 - x0) * t; |
|
cy = y0 + (y1 - y0) * t; |
|
coords[pos+2] = x0; |
|
coords[pos+3] = y0; |
|
coords[pos+4] = cx; |
|
coords[pos+5] = cy; |
|
coords[pos+6] = x1; |
|
coords[pos+7] = y1; |
|
} |
|
|
|
public Order2(double x0, double y0, |
|
double cx0, double cy0, |
|
double x1, double y1, |
|
int direction) |
|
{ |
|
super(direction); |
|
// REMIND: Better accuracy in the root finding methods would |
|
// ensure that cy0 is in range. As it stands, it is never |
|
|
|
if (cy0 < y0) { |
|
cy0 = y0; |
|
} else if (cy0 > y1) { |
|
cy0 = y1; |
|
} |
|
this.x0 = x0; |
|
this.y0 = y0; |
|
this.cx0 = cx0; |
|
this.cy0 = cy0; |
|
this.x1 = x1; |
|
this.y1 = y1; |
|
xmin = Math.min(Math.min(x0, x1), cx0); |
|
xmax = Math.max(Math.max(x0, x1), cx0); |
|
xcoeff0 = x0; |
|
xcoeff1 = cx0 + cx0 - x0 - x0; |
|
xcoeff2 = x0 - cx0 - cx0 + x1; |
|
ycoeff0 = y0; |
|
ycoeff1 = cy0 + cy0 - y0 - y0; |
|
ycoeff2 = y0 - cy0 - cy0 + y1; |
|
} |
|
|
|
public int getOrder() { |
|
return 2; |
|
} |
|
|
|
public double getXTop() { |
|
return x0; |
|
} |
|
|
|
public double getYTop() { |
|
return y0; |
|
} |
|
|
|
public double getXBot() { |
|
return x1; |
|
} |
|
|
|
public double getYBot() { |
|
return y1; |
|
} |
|
|
|
public double getXMin() { |
|
return xmin; |
|
} |
|
|
|
public double getXMax() { |
|
return xmax; |
|
} |
|
|
|
public double getX0() { |
|
return (direction == INCREASING) ? x0 : x1; |
|
} |
|
|
|
public double getY0() { |
|
return (direction == INCREASING) ? y0 : y1; |
|
} |
|
|
|
public double getCX0() { |
|
return cx0; |
|
} |
|
|
|
public double getCY0() { |
|
return cy0; |
|
} |
|
|
|
public double getX1() { |
|
return (direction == DECREASING) ? x0 : x1; |
|
} |
|
|
|
public double getY1() { |
|
return (direction == DECREASING) ? y0 : y1; |
|
} |
|
|
|
public double XforY(double y) { |
|
if (y <= y0) { |
|
return x0; |
|
} |
|
if (y >= y1) { |
|
return x1; |
|
} |
|
return XforT(TforY(y)); |
|
} |
|
|
|
public double TforY(double y) { |
|
if (y <= y0) { |
|
return 0; |
|
} |
|
if (y >= y1) { |
|
return 1; |
|
} |
|
return TforY(y, ycoeff0, ycoeff1, ycoeff2); |
|
} |
|
|
|
public static double TforY(double y, |
|
double ycoeff0, double ycoeff1, double ycoeff2) |
|
{ |
|
// The caller should have already eliminated y values |
|
|
|
ycoeff0 -= y; |
|
if (ycoeff2 == 0.0) { |
|
// The quadratic parabola has degenerated to a line. |
|
// ycoeff1 should not be 0.0 since we have already eliminated |
|
// totally horizontal lines, but if it is, then we will generate |
|
// infinity here for the root, which will not be in the [0,1] |
|
|
|
double root = -ycoeff0 / ycoeff1; |
|
if (root >= 0 && root <= 1) { |
|
return root; |
|
} |
|
} else { |
|
|
|
double d = ycoeff1 * ycoeff1 - 4.0 * ycoeff2 * ycoeff0; |
|
|
|
if (d >= 0.0) { |
|
d = Math.sqrt(d); |
|
// For accuracy, calculate one root using: |
|
// (-ycoeff1 +/- d) / 2ycoeff2 |
|
// and the other using: |
|
// 2ycoeff0 / (-ycoeff1 +/- d) |
|
// Choose the sign of the +/- so that ycoeff1+d |
|
|
|
if (ycoeff1 < 0.0) { |
|
d = -d; |
|
} |
|
double q = (ycoeff1 + d) / -2.0; |
|
|
|
double root = q / ycoeff2; |
|
if (root >= 0 && root <= 1) { |
|
return root; |
|
} |
|
if (q != 0.0) { |
|
root = ycoeff0 / q; |
|
if (root >= 0 && root <= 1) { |
|
return root; |
|
} |
|
} |
|
} |
|
} |
|
/* We failed to find a root in [0,1]. What could have gone wrong? |
|
* First, remember that these curves are constructed to be monotonic |
|
* in Y and totally horizontal curves have already been eliminated. |
|
* Now keep in mind that the Y coefficients of the polynomial form |
|
* of the curve are calculated from the Y coordinates which define |
|
* our curve. They should theoretically define the same curve, |
|
* but they can be off by a couple of bits of precision after the |
|
* math is done and so can represent a slightly modified curve. |
|
* This is normally not an issue except when we have solutions near |
|
* the endpoints. Since the answers we get from solving the polynomial |
|
* may be off by a few bits that means that they could lie just a |
|
* few bits of precision outside the [0,1] range. |
|
* |
|
* Another problem could be that while the parametric curve defined |
|
* by the Y coordinates has a local minima or maxima at or just |
|
* outside of the endpoints, the polynomial form might express |
|
* that same min/max just inside of and just shy of the Y coordinate |
|
* of that endpoint. In that case, if we solve for a Y coordinate |
|
* at or near that endpoint, we may be solving for a Y coordinate |
|
* that is below that minima or above that maxima and we would find |
|
* no solutions at all. |
|
* |
|
* In either case, we can assume that y is so near one of the |
|
* endpoints that we can just collapse it onto the nearest endpoint |
|
* without losing more than a couple of bits of precision. |
|
*/ |
|
// First calculate the midpoint between y0 and y1 and choose to |
|
// return either 0.0 or 1.0 depending on whether y is above |
|
// or below the midpoint... |
|
// Note that we subtracted y from ycoeff0 above so both y0 and y1 |
|
// will be "relative to y" so we are really just looking at where |
|
|
|
double y0 = ycoeff0; |
|
double y1 = ycoeff0 + ycoeff1 + ycoeff2; |
|
return (0 < (y0 + y1) / 2) ? 0.0 : 1.0; |
|
} |
|
|
|
public double XforT(double t) { |
|
return (xcoeff2 * t + xcoeff1) * t + xcoeff0; |
|
} |
|
|
|
public double YforT(double t) { |
|
return (ycoeff2 * t + ycoeff1) * t + ycoeff0; |
|
} |
|
|
|
public double dXforT(double t, int deriv) { |
|
switch (deriv) { |
|
case 0: |
|
return (xcoeff2 * t + xcoeff1) * t + xcoeff0; |
|
case 1: |
|
return 2 * xcoeff2 * t + xcoeff1; |
|
case 2: |
|
return 2 * xcoeff2; |
|
default: |
|
return 0; |
|
} |
|
} |
|
|
|
public double dYforT(double t, int deriv) { |
|
switch (deriv) { |
|
case 0: |
|
return (ycoeff2 * t + ycoeff1) * t + ycoeff0; |
|
case 1: |
|
return 2 * ycoeff2 * t + ycoeff1; |
|
case 2: |
|
return 2 * ycoeff2; |
|
default: |
|
return 0; |
|
} |
|
} |
|
|
|
public double nextVertical(double t0, double t1) { |
|
double t = -xcoeff1 / (2 * xcoeff2); |
|
if (t > t0 && t < t1) { |
|
return t; |
|
} |
|
return t1; |
|
} |
|
|
|
public void enlarge(Rectangle2D r) { |
|
r.add(x0, y0); |
|
double t = -xcoeff1 / (2 * xcoeff2); |
|
if (t > 0 && t < 1) { |
|
r.add(XforT(t), YforT(t)); |
|
} |
|
r.add(x1, y1); |
|
} |
|
|
|
public Curve getSubCurve(double ystart, double yend, int dir) { |
|
double t0, t1; |
|
if (ystart <= y0) { |
|
if (yend >= y1) { |
|
return getWithDirection(dir); |
|
} |
|
t0 = 0; |
|
} else { |
|
t0 = TforY(ystart, ycoeff0, ycoeff1, ycoeff2); |
|
} |
|
if (yend >= y1) { |
|
t1 = 1; |
|
} else { |
|
t1 = TforY(yend, ycoeff0, ycoeff1, ycoeff2); |
|
} |
|
double eqn[] = new double[10]; |
|
eqn[0] = x0; |
|
eqn[1] = y0; |
|
eqn[2] = cx0; |
|
eqn[3] = cy0; |
|
eqn[4] = x1; |
|
eqn[5] = y1; |
|
if (t1 < 1) { |
|
split(eqn, 0, t1); |
|
} |
|
int i; |
|
if (t0 <= 0) { |
|
i = 0; |
|
} else { |
|
split(eqn, 0, t0 / t1); |
|
i = 4; |
|
} |
|
return new Order2(eqn[i+0], ystart, |
|
eqn[i+2], eqn[i+3], |
|
eqn[i+4], yend, |
|
dir); |
|
} |
|
|
|
public Curve getReversedCurve() { |
|
return new Order2(x0, y0, cx0, cy0, x1, y1, -direction); |
|
} |
|
|
|
public int getSegment(double coords[]) { |
|
coords[0] = cx0; |
|
coords[1] = cy0; |
|
if (direction == INCREASING) { |
|
coords[2] = x1; |
|
coords[3] = y1; |
|
} else { |
|
coords[2] = x0; |
|
coords[3] = y0; |
|
} |
|
return PathIterator.SEG_QUADTO; |
|
} |
|
|
|
public String controlPointString() { |
|
return ("("+round(cx0)+", "+round(cy0)+"), "); |
|
} |
|
} |