Cocos2D drawing a custom filled polygon
In this article I'm going to explain how to draw custom polygons using Cocos2D and OpenGL. Cocos2D has the capability to draw primative polygons using the ccDrawPrimative class. This however comes with some limitations:
- It is not possible to draw filled polygons
- The functions are primative OpenGL commands which means that you can't take advantage of Cocos2D's node actions
To get around this I've written a PolygonNode class. This extends Cocos2D's node object and allows you to create nodes which contain custom polygons.
The Interface:
#import <Foundation/Foundation.h> #import "cocos2d.h" @interface PolygonNode : CCNode // Store the number of points in the polygon @property (nonatomic, readwrite) int numberOfPoints; @property (nonatomic, readwrite) ccVertex2F * glPoints ; // The stroke color @property (nonatomic, readwrite) ccColor4B strokeColor; // The fill color @property (nonatomic, readwrite) ccColor4B fillColor; // If a stroke should be drawn @property (nonatomic, readwrite) BOOL stroke; // If the polygon should be filled @property (nonatomic, readwrite) BOOL fill; // Whether to close the polygon @property (nonatomic, readwrite) BOOL closed; // Standard constructor +(id) newPolygonNode: (NSMutableArray *) newPoints; // Draw the polygon node as a rectangle +(id) newPolygonNodeAsRectangle: (int) width: (int) height; // Set the points using a NSMutableArray -(void) setupPoints: (NSMutableArray*) points; @end
The Implementation:
This code defined the object properties. Properties can be set to decide whether the polygon is filled, stroked and closed as well as the fill and stroke color.
#import "PolygonNode.h" @implementation PolygonNode @synthesize strokeColor; @synthesize fillColor; @synthesize glPoints; @synthesize numberOfPoints; @synthesize stroke; @synthesize fill; @synthesize closed; // Initialise the object. Allocate the array of points and set the default values. // by default this will draw a closed white polygon with a red border -(id) init { if( (self=[super init]) ) { //points = [[NSMutableArray alloc] initWithObjects: nil]; closed = YES; fill = YES; stroke = YES; self.strokeColor = ccc4(255, 0, 0, 255); self.fillColor = ccc4(255, 255, 255, 255); } return self; } // Override the node draw method. Call the ccDrawPoly method // to perform the OpenGL draw operation -(void) draw { [super draw]; [self ccDrawPoly]; } // Draw the polygon using OpenGL -(void) ccDrawPoly { // Disable textures - we want to draw with plaine colors glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisable(GL_TEXTURE_2D); // Setup the vertex pointer. This puts gives OpenGL access // to our array of points glVertexPointer(2, GL_FLOAT, 0, self.glPoints); if(fill) { // Set the OpenGL color glColor4ub(fillColor.r, fillColor.g, fillColor.b, fillColor.a); // Draw the polygon if(closed) glDrawArrays(GL_TRIANGLE_FAN, 0, numberOfPoints); else glDrawArrays(GL_TRIANGLE_STRIP, 0, numberOfPoints); } if(stroke) { glColor4f(strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a); if(closed) glDrawArrays(GL_LINE_LOOP, 0, (GLsizei) numberOfPoints); else glDrawArrays(GL_LINE_STRIP, 0, (GLsizei) numberOfPoints); } // restore default state glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnable(GL_TEXTURE_2D); } // Constructor to create a polygon from an NSMutableArray of points +(id) newPolygonNode: (NSMutableArray *) newPoints { return [[self alloc] initPolygonNode: newPoints] ; } // Set polygon as rectangle + (id) newPolygonNodeAsRectangle:(int)width withHeight:(int)height { NSMutableArray * pts = [[NSMutableArray alloc] initWithObjects:Nil]; [pts addObject:[NSValue valueWithCGPoint:ccp(0,0)]]; [pts addObject:[NSValue valueWithCGPoint:ccp(width,0)]]; [pts addObject:[NSValue valueWithCGPoint:ccp(width,height)]]; [pts addObject:[NSValue valueWithCGPoint:ccp(0,height)]]; return [[self alloc] initPolygonNode: pts]; } -(id) initPolygonNode: (NSMutableArray *) points { if((self = [self init] )) { [self setupPoints:points]; } return self; } // Convert a NSMutableArray into an array of ccVertex2F which can // be understood by OpenGL -(void) setupPoints: (NSMutableArray*) points { self.numberOfPoints = [points count]; ccVertex2F *newPoint = malloc(numberOfPoints * sizeof(ccVertex2F)); if (newPoint == NULL) { // Handle error } // iPhone and 32-bit machines if( sizeof(CGPoint) == sizeof(ccVertex2F) ) { for(NSUInteger i=0; i<numberOfPoints; i++) { NSValue *val = [points objectAtIndex:i]; CGPoint pt = [val CGPointValue]; newPoint[i] = (ccVertex2F) { pt.x * CC_CONTENT_SCALE_FACTOR(), pt.y * CC_CONTENT_SCALE_FACTOR() }; } } else { // 64-bit machines (Mac) for(NSUInteger i=0; i<numberOfPoints; i++) { NSValue *val = [points objectAtIndex:i]; CGPoint pt = [val CGPointValue]; newPoint[i] = (ccVertex2F) { pt.x, pt.y }; } } self.glPoints = newPoint; } -(void) dealloc { free(self.glPoints); [super dealloc]; } @end
It should be relatively clear what's going on if you are in any familiar with OpenGL and Objective C. In case you're not here's a brief explanation.
This class extends the Cocos2D CCNode class. This means that it can be included in a game layer just like any other node. This has that advantage that you can animate your polygon using actions. When it starts up the class takes a list of points in the form of a NSMutableArray. These points are then translated into a format that OpenGL can understand in the setupPoints method. OpenGL uses primitive C data types. Essentially the code does the following:
- Creates a pointer of type ccVertex2F and allocates enough memory to hold all our points. To know the size of memory required we multiply the size of each chunk (ccVertex2F) buy the number of chunks (points). Malloc is a C function which allocates this memory for us.
- Next we loop over our NSMutableArray of points and insert the x and y coordinates of the point into our primitive array
- Finally we set our instance variable glPoints to point at the this array
Next looking at the draw method. First we have to disable textures because we're only interested in drawing block colors. Then set up a vertex pointer. This tells OpenGL to look at the block of memory containing our points. Next we set the color. OpenGL is a state machine so if we set one color OpenGL will continue using that color until we change it again. Next we draw our shape using either a triangle fan or closed shapes or a triangle strip for open shapes. Next we do the same for our stroke. Finally we enable textures again.
Here's a typical usage pattern for this class:
// Create an array of CGPoints CGPoint points[] = {ccp(0,0), ccp(100,0), ccp(100,100), ccp(50, 150), ccp(0,100)}; // Create a new mutable array NSMutableArray * result = [[NSMutableArray alloc] initWithObjects: nil]; // Add the points to the mutable array using a NSValue wrapper for(int i=0; i<(sizeof(points)/sizeof(CGPoint)); i++) { NSValue* point = [NSValue valueWithCGPoint:points[i]]; [result addObject:point]; } // Create the polygon poly = [PolygonNode newPolygonNode: result]; // Set the fill color poly.fillColor = ccc4(0, 255, 100, 255); // Add the polygon to the scene [self addChild:poly];
Hope this is useful to someone!
Comments
Stéphane (not verified)
Sun, 06/03/2012 - 13:59
Permalink
great class
thanks a lot for sharing, it's a very useful class.
F (not verified)
Mon, 08/13/2012 - 11:04
Permalink
Greate Job.
Greate Job.
Plus , can you add one method to detect the collision ,
something like ,detect the region is intersected with other shapes ?
bensmiley
Wed, 08/22/2012 - 17:02
Permalink
Collisions
Personally I'd use a physics engine to do that. Cocos2D comes with Chipmunk and Box2D so it's already integrated. If you're looking to use complex polygons you'd do best to download Shape Workshop and take a look at the included libraries. I've included a method which will take an arbitrary polygon and create a Box2D shape from it. Generally it's difficult to create intersection algorithms for arbitrary polygons and since there are libraries which do it very efficiently I choose to use them rather than trying to work it out myself.
Dulguun (not verified)
Wed, 08/22/2012 - 06:51
Permalink
thanks
Thanks a lot
nik (not verified)
Thu, 10/18/2012 - 14:26
Permalink
source downloading
will is better download source files ;)
thanks
Add new comment