Implement O2PathContainsPoint()

This commit is contained in:
Robert Grant 2012-08-31 14:28:03 -04:00
parent 5988ce5816
commit 0ef747f0a2
2 changed files with 185 additions and 6 deletions

View File

@ -494,8 +494,6 @@ static int numberOfPointsForOperator(int op){
-(BOOL)containsPoint:(NSPoint)point
{
// TODO: handle winding rule
if ([self isEmpty]) {
return NO;
}
@ -504,7 +502,10 @@ static int numberOfPointsForOperator(int op){
if (NSPointInRect(point, [self bounds]) == NO) {
return NO;
}
// This algorithm implements the NSEvenOddWindingRule
// TODO: port the NSNonZeroWindingRule implementation from O2Path
int cn = 0; // the crossing number counter
int count = [self elementCount];

View File

@ -147,9 +147,187 @@ BOOL O2PathIsRect(O2PathRef self,O2Rect *rect) {
return YES;
}
BOOL O2PathContainsPoint(O2PathRef self,const O2AffineTransform *xform,O2Point point,BOOL evenOdd) {
O2UnimplementedFunction();
return NO;
// _positionOfPointToLine(): tests if a point is Left|On|Right of an infinite line.
// Input: three points lineStart, lineEnd, and the point to test
// Return: >0 for point left of the line
// =0 for point on the line
// <0 for point right of the line
// See: the January 2001 Algorithm "Area of 2D and 3D Triangles and Polygons"
static float _positionOfPointToLine(O2Point point, O2Point lineStart, O2Point lineEnd)
{
float position = ((lineEnd.x - lineStart.x) * (point.y - lineStart.y) -
(point.x - lineStart.x) * (lineEnd.y - lineStart.y));
return position;
}
static BOOL _curveIsFlat(float desiredFlatness, O2Point start, O2Point cp1, O2Point cp2, O2Point end)
{
// Roughly compute the furthest distance of the curved path from the line connecting start to end
double ux = 3.0*cp1.x - 2.0*start.x - end.x; ux *= ux;
double uy = 3.0*cp1.y - 2.0*start.y - end.y; uy *= uy;
double vx = 3.0*cp2.x - 2.0*end.x - start.x; vx *= vx;
double vy = 3.0*cp2.y - 2.0*end.y - start.y; vy *= vy;
if (ux < vx) ux = vx;
if (uy < vy) uy = vy;
return (ux+uy <= desiredFlatness);
}
int _countEvenOddCrossingOfPointAndLine(O2Point point, O2Point lineStart, O2Point lineEnd)
{
if (((lineStart.y <= point.y) && (lineEnd.y > point.y)) || // an upward crossing
((lineStart.y > point.y) && (lineEnd.y <= point.y))) { // a downward crossing
// compute the actual edge-ray intersect x-coordinate
float vt = (float)(point.y - lineStart.y) / (lineEnd.y - lineStart.y);
if (point.x < lineStart.x + vt * (lineEnd.x - lineStart.x)) { // point.x < intersect
return 1; // a valid crossing of y=point.y right of point.x
}
}
return 0;
}
int _countWindingAroundPointByLine(O2Point point, O2Point lineStart, O2Point lineEnd)
{
if (lineStart.y <= point.y) {
if (lineEnd.y > point.y) { // an upward crossing
if (_positionOfPointToLine(point, lineStart, lineEnd) > 0) { // point is left of edge
return 1; // so we have a valid "up" intersect
}
}
}
else { // lineStart.y > point.y (no test needed)
if (lineEnd.y <= point.y) { // a downward crossing
if (_positionOfPointToLine(point, lineStart, lineEnd) < 0) { // point is right of edge
return -1; // so we have a valid "down" intersect
}
}
}
return 0;
}
// Count the crossings on the Y axis from the point to a bezier curve
int _countYCrossingFromPointToCurve(O2Point point, O2Point curveFromPoint, O2Point curveToPoint, O2Point tan1, O2Point tan2, BOOL evenOdd)
{
int count = 0;
// No need to test if there no chance of any crossing
float minY = MIN(MIN(curveFromPoint.y, curveToPoint.y), MIN(tan1.y, tan2.y));
float maxY = MAX(MAX(curveFromPoint.y, curveToPoint.y), MAX(tan1.y, tan2.y));
if (minY > point.y || maxY < point.y) {
return 0;
}
if (_curveIsFlat(0.6, curveFromPoint, tan1, tan2, curveToPoint)) {
if (evenOdd) {
// Flat enough curve : handle it like a segment
count += _countEvenOddCrossingOfPointAndLine(point, curveFromPoint, curveToPoint);
} else {
count += _countWindingAroundPointByLine(point, curveFromPoint, curveToPoint);
}
} else {
// Subdivide the bezier path and test both subpaths - adapted from the flatten path code
O2Point sub1_start = curveFromPoint;
O2Point sub1_cp1 = O2PointMake((curveFromPoint.x + tan1.x)/2, (curveFromPoint.y + tan1.y)/2);
O2Point T = O2PointMake((tan1.x + tan2.x)/2, (tan1.y + tan2.y)/2);
O2Point sub1_cp2 = O2PointMake((sub1_cp1.x + T.x)/2, (sub1_cp1.y + T.y)/2);
O2Point sub2_end = curveToPoint;
O2Point sub2_cp2 = O2PointMake((tan2.x + curveToPoint.x)/2, (tan2.y + curveToPoint.y)/2);
O2Point sub2_cp1 = O2PointMake((T.x + sub2_cp2.x)/2, (T.y + sub2_cp2.y)/2);
O2Point sub2_start = O2PointMake((sub1_cp2.x + sub2_cp1.x)/2, (sub1_cp2.y + sub2_cp1.y)/2);
O2Point sub1_end = sub2_start;
count += _countYCrossingFromPointToCurve(point, sub1_start, sub1_end, sub1_cp1, sub1_cp2, evenOdd);
count += _countYCrossingFromPointToCurve(point, sub2_start, sub2_end, sub2_cp1, sub2_cp2, evenOdd);
}
return count;
}
BOOL O2PathContainsPoint(O2PathRef self,const O2AffineTransform *xform,O2Point point,BOOL evenOdd)
{
if (O2PathIsEmpty(self)) {
return NO;
}
// Some quick test first
if (O2RectContainsPoint(O2PathGetBoundingBox(self), point) == NO) {
return NO;
}
// Adapted from the implementation in NSBezierPath which is in turn taken from
// http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm
int cn = 0; // the crossing number counter
int count = O2PathNumberOfElements(self);
int i = 0;
O2Point startPoint = O2PointZero, currentPoint = O2PointZero, toPoint = O2PointZero;
O2Point *points = self->_points;
uint8_t *element = self->_elements;
for (i = 0; i < count; ++i) {
switch (*element++) {
case kO2PathElementMoveToPoint:
startPoint = currentPoint = points[0];
points++;
break;
case kO2PathElementAddLineToPoint:
toPoint = points[0];
if (evenOdd) {
cn += _countEvenOddCrossingOfPointAndLine(point, currentPoint, toPoint);
} else {
cn += _countWindingAroundPointByLine(point, currentPoint, toPoint);
}
currentPoint = toPoint;
points++;
break;
case kO2PathElementAddQuadCurveToPoint:
{
toPoint = points[1];
CGPoint control = points[0];
// Calc the equivalent Cubic curve control points
CGPoint control1;
control1.x = currentPoint.x/3.f + 2.f*control.x/3.f;
control1.y = currentPoint.y/3.f + 2.f*control.y/3.f;
CGPoint control2;
control2.x = toPoint.x/3.f + 2.f*control.x/3.f;
control2.y = toPoint.y/3.f + 2.f*control.y/3.f;
// And carry on
cn += _countYCrossingFromPointToCurve(point, currentPoint, toPoint, control1, control2, evenOdd);
currentPoint = toPoint;
points+=2;
}
break;
case kO2PathElementAddCurveToPoint:
toPoint = points[2];
cn += _countYCrossingFromPointToCurve(point, currentPoint, toPoint, points[0], points[1], evenOdd);
currentPoint = toPoint;
points+=3;
break;
case kO2PathElementCloseSubpath:
toPoint = startPoint;
if (evenOdd) {
cn += _countEvenOddCrossingOfPointAndLine(point, currentPoint, toPoint);
} else {
cn += _countWindingAroundPointByLine(point, currentPoint, toPoint);
}
currentPoint = startPoint;
break;
}
}
if (evenOdd) {
return (cn&1); // 0 if even (out), and 1 if odd (in)
} else {
return cn != 0; // non-zero means we're inside
}
}
O2Rect O2PathGetBoundingBox(O2PathRef self) {