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:

  1. #import <Foundation/Foundation.h>
  2. #import "cocos2d.h"
  3.  
  4. @interface PolygonNode : CCNode
  5.  
  6. // Store the number of points in the polygon
  7. @property (nonatomic, readwrite) int numberOfPoints;
  8.  
  9. @property (nonatomic, readwrite) ccVertex2F * glPoints ;
  10.  
  11. // The stroke color
  12. @property (nonatomic, readwrite) ccColor4B strokeColor;
  13.  
  14. // The fill color
  15. @property (nonatomic, readwrite) ccColor4B fillColor;
  16.  
  17. // If a stroke should be drawn
  18. @property (nonatomic, readwrite) BOOL stroke;
  19.  
  20. // If the polygon should be filled
  21. @property (nonatomic, readwrite) BOOL fill;
  22.  
  23. // Whether to close the polygon
  24. @property (nonatomic, readwrite) BOOL closed;
  25.  
  26. // Standard constructor
  27. +(id) newPolygonNode: (NSMutableArray *) newPoints;
  28.  
  29. // Draw the polygon node as a rectangle
  30. +(id) newPolygonNodeAsRectangle: (int) width: (int) height;
  31.  
  32. // Set the points using a NSMutableArray
  33. -(void) setupPoints: (NSMutableArray*) points;
  34.  
  35. @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.

  1. #import "PolygonNode.h"
  2.  
  3. @implementation PolygonNode
  4.  
  5. @synthesize strokeColor;
  6. @synthesize fillColor;
  7. @synthesize glPoints;
  8. @synthesize numberOfPoints;
  9.  
  10. @synthesize stroke;
  11. @synthesize fill;
  12. @synthesize closed;
  13.  
  14. // Initialise the object. Allocate the array of points and set the default values.
  15. // by default this will draw a closed white polygon with a red border
  16. -(id) init {
  17. if( (self=[super init]) ) {
  18. //points = [[NSMutableArray alloc] initWithObjects: nil];
  19.  
  20. closed = YES;
  21. fill = YES;
  22. stroke = YES;
  23.  
  24. self.strokeColor = ccc4(255, 0, 0, 255);
  25. self.fillColor = ccc4(255, 255, 255, 255);
  26. }
  27. return self;
  28. }
  29.  
  30. // Override the node draw method. Call the ccDrawPoly method
  31. // to perform the OpenGL draw operation
  32. -(void) draw {
  33. [super draw];
  34. [self ccDrawPoly];
  35. }
  36.  
  37. // Draw the polygon using OpenGL
  38. -(void) ccDrawPoly {
  39.  
  40. // Disable textures - we want to draw with plaine colors
  41. glDisableClientState(GL_COLOR_ARRAY);
  42. glDisableClientState(GL_TEXTURE_COORD_ARRAY);
  43. glDisable(GL_TEXTURE_2D);
  44.  
  45. // Setup the vertex pointer. This puts gives OpenGL access
  46. // to our array of points
  47. glVertexPointer(2, GL_FLOAT, 0, self.glPoints);
  48.  
  49. if(fill) {
  50. // Set the OpenGL color
  51. glColor4ub(fillColor.r, fillColor.g, fillColor.b, fillColor.a);
  52.  
  53. // Draw the polygon
  54. if(closed)
  55. glDrawArrays(GL_TRIANGLE_FAN, 0, numberOfPoints);
  56. else
  57. glDrawArrays(GL_TRIANGLE_STRIP, 0, numberOfPoints);
  58. }
  59. if(stroke) {
  60. glColor4f(strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a);
  61. if(closed)
  62. glDrawArrays(GL_LINE_LOOP, 0, (GLsizei) numberOfPoints);
  63. else
  64. glDrawArrays(GL_LINE_STRIP, 0, (GLsizei) numberOfPoints);
  65. }
  66.  
  67.  
  68. // restore default state
  69. glEnableClientState(GL_COLOR_ARRAY);
  70. glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  71. glEnable(GL_TEXTURE_2D);
  72.  
  73. }
  74.  
  75. // Constructor to create a polygon from an NSMutableArray of points
  76. +(id) newPolygonNode: (NSMutableArray *) newPoints {
  77. return [[self alloc] initPolygonNode: newPoints] ;
  78. }
  79.  
  80. // Set polygon as rectangle
  81. + (id) newPolygonNodeAsRectangle:(int)width withHeight:(int)height {
  82. NSMutableArray * pts = [[NSMutableArray alloc] initWithObjects:Nil];
  83.  
  84. [pts addObject:[NSValue valueWithCGPoint:ccp(0,0)]];
  85. [pts addObject:[NSValue valueWithCGPoint:ccp(width,0)]];
  86. [pts addObject:[NSValue valueWithCGPoint:ccp(width,height)]];
  87. [pts addObject:[NSValue valueWithCGPoint:ccp(0,height)]];
  88.  
  89. return [[self alloc] initPolygonNode: pts];
  90. }
  91.  
  92. -(id) initPolygonNode: (NSMutableArray *) points {
  93. if((self = [self init] )) {
  94. [self setupPoints:points];
  95. }
  96. return self;
  97. }
  98.  
  99. // Convert a NSMutableArray into an array of ccVertex2F which can
  100. // be understood by OpenGL
  101. -(void) setupPoints: (NSMutableArray*) points {
  102. self.numberOfPoints = [points count];
  103.  
  104. ccVertex2F *newPoint = malloc(numberOfPoints * sizeof(ccVertex2F));
  105. if (newPoint == NULL) {
  106. // Handle error
  107. }
  108.  
  109. // iPhone and 32-bit machines
  110. if( sizeof(CGPoint) == sizeof(ccVertex2F) ) {
  111. for(NSUInteger i=0; i<numberOfPoints; i++) {
  112. NSValue *val = [points objectAtIndex:i];
  113. CGPoint pt = [val CGPointValue];
  114. newPoint[i] = (ccVertex2F) { pt.x * CC_CONTENT_SCALE_FACTOR(), pt.y * CC_CONTENT_SCALE_FACTOR() };
  115. }
  116. } else {
  117. // 64-bit machines (Mac)
  118.  
  119. for(NSUInteger i=0; i<numberOfPoints; i++) {
  120. NSValue *val = [points objectAtIndex:i];
  121. CGPoint pt = [val CGPointValue];
  122. newPoint[i] = (ccVertex2F) { pt.x, pt.y };
  123. }
  124. }
  125.  
  126. self.glPoints = newPoint;
  127. }
  128.  
  129. -(void) dealloc {
  130. free(self.glPoints);
  131. [super dealloc];
  132. }
  133. @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:

  1. // Create an array of CGPoints
  2. CGPoint points[] = {ccp(0,0), ccp(100,0), ccp(100,100), ccp(50, 150), ccp(0,100)};
  3. // Create a new mutable array
  4. NSMutableArray * result = [[NSMutableArray alloc] initWithObjects: nil];
  5.  
  6. // Add the points to the mutable array using a NSValue wrapper
  7. for(int i=0; i<(sizeof(points)/sizeof(CGPoint)); i++) {
  8. NSValue* point = [NSValue valueWithCGPoint:points[i]];
  9. [result addObject:point];
  10. }
  11.  
  12. // Create the polygon
  13. poly = [PolygonNode newPolygonNode: result];
  14.  
  15. // Set the fill color
  16. poly.fillColor = ccc4(0, 255, 100, 255);
  17.  
  18. // Add the polygon to the scene
  19. [self addChild:poly];

Hope this is useful to someone!

Tweet: 

Comments

thanks a lot for sharing, it's a very useful class.

Greate Job.
Plus , can you add one method to detect the collision ,
something like ,detect the region is intersected with other shapes ?

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.

Thanks a lot

will is better download source files ;)
thanks

Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <c>, <cpp>, <drupal5>, <drupal6>, <java>, <javascript>, <php>, <python>, <ruby>. The supported tag styles are: <foo>, [foo].
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.