/*
* Copyright 2003-2010 Tufts University Licensed under the
* Educational Community License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://www.osedu.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package tufts.vue.shape;
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) {
curves.add(new Order2(x0, y0, cx0, cy0, x1, y1, direction));
return;
}
// assert(numparams == 1);
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);
Order2 c1 = getInstance(x0, y0,
tmp[2], tmp[3],
tmp[4], tmp[5],
direction);
Order2 c2 = getInstance(tmp[4], tmp[5],
tmp[6], tmp[7],
x1, y1,
direction);
if (direction == INCREASING) {
curves.add(c1);
curves.add(c2);
} else {
curves.add(c2);
curves.add(c1);
}
}
public static Order2 getInstance(double x0, double y0,
double cx0, double cy0,
double x1, double y1,
int direction) {
if (y0 > y1) {
return new Order2(x1, y1, cx0, cy0, x0, y0, -direction);
} else {
return new Order2(x0, y0, cx0, cy0, x1, y1, direction);
}
}
/*
* Fill an array with the coefficients of the parametric equation
* in t, ready for solving with solveQuadratic.
* We currently have:
* Py(t) = C0*(1-t)^2 + 2*CP*t*(1-t) + C1*t^2
* = C0 - 2*C0*t + C0*t^2 + 2*CP*t - 2*CP*t^2 + C1*t^2
* = C0 + (2*CP - 2*C0)*t + (C0 - 2*CP + C1)*t^2
* Py(t) = (C0) + (2*CP - 2*C0)*t + (C0 - 2*CP + C1)*t^2
* Py(t) = C + Bt + At^2
* C = C0
* B = 2*CP - 2*C0
* A = C0 - 2*CP + C1
*/
public static void getEqn(double eqn[], double c0, double cp, double c1) {
eqn[0] = c0;
eqn[1] = cp + cp - c0 - c0;
eqn[2] = c0 - cp - cp + c1;
}
/*
* Return the count of the number of horizontal sections of the
* specified quadratic Bezier curve. Put the parameters for the
* horizontal sections into the specified <code>ret</code> array.
* <p>
* If we examine the parametric equation in t from the getEqn method
* and take the derivative, we get:
* Py(t) = At^2 + Bt + C
* dPy(t) = 2At + B = 0
* 2*(C0 - 2*CP + C1)t + 2*(CP - C0) = 0
* 2*(C0 - 2*CP + C1)t = 2*(C0 - CP)
* t = 2*(C0 - CP) / 2*(C0 - 2*CP + C1)
* t = (C0 - CP) / (C0 - CP + C1 - CP)
* Note that this method will return 0 if the equation is a line,
* which is either always horizontal or never horizontal.
* Completely horizontal curves need to be eliminated by other
* means outside of this method.
*/
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 then cp == (c0+c1)/2 and we have a line.
if (denom == 0) {
return 0;
}
double t = c0 / denom;
// No splits at t==0 and t==1
if (t <= 0 || t >= 1) {
return 0;
}
ret[0] = t;
return 1;
}
/*
* Split the quadratic Bezier stored at coords[pos...pos+5] representing
* the paramtric range [0..1] into two subcurves representing the
* parametric subranges [0..t] and [t..1]. Store the results back
* into the array at coords[pos...pos+5] and coords[pos+4...pos+9].
*/
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
// more than "1 mantissa bit" out of range...
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) {
double eqn[] = new double[3];
getEqn(eqn, y0, cy0, y1);
eqn[0] -= y;
int numroots = QuadCurve2D.solveQuadratic(eqn, eqn);
return firstValidRoot(eqn, numroots);
}
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 = -ycoeff1 / (2 * ycoeff2);
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) {
if (ystart == y0 && yend == y1) {
return getWithDirection(dir);
}
double eqn[] = new double[10];
double t0, t1;
if (ystart == y0) {
t0 = 0;
} else {
getEqn(eqn, y0, cy0, y1);
eqn[0] -= ystart;
int numroots = QuadCurve2D.solveQuadratic(eqn, eqn);
t0 = firstValidRoot(eqn, numroots);
}
if (yend == y1) {
t1 = 1;
} else {
getEqn(eqn, y0, cy0, y1);
eqn[0] -= yend;
int numroots = QuadCurve2D.solveQuadratic(eqn, eqn);
t1 = firstValidRoot(eqn, numroots);
}
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);
}
if (t0 <= 0) {
return new Order2(eqn[0], ystart,
eqn[2], eqn[3],
eqn[4], yend,
dir);
} else {
split(eqn, 0, t0 / t1);
return new Order2(eqn[4], ystart,
eqn[6], eqn[7],
eqn[8], 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)+"), ");
}
}