mirror of
https://github.com/darlinghq/darling-cocotron.git
synced 2024-11-27 05:50:27 +00:00
Implement O2PathContainsPoint()
This commit is contained in:
parent
5988ce5816
commit
0ef747f0a2
@ -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];
|
||||
|
184
Onyx2D/O2Path.m
184
Onyx2D/O2Path.m
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user