2005-01-11 23:50 Alexander Malmberg <alexander@malmberg.org>

* Headers/AppKit/NSBezierPath.h (NSWindingRule)
 	(-containsPoint): Document.
	(-windingCountAtPoint:): New method.
	* Source/NSBezierPath.m: Remove some trailing whitespace.
	(PI): Correct rounding.
	(contribution, -contributionToContains:): Remove.
	(winding_line, winding_curve): New functions.
	(-windingCountAtPoint:): New method.
	(-containsPoint:): Use -windingCountAtPoint: instead of
	-contributionToContains:. Don't flatten the path.


git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@20538 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Alexander Malmberg 2005-01-11 22:53:08 +00:00
parent 3cbd603819
commit ff7516df4b
3 changed files with 335 additions and 121 deletions

View file

@ -1,3 +1,16 @@
2005-01-11 23:50 Alexander Malmberg <alexander@malmberg.org>
* Headers/AppKit/NSBezierPath.h (NSWindingRule)
(-containsPoint): Document.
(-windingCountAtPoint:): New method.
* Source/NSBezierPath.m: Remove some trailing whitespace.
(PI): Correct rounding.
(contribution, -contributionToContains:): Remove.
(winding_line, winding_curve): New functions.
(-windingCountAtPoint:): New method.
(-containsPoint:): Use -windingCountAtPoint: instead of
-contributionToContains:. Don't flatten the path.
2005-01-09 Matt Rice <ratmice@yahoo.com> 2005-01-09 Matt Rice <ratmice@yahoo.com>
* Source/NSTableHeaderCell.m (-drawInteriorWithFrame:inView:): Use * Source/NSTableHeaderCell.m (-drawInteriorWithFrame:inView:): Use

View file

@ -1,7 +1,7 @@
/* /*
The NSBezierPath class The NSBezierPath class
Copyright (C) 1999 Free Software Foundation, Inc. Copyright (C) 1999, 2005 Free Software Foundation, Inc.
Author: Enrico Sersale <enrico@imago.ro> Author: Enrico Sersale <enrico@imago.ro>
Date: Dec 1999 Date: Dec 1999
@ -45,6 +45,17 @@ typedef enum {
NSBevelLineJoinStyle = 2 NSBevelLineJoinStyle = 2
} NSLineJoinStyle; } NSLineJoinStyle;
/** A winding rule defines which points are considered inside and which
points are considered outside a path.
<deflist>
<term>NSNonZeroWindingRule</term>
<desc>A point is inside the path iff the winding count at the point
is non-zero.</desc>
<term>NSEvenOddWindingRule</term>
<desc>A point is inside the path iff the winding count at the point
is odd.</desc>
</deflist>
*/
typedef enum { typedef enum {
NSNonZeroWindingRule, NSNonZeroWindingRule,
NSEvenOddWindingRule NSEvenOddWindingRule
@ -209,7 +220,15 @@ typedef enum {
// //
// Hit detection // Hit detection
// //
/** Returns the winding count, according to the PostScript definition,
at the given point. */
- (int) windingCountAtPoint: (NSPoint)point;
/** Returns YES iff the path contains, according to the current
<ref type="type" id="NSWindingRule">winding rule</ref>, the given point.
*/
- (BOOL)containsPoint:(NSPoint)point; - (BOOL)containsPoint:(NSPoint)point;
// //

View file

@ -2,7 +2,7 @@
<abstract>The NSBezierPath class</abstract> <abstract>The NSBezierPath class</abstract>
Copyright (C) 1999 Free Software Foundation, Inc. Copyright (C) 1999, 2005 Free Software Foundation, Inc.
Author: Enrico Sersale <enrico@imago.ro> Author: Enrico Sersale <enrico@imago.ro>
Date: Dec 1999 Date: Dec 1999
@ -38,7 +38,7 @@
#include <math.h> #include <math.h>
#ifndef PI #ifndef PI
#define PI 3.1415926535897932384626433 #define PI 3.1415926535897932384626434
#endif #endif
// This magic number is 4 *(sqrt(2) -1)/3 // This magic number is 4 *(sqrt(2) -1)/3
@ -455,12 +455,12 @@ static float default_miter_limit = 10.0;
// FIXME: I don't see how this should work with color changes // FIXME: I don't see how this should work with color changes
if(_cacheImage == nil) if(_cacheImage == nil)
{ {
_cacheImage = [[NSImage alloc] initWithSize: bounds.size]; _cacheImage = [[NSImage alloc] initWithSize: bounds.size];
[_cacheImage lockFocus]; [_cacheImage lockFocus];
DPStranslate(ctxt, -origin.x, -origin.y); DPStranslate(ctxt, -origin.x, -origin.y);
[ctxt GSSendBezierPath: self]; [ctxt GSSendBezierPath: self];
DPSstroke(ctxt); DPSstroke(ctxt);
[_cacheImage unlockFocus]; [_cacheImage unlockFocus];
} }
[_cacheImage compositeToPoint: origin operation: NSCompositeCopy]; [_cacheImage compositeToPoint: origin operation: NSCompositeCopy];
} }
@ -483,7 +483,7 @@ static float default_miter_limit = 10.0;
// FIXME: I don't see how this should work with color changes // FIXME: I don't see how this should work with color changes
if(_cacheImage == nil) if(_cacheImage == nil)
{ {
_cacheImage = [[NSImage alloc] initWithSize: bounds.size]; _cacheImage = [[NSImage alloc] initWithSize: bounds.size];
[_cacheImage lockFocus]; [_cacheImage lockFocus];
DPStranslate(ctxt, -origin.x, -origin.y); DPStranslate(ctxt, -origin.x, -origin.y);
[ctxt GSSendBezierPath: self]; [ctxt GSSendBezierPath: self];
@ -768,18 +768,18 @@ static float default_miter_limit = 10.0;
- (NSBezierPathElement) elementAtIndex: (int)index - (NSBezierPathElement) elementAtIndex: (int)index
associatedPoints: (NSPoint *)points associatedPoints: (NSPoint *)points
{ {
[self subclassResponsibility:_cmd]; [self subclassResponsibility:_cmd];
return 0; return 0;
} }
- (NSBezierPathElement) elementAtIndex: (int)index - (NSBezierPathElement) elementAtIndex: (int)index
{ {
return [self elementAtIndex: index associatedPoints: NULL]; return [self elementAtIndex: index associatedPoints: NULL];
} }
- (void)setAssociatedPoints:(NSPoint *)points atIndex:(int)index - (void)setAssociatedPoints:(NSPoint *)points atIndex:(int)index
{ {
[self subclassResponsibility:_cmd]; [self subclassResponsibility:_cmd];
} }
// //
@ -1141,141 +1141,323 @@ static float default_miter_limit = 10.0;
// TODO // TODO
} }
//
// Hit detection /* We use our own point structure with double elements while recursing to
// avoid losing accuracy at really fine subdivisions of curves. */
/* typedef struct
* Return the contribution of a path segment from start to end to
* the containsPoint method for point.
*/
static
int contribution(NSPoint point, float dir, NSPoint start, NSPoint end, BOOL *hit)
{ {
double t; double x, y;
double len; } double_point;
double a;
start.x -= point.x; static int winding_line(double_point from, double_point to, double_point p)
start.y -= point.y; {
len = sqrt(start.x * start.x + start.y * start.y); int y_dir;
a = atan2(start.y, start.x); double k, x;
start.x = cos(a - dir) * len;
start.y = sin(a - dir) * len;
end.x -= point.x;
end.y -= point.y;
len = sqrt(end.x * end.x + end.y * end.y);
a = atan2(end.y, end.x);
end.x = cos(a - dir) * len;
end.y = sin(a - dir) * len;
if ((start.y == 0) || (end.y == 0)) if (from.y == to.y)
{ return 0;
*hit = YES;
return 0;
}
*hit = NO;
// Both point on the same half plain if (to.y < from.y)
if ((start.y < 0) == (end.y < 0))
{ {
return 0; y_dir = -2;
if (p.y < to.y)
return 0;
if (p.y > from.y)
return 0;
}
else
{
y_dir = 2;
if (p.y < from.y)
return 0;
if (p.y > to.y)
return 0;
} }
// Does the line hit the coordinate line on the positive side?
t = (0 - start.y) * (end.x - start.x) / (end.y - start.y) + start.x;
if (t > 0) if (p.y == from.y || p.y == to.y)
y_dir /= 2;
/* The line is intersected. Check if the intersection is outside the
line's bounding box. */
if (to.x < from.x)
{ {
if (start.y > 0) if (p.x < to.x)
return 1; return 0;
else if (p.x > from.x)
return -1; return y_dir;
} }
else else
{ {
if (t == 0) if (p.x < from.x)
*hit = YES; return 0;
return 0; if (p.x > to.x)
return y_dir;
} }
/* Determine the exact x coordinate of the intersection. */
k = (double)(from.x - to.x) / (double)(from.y - to.y);
x = to.x + k * (double)(p.y - to.y);
if (x < p.x)
return y_dir;
return 0;
} }
- (int) contributionToContains: (NSPoint)point static int winding_curve(double_point from, double_point to, double_point c1,
double_point c2, double_point p, int depth)
{ {
int i; double x0, x1;
// Full total of contribution double y0, y1;
int sum = 0; double scale;
// running total for the current subpath
int sub = 0;
NSBezierPathElement type;
NSPoint p, pts[3];
NSPoint first_p, last_p;
int count = [self elementCount];
BOOL first = YES;
float dir = 2 * PI * rand() / RAND_MAX;
BOOL hit;
for(i = 0; i < count; i++) /* Get the vertical extents of the convex hull. */
y0 = y1 = from.y;
if (to.y < y0)
y0 = to.y;
else if (to.y > y1)
y1 = to.y;
if (c1.y < y0)
y0 = c1.y;
else if (c1.y > y1)
y1 = c1.y;
if (c2.y < y0)
y0 = c2.y;
else if (c2.y > y1)
y1 = c2.y;
/* If the point is outside the convex hull, the line can't intersect the
curve. */
if (p.y < y0 || p.y > y1)
return 0;
/* Get the horizontal convex hull. */
x0 = x1 = from.x;
if (to.x < x0)
x0 = to.x;
else if (to.x > x1)
x1 = to.x;
if (c1.x < x0)
x0 = c1.x;
else if (c1.x > x1)
x1 = c1.x;
if (c2.x < x0)
x0 = c2.x;
else if (c2.x > x1)
x1 = c2.x;
/* If the point is left of the convex hull, the line doesn't intersect
the curve. */
if (p.x < x0)
return 0;
/* If the point is right of the convex hull, the net winding count is 0,
1, or -1, and it depends only on how the end-points are placed in
relation to the point. Essentially, it's equivalent to a line. */
if (p.x > x1)
return winding_line(from, to, p);
/* Limit the recursion, just to be safe. */
if (depth >= 40)
return winding_line(from, to, p);
/* The line possibly intersects the curve in some interesting way. If the
curve is flat enough, we can pretend it's a line. Otherwise, we
subdivide and recurse.
First, calculate a suitable scale based on the coordinates of the
convex hull. This is used to get a good cutoff for the subdivision.
Since it's based on the coordinates in the curve, scaling the curve
up or down won't affect relative accuracy. Note that if the scale is
zero, the convex hull, and thus the curve, has no extent. */
scale = fabs(x0) + fabs(x1) + fabs(y0) + fabs(y1);
if (!scale)
return 0;
scale /= 40000000.0;
/* Deal with the degenerate case to == from. */
if (to.x == from.x && to.y == from.y)
{
if (x1 - x0 < scale && y1 - y0 < scale)
return winding_line(from, to, p);
}
else
{
double dx, dy;
double nx, ny;
double d0, d1, d2, d3;
/* Get the direction vector and the normal vector. */
dx = to.x - from.x;
dy = to.y - from.y;
d0 = sqrt(dx * dx + dy * dy);
dx /= d0;
dy /= d0;
nx = dy;
ny = -dx;
/* Check that the distances along the direction vector are
monotone. */
d0 = from.x * dx + from.y * dy;
d1 = c1.x * dx + c1.y * dy;
d2 = c2.x * dx + c2.y * dy;
d3 = to.x * dx + to.y * dy;
if ((d3 > d2 && d2 > d1 && d1 > d0)
|| (d3 < d2 && d2 < d1 && d1 < d0))
{
/* Check that the control points are close to the straigt line
between from and to. */
d0 = to.x * nx + to.y * ny;
d1 = c1.x * nx + c1.y * ny;
d2 = c2.x * nx + c2.y * ny;
if (fabs(d0 - d1) < scale && fabs(d0 - d2) < scale)
{
/* It's flat enough. */
return winding_line(from, to, p);
}
}
}
{
/* Subdivide. */
double_point m, l1, l2, r1, r2;
m.x = (from.x + to.x + 3 * (c1.x + c2.x)) / 8;
m.y = (from.y + to.y + 3 * (c1.y + c2.y)) / 8;
l1.x = (from.x + c1.x) / 2;
l1.y = (from.y + c1.y) / 2;
l2.x = (from.x + 2 * c1.x + c2.x) / 4;
l2.y = (from.y + 2 * c1.y + c2.y) / 4;
r2.x = (to.x + c2.x) / 2;
r2.y = (to.y + c2.y) / 2;
r1.x = (to.x + 2 * c2.x + c1.x) / 4;
r1.y = (to.y + 2 * c2.y + c1.y) / 4;
return winding_curve(from, m, l1, l2, p, depth + 1)
+ winding_curve(m, to, r1, r2, p, depth + 1);
}
}
- (int) windingCountAtPoint: (NSPoint)point
{
int total;
NSBezierPathElement type;
int count;
BOOL first;
NSPoint pts[3];
NSPoint first_p, last_p;
int i;
/* We trace a line from (-INF, point.y) to (point) and count the
intersections. Simple, really. ;)
Lines are trivially checked with a few complications:
a. Tangent lines, i.e. horizontal lines. These can be ignored since
the winding count is undefined on edges.
b. Lines whose endpoints are touched by our infinite line. To get
these right, we return half a winding for such intersections.
Except for intermediate horizontal lines, which are ignored, the
next line will also be intersected in one endpoint. If it's going
in the same direction as the first line, the two half intersections
will add up to one real intersection. If it isn't, the two half
intersections with opposite signs will cancel out. Either way, we
get the right results.
(If this happens for the first element, s/next/previous/ in the
explanation. In practice, we double the winding counts while
working and divide by 2 just before returning.)
Curves are checked first using the convex hull, and if necessary, by
subdividing until they are flat enough to check as lines. We use a
very fine subdivision, and thus get good accuracy. This is possible
because only the parts of the curve that might intersect the line are
subdivided (due to the convex hull checks). */
total = 0;
count = [self elementCount];
/* 'Unroll' the first element to avoid compiler warnings. It has to be
a MoveTo, anyway. */
type = [self elementAtIndex: 0 associatedPoints: pts];
if (type != NSMoveToBezierPathElement)
{
NSWarnLog(@"Invalid path, first element isn't MoveTo.");
return 0;
}
last_p = first_p = pts[0];
first = NO;
#define D(a) (double_point){a.x,a.y}
for (i = 1; i < count; i++)
{ {
type = [self elementAtIndex: i associatedPoints: pts]; type = [self elementAtIndex: i associatedPoints: pts];
switch(type)
switch(type) {
{
case NSMoveToBezierPathElement: case NSMoveToBezierPathElement:
// Was the last sub path closed without close? if (!first)
if (!first && NSEqualPoints(first_p, last_p)) {
{ total += winding_line(D(last_p), D(first_p), D(point));
sum += sub; }
} last_p = first_p = pts[0];
sub = 0; first = NO;
first_p = last_p = pts[0]; break;
first = NO;
break;
case NSLineToBezierPathElement: case NSLineToBezierPathElement:
p = pts[0]; if (first)
if (first) {
{ NSWarnLog(@"Invalid path, LineTo without MoveTo.");
first_p = last_p = p;
first = NO;
}
else
{
sub += contribution(point, dir, last_p, p, &hit);
if (hit)
return 0;
last_p = p;
}
break;
case NSCurveToBezierPathElement:
// Not possible as flattend!
break;
case NSClosePathBezierPathElement:
sub += contribution(point, dir, last_p, first_p, &hit);
if (hit)
return 0; return 0;
sum += sub; }
sub = 0; total += winding_line(D(last_p), D(pts[0]), D(point));
last_p = first_p; last_p = pts[0];
first = YES; break;
break; case NSCurveToBezierPathElement:
if (first)
{
NSWarnLog(@"Invalid path, CurveTo without MoveTo.");
return 0;
}
total += winding_curve(D(last_p), D(pts[2]), D(pts[0]), D(pts[1]), D(point), 0);
last_p = pts[2];
break;
case NSClosePathBezierPathElement:
if (first)
{
NSWarnLog(@"Invalid path, ClosePath with no open subpath.");
return 0;
}
first = YES;
total += winding_line(D(last_p), D(first_p), D(point));
break;
default: default:
break; NSWarnLog(@"Invalid element in path.");
return 0;
} }
}
/*
* Check if the last sub path was closed.
* It wont do any harm, if there was only one point in the subpath,
* as this will result in a subtotal of 0.
*/
if (!first && NSEqualPoints(first_p, last_p))
{
sum += sub;
} }
return sum; if (!first)
total += winding_line(D(last_p), D(first_p), D(point));
#undef D
if (total & 1)
{
/* This should only happen for points on edges, and the winding
count is undefined at such points. */
NSDebugLLog(@"NSBezierPath", @"winding count total is odd");
}
return total / 2;
} }
- (BOOL)containsPoint:(NSPoint)point - (BOOL) containsPoint: (NSPoint)point
{ {
int sum; int sum;
@ -1285,7 +1467,7 @@ int contribution(NSPoint point, float dir, NSPoint start, NSPoint end, BOOL *hit
if (!NSPointInRect(point, [self bounds])) if (!NSPointInRect(point, [self bounds]))
return NO; return NO;
sum = [[self bezierPathByFlatteningPath] contributionToContains: point]; sum = [self windingCountAtPoint: point];
if ([self windingRule] == NSNonZeroWindingRule) if ([self windingRule] == NSNonZeroWindingRule)
{ {
if (sum == 0) if (sum == 0)