问题
I want to verify if a Point is part of a quadratic Bezier curve defined by points p0, p1 and p2..
This is my function to obtain a Point in the curve with a certain t:
public static final Point quadratic (Point p0, Point p1, Point p2, double t) {
double x = Math.pow(1-t, 2) * p0.x + 2 * (1-t) * t * p1.x + Math.pow(t, 2) * p2.x;
double y = Math.pow(1-t, 2) * p0.y + 2 * (1-t) * t * p1.y + Math.pow(t, 2) * p2.y;
return new Point((int)x, (int)y);
}
Considering that the point B(t) in a quadratic curve is obtained as follows:
B(t) = (1 - t)^2 * p0 + 2 * t * (1 - t) * p1 + t^2 * p2
I should verify if a point P belongs to a curve by getting the t value for that point and comparing it to the Point obtained using that t param, but in Java I'm having problems with the precision of the variables.
My function to verify a point is the following:
public static final boolean belongsQuadratic (Point p, Point p0, Point p1, Point p2) {
double[] tx = obtainTs(p.x, p0, p1, p2);
double[] ty = obtainTs(p.y, p0, p1, p2);
if (tx[0] >= 0) {
if ((tx[0] >= ty[0] - ERROR && tx[0] <= ty[0] + ERROR) || (tx[0] >= ty[1] - ERROR && tx[0] <= ty[1] + ERROR)) {
return true;
}
}
if (tx[1] >= 0) {
if ((tx[1] >= ty[0] - ERROR && tx[1] <= ty[0] + ERROR) || (tx[1] >= ty[1] - ERROR && tx[1] <= ty[1] + ERROR)) {
return true;
}
}
return false;
}
public static double[] obtainTs (int comp, Point p0, Point p1, Point p2) {
double a = p0.x - 2*p1.x + p2.x;
double b = 2*p1.x - 2*p0.x ;
double c = p0.x - comp;
double t1 = (-b + Math.sqrt(b*b - 4*a*c)) / (2*a);
double t2 = (-b - Math.sqrt(b*b - 4*a*c)) / (2*a);
return new double[] {t1, t2};
}
So if I run the code with this values:
Point p0 = new Point(320, 480);
Point p1 = new Point(320, 240);
Point p2 = new Point(0, 240);
double t = 0.10f;
Point p = Bezier.quadratic(p0, p1, p2, t);
double[] ts = Bezier.obtainTs(p.x, p0, p1, p2);
I obtain the following output:
For t=0.10000000149011612, java.awt.Point[x=316,y=434]
For t1: -0.1118033988749895, java.awt.Point[x=316,y=536]
For t2: 0.1118033988749895, java.awt.Point[x=316,y=429]
java.awt.Point[x=316,y=434] belongs?: false
Should I use BigDecimal
to perform the operations? Is there another way to verify this? Thanks
回答1:
There is an error here:
double[] ty = obtainTs(p.y, p0, p1, p2);
because obtainTs()
uses the x-coordinates of p0, p1, p2 to find the t-parameter for
the y-coordinate of p.
If you change the method parameters to int
(which can be the x- or y-coordinates of the point):
public static double[] obtainTs (int comp, int p0, int p1, int p2) {
double a = p0 - 2*p1 + p2;
double b = 2*p1 - 2*p0 ;
double c = p0 - comp;
double t1 = (-b + Math.sqrt(b*b - 4*a*c)) / (2*a);
double t2 = (-b - Math.sqrt(b*b - 4*a*c)) / (2*a);
return new double[] {t1, t2};
}
and call it
double[] tx = obtainTs(p.x, p0.x, p1.x, p2.x);
double[] ty = obtainTs(p.y, p0.y, p1.y, p2.y);
then your test code will return "true" (tested with ERROR = 0.02).
Note that if you write down the equation
B(t) = (1 - t)^2 * p0 + 2 * t * (1 - t) * p1 + t^2 * p2
for both x- and y-coordinate, then you can eliminate the t^2-Term and get a single linear equation for t. This gives the following method, which might be slightly simpler and does not use square roots:
public static final boolean belongsQuadratic2 (Point p, Point p0, Point p1, Point p2) {
double ax = p0.x - 2*p1.x + p2.x;
double bx = 2*p1.x - 2*p0.x ;
double cx = p0.x - p.x;
double ay = p0.y - 2*p1.y + p2.y;
double by = 2*p1.y - 2*p0.y ;
double cy = p0.y - p.y;
// "Candidate" for t:
double t = -(cx*ay - cy*ax)/(bx*ay - by*ax);
if (t < 0 || t > 1)
return false;
// Compute the point corresponding to this candidate value ...
Point q = Bezier.quadratic(p0, p1, p2, t);
// ... and check if it is near the given point p:
return Math.abs(q.x - p.x) <= 1 && Math.abs(q.y - p.y) <= 1;
}
Of course, one would have to check for special cases, such as bx*ay - by*ax == 0
.
Note also that it is difficult do decide exactly if a point lies on the curve because the point coordinates are rounded to integers.
回答2:
As an alternative answer, to circumvent the problem noted by Martin R, you can simply build a lookup table and resolve coordinates to on-curve-ness that way. When you build the curve, generate an N-point array of coordinates for incremental t values, and then when you need to test whether a coordinate lies on the curve, find the nearest t value to that coordinate by checking whether that coordinate is in the lookup table, or "near enough to" a coordinate in the lookup table. In code:
point[] points = new point[100];
for(i=0; i<100; i++) {
t = i/100;
points[i] = new point(computeX(t), computeY(t));
}
and then when you need on-curve-testing:
for(i=0; i<points.length; i++) {
point = points[i];
if(abs(point-coordinate)<3) {
// close enough to the curve to count,
// so we can use t value map(i,0,100,0,1)
}
}
building the LUT costs virtually nothing since we need to build this LUT anyway if we want to draw the curve at all, and you're not going to run an on-curve test without first making sure the coordinate is even in the curve's bounding box.
来源:https://stackoverflow.com/questions/17119876/verify-if-a-point-is-part-of-quadratic-bezier-curve-in-java