Common implementation for curve segment scanline

This commit is contained in:
Chlumsky 2023-10-23 15:45:42 +02:00
parent 3e8774abde
commit 5f9f885c4f
2 changed files with 120 additions and 91 deletions

View File

@ -325,6 +325,18 @@ SignedDistance CubicSegment::signedDistance(Point2 origin, double &param) const
#endif #endif
int LinearSegment::scanlineIntersections(double x[3], int dy[3], double y) const { int LinearSegment::scanlineIntersections(double x[3], int dy[3], double y) const {
return horizontalScanlineIntersections(x, dy, y);
}
int QuadraticSegment::scanlineIntersections(double x[3], int dy[3], double y) const {
return horizontalScanlineIntersections(x, dy, y);
}
int CubicSegment::scanlineIntersections(double x[3], int dy[3], double y) const {
return horizontalScanlineIntersections(x, dy, y);
}
int LinearSegment::horizontalScanlineIntersections(double x[3], int dy[3], double y) const {
if ((y >= p[0].y && y < p[1].y) || (y >= p[1].y && y < p[0].y)) { if ((y >= p[0].y && y < p[1].y) || (y >= p[1].y && y < p[0].y)) {
double param = (y-p[0].y)/(p[1].y-p[0].y); double param = (y-p[0].y)/(p[1].y-p[0].y);
x[0] = mix(p[0].x, p[1].x, param); x[0] = mix(p[0].x, p[1].x, param);
@ -334,120 +346,128 @@ int LinearSegment::scanlineIntersections(double x[3], int dy[3], double y) const
return 0; return 0;
} }
int QuadraticSegment::scanlineIntersections(double x[3], int dy[3], double y) const { int LinearSegment::verticalScanlineIntersections(double y[3], int dx[3], double x) const {
if ((x >= p[0].x && x < p[1].x) || (x >= p[1].x && x < p[0].x)) {
double param = (x-p[0].x)/(p[1].x-p[0].x);
y[0] = mix(p[0].y, p[1].y, param);
dx[0] = sign(p[1].x-p[0].x);
return 1;
}
return 0;
}
// p0, p1, q, r, s in the dimension of the scanline
// p0 = scanline - first endpoint
// p1 = scanline - last endpoint
// q, r, s = first, second, third order derivatives (see bezier-solver)
// descendingAtEnd is true if the first non-zero derivative at the last endpoint is negative
static int curveScanlineIntersections(double t[3], int d[3], double p0, double p1, double q, double r, double s, bool descendingAtEnd) {
int total = 0; int total = 0;
int nextDY = y > p[0].y ? 1 : -1; int nextD = p0 > 0 ? 1 : -1;
x[total] = p[0].x; t[total] = 0;
if (p[0].y == y) { if (p0 == 0) {
if (p[0].y < p[1].y || (p[0].y == p[1].y && p[0].y < p[2].y)) if (q > 0 || (q == 0 && (r > 0 || (r == 0 && s > 0))))
dy[total++] = 1; d[total++] = 1;
else else
nextDY = 1; nextD = 1;
} }
{ {
Vector2 ab = p[1]-p[0]; double ts[3];
Vector2 br = p[2]-p[1]-ab; int solutions = solveCubic(ts, s, r, q, -p0);
double t[2];
int solutions = solveQuadratic(t, br.y, 2*ab.y, p[0].y-y);
// Sort solutions // Sort solutions
double tmp; double tmp;
if (solutions >= 2 && t[0] > t[1]) if (solutions >= 2) {
tmp = t[0], t[0] = t[1], t[1] = tmp; if (ts[0] > ts[1])
for (int i = 0; i < solutions && total < 2; ++i) { tmp = ts[0], ts[0] = ts[1], ts[1] = tmp;
if (t[i] >= 0 && t[i] <= 1) { if (solutions >= 3 && ts[1] > ts[2]) {
x[total] = p[0].x+2*t[i]*ab.x+t[i]*t[i]*br.x; tmp = ts[1], ts[1] = ts[2], ts[2] = tmp;
if (nextDY*(ab.y+t[i]*br.y) >= 0) { if (ts[0] > ts[1])
dy[total++] = nextDY; tmp = ts[0], ts[0] = ts[1], ts[1] = tmp;
nextDY = -nextDY; }
}
for (int i = 0; i < solutions && total < 3; ++i) {
if (ts[i] >= 0 && ts[i] <= 1) {
t[total] = ts[i];
if (nextD*(q+(2*r+3*s*ts[i])*ts[i]) >= 0) {
d[total++] = nextD;
nextD = -nextD;
} }
} }
} }
} }
if (p[2].y == y) { if (p1 == 0) {
if (nextDY > 0 && total > 0) { if (nextD > 0 && total > 0) {
--total; --total;
nextDY = -1; nextD = -1;
} }
if ((p[2].y < p[1].y || (p[2].y == p[1].y && p[2].y < p[0].y)) && total < 2) { if (descendingAtEnd && total < 3) {
x[total] = p[2].x; t[total] = 1;
if (nextDY < 0) { if (nextD < 0) {
dy[total++] = -1; d[total++] = -1;
nextDY = 1; nextD = 1;
} }
} }
} }
if (nextDY != (y >= p[2].y ? 1 : -1)) { if (nextD != (p1 >= 0 ? 1 : -1)) {
if (total > 0) if (total > 0)
--total; --total;
else { else {
if (fabs(p[2].y-y) < fabs(p[0].y-y)) if (fabs(p0) > fabs(p1))
x[total] = p[2].x; t[total] = 1;
dy[total++] = nextDY; d[total++] = nextD;
} }
} }
return total; return total;
} }
int CubicSegment::scanlineIntersections(double x[3], int dy[3], double y) const { int QuadraticSegment::horizontalScanlineIntersections(double x[3], int dy[3], double y) const {
int total = 0; double p01 = p[1].y-p[0].y;
int nextDY = y > p[0].y ? 1 : -1; double p12 = p[2].y-p[1].y;
x[total] = p[0].x; bool descendingAtEnd = p[2].y < p[1].y || (p[2].y == p[1].y && p[2].y < p[0].y);
if (p[0].y == y) { double t[3] = { };
if (p[0].y < p[1].y || (p[0].y == p[1].y && (p[0].y < p[2].y || (p[0].y == p[2].y && p[0].y < p[3].y)))) int n = curveScanlineIntersections(t, dy, y-p[0].y, y-p[2].y, 2*p01, p12-p01, 0, descendingAtEnd);
dy[total++] = 1; x[0] = point(t[0]).x;
else x[1] = point(t[1]).x;
nextDY = 1; x[2] = point(t[2]).x;
} return n;
{ }
Vector2 ab = p[1]-p[0];
Vector2 br = p[2]-p[1]-ab; int QuadraticSegment::verticalScanlineIntersections(double y[3], int dx[3], double x) const {
Vector2 as = (p[3]-p[2])-(p[2]-p[1])-br; double p01 = p[1].x-p[0].x;
double t[3]; double p12 = p[2].x-p[1].x;
int solutions = solveCubic(t, as.y, 3*br.y, 3*ab.y, p[0].y-y); bool descendingAtEnd = p[2].x < p[1].x || (p[2].x == p[1].x && p[2].x < p[0].x);
// Sort solutions double t[3] = { };
double tmp; int n = curveScanlineIntersections(t, dx, x-p[0].x, x-p[2].x, 2*p01, p12-p01, 0, descendingAtEnd);
if (solutions >= 2) { y[0] = point(t[0]).y;
if (t[0] > t[1]) y[1] = point(t[1]).y;
tmp = t[0], t[0] = t[1], t[1] = tmp; y[2] = point(t[2]).y;
if (solutions >= 3 && t[1] > t[2]) { return n;
tmp = t[1], t[1] = t[2], t[2] = tmp; }
if (t[0] > t[1])
tmp = t[0], t[0] = t[1], t[1] = tmp; int CubicSegment::horizontalScanlineIntersections(double x[3], int dy[3], double y) const {
} double p01 = p[1].y-p[0].y;
} double p12 = p[2].y-p[1].y;
for (int i = 0; i < solutions && total < 3; ++i) { double p23 = p[3].y-p[2].y;
if (t[i] >= 0 && t[i] <= 1) { bool descendingAtEnd = p[3].y < p[2].y || (p[3].y == p[2].y && (p[3].y < p[1].y || (p[3].y == p[1].y && p[3].y < p[0].y)));
x[total] = p[0].x+3*t[i]*ab.x+3*t[i]*t[i]*br.x+t[i]*t[i]*t[i]*as.x; double t[3] = { };
if (nextDY*(ab.y+2*t[i]*br.y+t[i]*t[i]*as.y) >= 0) { int n = curveScanlineIntersections(t, dy, y-p[0].y, y-p[3].y, 3*p01, 3*(p12-p01), p23-2*p12+p01, descendingAtEnd);
dy[total++] = nextDY; x[0] = point(t[0]).x;
nextDY = -nextDY; x[1] = point(t[1]).x;
} x[2] = point(t[2]).x;
} return n;
} }
}
if (p[3].y == y) { int CubicSegment::verticalScanlineIntersections(double y[3], int dx[3], double x) const {
if (nextDY > 0 && total > 0) { double p01 = p[1].x-p[0].x;
--total; double p12 = p[2].x-p[1].x;
nextDY = -1; double p23 = p[3].x-p[2].x;
} bool descendingAtEnd = p[3].x < p[2].x || (p[3].x == p[2].x && (p[3].x < p[1].x || (p[3].x == p[1].x && p[3].x < p[0].x)));
if ((p[3].y < p[2].y || (p[3].y == p[2].y && (p[3].y < p[1].y || (p[3].y == p[1].y && p[3].y < p[0].y)))) && total < 3) { double t[3] = { };
x[total] = p[3].x; int n = curveScanlineIntersections(t, dx, x-p[0].x, x-p[3].x, 3*p01, 3*(p12-p01), p23-2*p12+p01, descendingAtEnd);
if (nextDY < 0) { y[0] = point(t[0]).y;
dy[total++] = -1; y[1] = point(t[1]).y;
nextDY = 1; y[2] = point(t[2]).y;
} return n;
}
}
if (nextDY != (y >= p[3].y ? 1 : -1)) {
if (total > 0)
--total;
else {
if (fabs(p[3].y-y) < fabs(p[0].y-y))
x[total] = p[3].x;
dy[total++] = nextDY;
}
}
return total;
} }
static void pointBounds(Point2 p, double &l, double &b, double &r, double &t) { static void pointBounds(Point2 p, double &l, double &b, double &r, double &t) {

View File

@ -33,6 +33,9 @@ public:
virtual void distanceToPseudoDistance(SignedDistance &distance, Point2 origin, double param) const; virtual void distanceToPseudoDistance(SignedDistance &distance, Point2 origin, double param) const;
/// Outputs a list of (at most three) intersections (their X coordinates) with an infinite horizontal scanline at y and returns how many there are. /// Outputs a list of (at most three) intersections (their X coordinates) with an infinite horizontal scanline at y and returns how many there are.
virtual int scanlineIntersections(double x[3], int dy[3], double y) const = 0; virtual int scanlineIntersections(double x[3], int dy[3], double y) const = 0;
virtual int horizontalScanlineIntersections(double x[3], int dy[3], double y) const = 0;
/// Outputs a list of (at most three) intersections (their Y coordinates) with an infinite vertical scanline at x and returns how many there are.
virtual int verticalScanlineIntersections(double y[3], int dx[3], double x) const = 0;
/// Adjusts the bounding box to fit the edge segment. /// Adjusts the bounding box to fit the edge segment.
virtual void bound(double &l, double &b, double &r, double &t) const = 0; virtual void bound(double &l, double &b, double &r, double &t) const = 0;
@ -67,6 +70,8 @@ public:
double length() const; double length() const;
SignedDistance signedDistance(Point2 origin, double &param) const; SignedDistance signedDistance(Point2 origin, double &param) const;
int scanlineIntersections(double x[3], int dy[3], double y) const; int scanlineIntersections(double x[3], int dy[3], double y) const;
int horizontalScanlineIntersections(double x[3], int dy[3], double y) const;
int verticalScanlineIntersections(double y[3], int dx[3], double x) const;
void bound(double &l, double &b, double &r, double &t) const; void bound(double &l, double &b, double &r, double &t) const;
void reverse(); void reverse();
@ -96,6 +101,8 @@ public:
double length() const; double length() const;
SignedDistance signedDistance(Point2 origin, double &param) const; SignedDistance signedDistance(Point2 origin, double &param) const;
int scanlineIntersections(double x[3], int dy[3], double y) const; int scanlineIntersections(double x[3], int dy[3], double y) const;
int horizontalScanlineIntersections(double x[3], int dy[3], double y) const;
int verticalScanlineIntersections(double y[3], int dx[3], double x) const;
void bound(double &l, double &b, double &r, double &t) const; void bound(double &l, double &b, double &r, double &t) const;
void reverse(); void reverse();
@ -126,6 +133,8 @@ public:
Vector2 directionChange(double param) const; Vector2 directionChange(double param) const;
SignedDistance signedDistance(Point2 origin, double &param) const; SignedDistance signedDistance(Point2 origin, double &param) const;
int scanlineIntersections(double x[3], int dy[3], double y) const; int scanlineIntersections(double x[3], int dy[3], double y) const;
int horizontalScanlineIntersections(double x[3], int dy[3], double y) const;
int verticalScanlineIntersections(double y[3], int dx[3], double x) const;
void bound(double &l, double &b, double &r, double &t) const; void bound(double &l, double &b, double &r, double &t) const;
void reverse(); void reverse();