When creating games with large levels it is often easy to run into memory limitations. Large maps also contain repetitive drawing of things like grass, trees, mountains, and so on. This recipe will show you how to efficiently render a polygon that is filled in with a repeated texture. These can be drawn at any size and still only use a small amount of memory and CPU time.
Execute the following code:
#import "Vector3D.h" //Included for CPP polygon triangulation #import "triangulate.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> @implementation TexturedPolygon @synthesize vertices, triangles; +(id) createWithFile:(NSString*)file withVertices:(NSArray*)verts { /*** Create a TexturedPolygon with vertices only. ***/ /*** Perform polygon trianglulation to get triangles. ***/ //Initialization TexturedPolygon *tp = [TexturedPolygon spriteWithFile:file]; tp.vertices = [[NSMutableArray alloc] init]; tp.triangles = [[NSMutableArray alloc] init]; //Polygon Triangulation Vector2dVector a; for(int i=0; i<[verts count];i+=1){ //Add polygon vertices [tp.vertices addObject:[verts objectAtIndex:i]]; //Add polygon vertices to triangulation container CGPoint vert = [[verts objectAtIndex:i] CGPointValue]; a.push_back( Vector2d(vert.x, vert.y) ); } //Run triangulation algorithm Vector2dVector result; Triangulate::Process(a,result); //Gather all triangles from result container int tcount = result.size()/3; for (int i=0; i<tcount; i++) { const Vector2d &p1 = result[i*3+0]; const Vector2d &p2 = result[i*3+1]; const Vector2d &p3 = result[i*3+2]; //Add triangle index [tp.triangles addObject: [tp getTriangleIndicesFromPoint1: ccp(p1.GetX(),p1.GetY()) point2:ccp(p2.GetX(),p2.GetY()) point3:ccp(p3.GetX(), p3.GetY())] ]; } //Set texture coordinate information [tp setCoordInfo]; return tp; } +(id) createWithFile:(NSString*)file withVertices:(NSArray*)verts withTriangles:(NSArray*)tris { /*** Create a TexturedPolygon with vertices and triangles given. ***/ //Initialization TexturedPolygon *tp = [TexturedPolygon spriteWithFile:file]; tp.vertices = [[NSMutableArray alloc] init]; tp.triangles = [[NSMutableArray alloc] init]; //Set polygon vertices for(int i=0; i<[verts count];i+=1){ [tp.vertices addObject:[verts objectAtIndex:i]]; } //Set triangle indices for(int i=0; i<[tris count];i+=1){ [tp.triangles addObject:[tris objectAtIndex:i]]; } //Set texture coordinate information [tp setCoordInfo]; return tp; } -(Vector3D*) getTriangleIndicesFromPoint1:(CGPoint)p1 point2:(CGPoint)p2 point3:(CGPoint)p3 { /*** Convert three polygon vertices to triangle indices ***/ Vector3D* indices = [Vector3D x:-1 y:-1 z:-1]; for(int i=0; i< [vertices count]; i++){ CGPoint vert = [[vertices objectAtIndex:i] CGPointValue]; if(p1.x == vert.x and p1.y == vert.y){ indices.x = i; }else if(p2.x == vert.x and p2.y == vert.y){ indices.y = i; }else if(p3.x == vert.x and p3.y == vert.y){ indices.z = i; } } return indices; } -(void) addAnimFrameWithFile:(NSString*)file toArray:(NSMutableArray*)arr { /*** For textured polygon animation ***/ ccTexParams params = {GL_NEAREST,GL_NEAREST_MIPMAP_NEAREST,GL_REPEAT,GL_REPEAT}; CCTexture2D *frameTexture = [[CCTextureCache sharedTextureCache] addImage:file]; [frameTexture setTexParameters:¶ms]; CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:frameTexture rect:self.textureRect]; [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFrame:frameTexture name:file]; [arr addObject:frame]; } -(void) setCoordInfo { /*** Set texture coordinates for each vertex ***/ if(coords){ free(coords); } coords = (ccV2F_T2F*)malloc(sizeof(ccV2F_T2F)*[vertices count]); for(int i=0;i<[vertices count];i++) { coords[i].vertices.x = [[vertices objectAtIndex:i] CGPointValue].x; coords[i].vertices.y = [[vertices objectAtIndex:i] CGPointValue].y; float atlasWidth = texture_.pixelsWide; float atlasHeight = texture_.pixelsHigh; coords[i].texCoords.u = (coords[i].vertices.x + rect_.origin.x)/ atlasWidth; coords[i].texCoords.v = (contentSize_.height - coords[i].vertices.y + rect_.origin.y)/ atlasHeight ; } } -(void) dealloc { //Release texture coordinates if necessary if(coords) free(coords); [super dealloc]; } -(void) draw { /*** This is where the magic happens. Texture and draw all triangles. ***/ glDisableClientState(GL_COLOR_ARRAY); glColor4ub( color_.r, color_.g, color_.b, quad_.bl.colors.a); BOOL newBlend = NO; if( blendFunc_.src != CC_BLEND_SRC || blendFunc_.dst != CC_BLEND_DST ) { newBlend = YES; glBlendFunc( blendFunc_.src, blendFunc_.dst ); } glBindTexture(GL_TEXTURE_2D, texture_.name); unsigned int offset = (unsigned int)coords; unsigned int diff = offsetof( ccV2F_T2F, vertices); glVertexPointer(2, GL_FLOAT, sizeof(ccV2F_T2F), (void*) (offset + diff)); diff = offsetof( ccV2F_T2F, texCoords); glTexCoordPointer(2, GL_FLOAT, sizeof(ccV2F_T2F), (void*) (offset + diff)); for(int i=0;i<[triangles count];i++){ Vector3D *tri = [triangles objectAtIndex:i]; short indices[] = {tri.x, tri.y, tri.z}; glDrawElements(GL_TRIANGLE_STRIP, 3, GL_UNSIGNED_SHORT, indices); } if(newBlend) { glBlendFunc(CC_BLEND_SRC, CC_BLEND_DST); } glColor4ub( 255, 255, 255, 255); glEnableClientState(GL_COLOR_ARRAY); } @end @implementation Ch1_RenderTexturedPolygon -(CCLayer*) runRecipe { CGSize s = [[CCDirector sharedDirector] winSize]; //Set polygon vertices CGPoint vertexArr[] = { ccp(248,340), ccp(200,226), ccp(62,202), ccp(156,120), ccp(134,2), ccp(250,64), ccp(360,0), ccp(338,128), ccp(434,200), ccp(306,230) }; int numVerts = 10; NSMutableArray *vertices = [[NSMutableArray alloc] init]; //Add vertices to array for(int i=0; i<numVerts; i++){ [vertices addObject:[NSValue valueWithCGPoint:vertexArr[i]]]; } //Note: Your texture size MUST be a product of 2 for this to work. //Set texture parameters to repeat ccTexParams params = {GL_NEAREST,GL_NEAREST_MIPMAP_NEAREST,GL_REPEAT,GL_REPEAT}; //Create textured polygon TexturedPolygon *texturedPoly = [TexturedPolygon createWithFile:@"bricks.jpg" withVertices:vertices]; [texturedPoly.texture setTexParameters:¶ms]; texturedPoly.position = ccp(128,128); //Add textured polygon to scene [self addChild:texturedPoly z:1 tag:0]; return self; } @end
TexturedPolygon
takes a given set of vertices and uses a polygon triangulation algorithm
to find all triangles contained within the polygon. It then textures and draws these triangles using OpenGL triangles strips.
Triangulation:
Triangulation , depending on the polygon, can be a complex process. This is often performed while a map is loading. For very complex polygons it can be advantageous to perform polygon triangulation during level creation and store triangle indices along with the polygon vertices. This can speed up level load times.
Textured polygons have many uses including static map textures and background textures.
Using this technique you can efficiently draw polygons of virtually any size. Space requirements rely on the size of each texture used rather that the size of each polygon. To use less space, modify
TexturedPolygon
to re-use pre-initialized textures.This technique has a few caveats. The textures used must be square and each side's size must be equal to 2n (16x16, 32x32, 64x64, and so on). Also, textures can only be single files, not sprite frames.
This recipe may be your first foray into combining Objective-C
and C++
code. This is commonly referred to as Objective-C++
. For more information please refer to Apple's official developer documentation Using C++ With Objective-C at http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocCPlusPlus.html.