Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fix #51- Implement non-zero winding rule

Because we now need to know the path's bounding area before we draw the
path, we collect the min/max pos when points are pushed to the path.

This also removes the copying of the points into a separate vertex
buffer - we draw straight from the std::vector instead.
  • Loading branch information...
commit 16429ca806ec471b6c8349dc44c903f09fdaab60 1 parent ea048fc
@phoboslab authored
Showing with 66 additions and 50 deletions.
  1. +2 −3 Classes/Ejecta/EJCanvas/EJPath.h
  2. +64 −47 Classes/Ejecta/EJCanvas/EJPath.mm
View
5 Classes/Ejecta/EJCanvas/EJPath.h
@@ -15,13 +15,11 @@ typedef enum {
@interface EJPath : NSObject {
EJVector2 currentPos, startPos;
+ EJVector2 minPos, maxPos;
int longestSubpath;
GLubyte stencilMask;
- EJVector2 * vertexBuffer;
- int vertexBufferLength;
-
float distanceTolerance;
CGAffineTransform transform;
@@ -29,6 +27,7 @@ typedef enum {
@property (nonatomic,assign) CGAffineTransform transform;;
+- (void)push:(EJVector2)v;
- (void)reset;
- (void)close;
- (void)endSubPath;
View
111 Classes/Ejecta/EJCanvas/EJPath.mm
@@ -24,21 +24,17 @@ - (id)init {
if(self) {
transform = CGAffineTransformIdentity;
stencilMask = 0x1;
+ [self reset];
}
return self;
}
-- (void)dealloc {
- if( vertexBuffer ) {
- free(vertexBuffer);
- }
- [super dealloc];
-}
-
- (id)copyWithZone:(NSZone *)zone {
EJPath * copy = [[EJPath allocWithZone:zone] init];
copy->currentPos = currentPos;
copy->startPos = startPos;
+ copy->minPos = minPos;
+ copy->maxPos = maxPos;
copy->longestSubpath = longestSubpath;
copy->transform = transform;
@@ -47,6 +43,14 @@ - (id)copyWithZone:(NSZone *)zone {
return copy;
}
+- (void)push:(EJVector2)v {
+ minPos.x = MIN( minPos.x, v.x );
+ minPos.y = MIN( minPos.y, v.y );
+ maxPos.x = MAX( maxPos.x, v.x );
+ maxPos.y = MAX( maxPos.y, v.y );
+ currentPath.push_back(v);
+}
+
- (void)reset {
longestSubpath = 0;
paths.clear();
@@ -54,11 +58,14 @@ - (void)reset {
currentPos = EJVector2Make( 0, 0 );
startPos = EJVector2Make( 0, 0 );
+
+ minPos = EJVector2Make(INFINITY, INFINITY);
+ maxPos = EJVector2Make(-INFINITY, -INFINITY);
}
- (void)close {
if( currentPos.x != startPos.x || currentPos.y != startPos.y ) {
- currentPath.push_back(startPos);
+ [self push:startPos];
currentPos = startPos;
}
[self endSubPath];
@@ -77,12 +84,12 @@ - (void)endSubPath {
- (void)moveToX:(float)x y:(float)y {
[self endSubPath];
currentPos = startPos = EJVector2ApplyTransform( EJVector2Make( x, y ), transform);
- currentPath.push_back(currentPos);
+ [self push:currentPos];
}
- (void)lineToX:(float)x y:(float)y {
currentPos = EJVector2ApplyTransform( EJVector2Make(x, y), transform);
- currentPath.push_back(currentPos);
+ [self push:currentPos];
}
- (void)bezierCurveToCpx1:(float)cpx1 cpy1:(float)cpy1 cpx2:(float)cpx2 cpy2:(float)cpy2 x:(float)x y:(float)y scale:(float)scale {
@@ -95,7 +102,7 @@ - (void)bezierCurveToCpx1:(float)cpx1 cpy1:(float)cpy1 cpx2:(float)cpx2 cpy2:(fl
[self recursiveBezierX1:currentPos.x y1:currentPos.y x2:cp1.x y2:cp1.y x3:cp2.x y3:cp2.y x4:p.x y4:p.y level:0];
currentPos = p;
- currentPath.push_back(currentPos);
+ [self push:currentPos];
}
- (void)recursiveBezierX1:(float)x1 y1:(float)y1
@@ -134,7 +141,7 @@ - (void)recursiveBezierX1:(float)x1 y1:(float)y1
if((d2 + d3)*(d2 + d3) <= distanceTolerance * (dx*dx + dy*dy)) {
// If the curvature doesn't exceed the distance_tolerance value
// we tend to finish subdivisions.
- currentPath.push_back(EJVector2Make(x1234, y1234));
+ [self push:EJVector2Make(x1234, y1234)];
return;
}
}
@@ -142,14 +149,14 @@ - (void)recursiveBezierX1:(float)x1 y1:(float)y1
if( d2 > EJ_PATH_COLLINEARITY_EPSILON ) {
// p1,p3,p4 are collinear, p2 is considerable
if( d2 * d2 <= distanceTolerance * (dx*dx + dy*dy) ) {
- currentPath.push_back(EJVector2Make(x1234, y1234));
+ [self push:EJVector2Make(x1234, y1234)];
return;
}
}
else if( d3 > EJ_PATH_COLLINEARITY_EPSILON ) {
// p1,p2,p4 are collinear, p3 is considerable
if( d3 * d3 <= distanceTolerance * (dx*dx + dy*dy) ) {
- currentPath.push_back(EJVector2Make(x1234, y1234));
+ [self push:EJVector2Make(x1234, y1234)];
return;
}
}
@@ -158,7 +165,7 @@ - (void)recursiveBezierX1:(float)x1 y1:(float)y1
dx = x1234 - (x1 + x4) / 2;
dy = y1234 - (y1 + y4) / 2;
if( dx*dx + dy*dy <= distanceTolerance ) {
- currentPath.push_back(EJVector2Make(x1234, y1234));
+ [self push:EJVector2Make(x1234, y1234)];
return;
}
}
@@ -181,7 +188,7 @@ - (void)quadraticCurveToCpx:(float)cpx cpy:(float)cpy x:(float)x y:(float)y scal
[self recursiveQuadraticX1:currentPos.x y1:currentPos.y x2:cp.x y2:cp.y x3:p.x y3:p.y level:0];
currentPos = p;
- currentPath.push_back(currentPos);
+ [self push:currentPos];
}
- (void)recursiveQuadraticX1:(float)x1 y1:(float)y1
@@ -206,7 +213,7 @@ - (void)recursiveQuadraticX1:(float)x1 y1:(float)y1
if( d > EJ_PATH_COLLINEARITY_EPSILON ) {
// Regular care
if( d * d <= distanceTolerance * (dx*dx + dy*dy) ) {
- currentPath.push_back(EJVector2Make(x123, y123));
+ [self push:EJVector2Make(x123, y123)];
return;
}
}
@@ -215,7 +222,7 @@ - (void)recursiveQuadraticX1:(float)x1 y1:(float)y1
dx = x123 - (x1 + x3) / 2;
dy = y123 - (y1 + y3) / 2;
if( dx*dx + dy*dy <= distanceTolerance ) {
- currentPath.push_back(EJVector2Make(x123, y123));
+ [self push:EJVector2Make(x123, y123)];
return;
}
}
@@ -290,7 +297,7 @@ - (void)arcX:(float)x y:(float)y
float angle = startAngle;
for( int i = 0; i <= steps; i++, angle += stepSize ) {
currentPos = EJVector2ApplyTransform( EJVector2Make( x + cosf(angle) * radius, y + sinf(angle) * radius ), transform);
- currentPath.push_back( currentPos );
+ [self push:currentPos];
}
}
@@ -310,48 +317,55 @@ - (void)drawPolygonsToContext:(EJCanvasContext *)context target:(EJPathPolygonTa
// to fill the created mask with the polygons color.
// TODO: add a fast path for polygons that only have 3 vertices
- // Make sure the vertex buffer holds enough space for the longest subpath
- if( vertexBufferLength < longestSubpath ) {
- vertexBuffer = (EJVector2 *)realloc( vertexBuffer, sizeof(EJVector2) * longestSubpath );
- vertexBufferLength = longestSubpath;
- }
-
[context flushBuffers];
[context createStencilBufferOnce];
- // Disable drawing to the color buffer and draw the polygons to the stencil buffer
- // as a triangle fan.
-
+ // Disable drawing to the color buffer, enable the stencil buffer
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisable(GL_BLEND);
glEnable(GL_STENCIL_TEST);
- glStencilMask(0x01);
- glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
- glStencilFunc(GL_ALWAYS, 0, ~0);
+ glStencilMask(0xff);
+
+ glStencilFunc(GL_ALWAYS, 0, 0xff);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
- float minX = INFINITY, minY = INFINITY, maxX = -INFINITY, maxY = -INFINITY;
+
+ // Clear the needed area in the stencil buffer
+
+ glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
+ [context
+ pushRectX:minPos.x y:minPos.y w:maxPos.x-minPos.x h:maxPos.y-minPos.y
+ tx:0 ty:0 tw:0 th:0
+ color:color withTransform:CGAffineTransformIdentity];
+ [context flushBuffers];
+
+
+ // For each subpath, draw to the stencil buffer twice:
+ // 1) for all back-facing polygons, increase the stencil value
+ // 2) for all front-facing polygons, decrease the stencil value
+
+ glEnable(GL_CULL_FACE);
for( path_t::iterator sp = paths.begin(); sp != paths.end(); ++sp ) {
- int vertexIndex = 0;
- for( subpath_t::iterator vertex = sp->begin(); vertex != sp->end(); ++vertex, ++vertexIndex ) {
- minX = MIN( minX, vertex->x );
- minY = MIN( minY, vertex->y );
- maxX = MAX( maxX, vertex->x );
- maxY = MAX( maxY, vertex->y );
-
- vertexBuffer[vertexIndex] = *vertex;
- }
- glVertexPointer(2, GL_FLOAT, sizeof(EJVector2), vertexBuffer);
- glDrawArrays(GL_TRIANGLE_FAN, 0, vertexIndex);
+ glVertexPointer(2, GL_FLOAT, sizeof(EJVector2), &sp->front());
+
+ glCullFace(GL_BACK);
+ glStencilOp(GL_INCR_WRAP, GL_INCR_WRAP, GL_INCR_WRAP);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, sp->size());
+
+ glCullFace(GL_FRONT);
+ glStencilOp(GL_DECR_WRAP, GL_DECR_WRAP, GL_DECR_WRAP);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, sp->size());
}
+ glDisable(GL_CULL_FACE);
[context bindVertexBuffer];
- // Disable drawing to the stencil buffer, enable drawing to the color or depth buffer
- // and push a rect with the correct size and color to the context.
+ // Enable drawing to the color or depth buffer and push a rect with the correct
+ // size and color to the context. This rect will also clear the stencil buffer
+ // again.
if( target == kEJPathPolygonTargetDepth ) {
glDepthFunc(GL_ALWAYS);
@@ -362,9 +376,12 @@ - (void)drawPolygonsToContext:(EJCanvasContext *)context target:(EJPathPolygonTa
glEnable(GL_BLEND);
}
- glStencilFunc(GL_EQUAL, 0x01, 0x01);
+ glStencilFunc(GL_NOTEQUAL, 0x00, 0xff);
glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
- [context pushRectX:minX y:minY w:maxX-minX h:maxY-minY tx:0 ty:0 tw:0 th:0 color:color withTransform:CGAffineTransformIdentity];
+ [context
+ pushRectX:minPos.x y:minPos.y w:maxPos.x-minPos.x h:maxPos.y-minPos.y
+ tx:0 ty:0 tw:0 th:0
+ color:color withTransform:CGAffineTransformIdentity];
[context flushBuffers];
glDisable(GL_STENCIL_TEST);
Please sign in to comment.
Something went wrong with that request. Please try again.