ORIGINAL ARTICLE
I am in the process of trying to implement raywenderlich's tutorial on generating hills with repeating striped coordinates using cocos2d, This article was written for Cocos2D 1.0, and as I am trying to port it to Cocos2D 2.0 This means updating it for openGl-es 2. So far everything has worked perfectly, However I am having problems with getting the texture of the hill to repeat properly...
Here is my code:
Sending the hills the texture:
CCSprite *stripes = [self stripedSpriteWithColor1:color3 color2:color4 textureSize:512 stripes:nStripes];
stripes.position = ccp(winSize.width/2,winSize.height/2);
ccTexParams tp2 = {GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_CLAMP_TO_EDGE};
[stripes.texture setTexParameters:&tp2];
_terrain.stripes = stripes;
_backgroundTerrain.stripes = stripes;
Generating texture:
-(CCSprite *)stripedSpriteWithColor1:(ccColor4F)c1 color2:(ccColor4F)c2 textureSize:(float)textureSize stripes:(int) nStripes {
// 1: Create new CCRenderTexture
CCRenderTexture *rt = [CCRenderTexture renderTextureWithWidth:textureSize height:textureSize];
// 2: Call CCRenderTexture:begin
[rt beginWithClear:c1.r g:c1.g b:c1.b a:c1.a];
// 3: Draw into texture
//OpenGL gradient
NSLog(@"Strip color is: %f : %f : %f", c2.r,c2.g,c2.b);
CGPoint vertices[nStripes*6];
ccColor4F colors[nStripes*6];
int nVertices = 0;
float x1 = -textureSize;
float x2;
float y1 = textureSize;
float y2 = 0;
float dx = textureSize / nStripes * 2;
float stripeWidth = dx/2;
ccColor4F stripColor = (ccColor4F){c2.r,c2.g,c2.b,c2.a};
for (int i=0; i<nStripes; i++) {
x2 = x1 + textureSize;
colors[nVertices] = stripColor;
vertices[nVertices++] = ccpMult(CGPointMake(x1, y1), CC_CONTENT_SCALE_FACTOR());
colors[nVertices] = stripColor;
vertices[nVertices++] = ccpMult(CGPointMake(x1+stripeWidth, y1), CC_CONTENT_SCALE_FACTOR());
colors[nVertices] = stripColor;
vertices[nVertices++] = ccpMult(CGPointMake(x2, y2), CC_CONTENT_SCALE_FACTOR());
colors[nVertices] = stripColor;
vertices[nVertices++] = vertices[nVertices-3];
colors[nVertices] = stripColor;
vertices[nVertices++] = vertices[nVertices-3];
colors[nVertices] = stripColor;
vertices[nVertices++] = ccpMult(CGPointMake(x2+stripeWidth, y2), CC_CONTENT_SCALE_FACTOR());
x1 += dx;
}
[self.shaderProgram use];
ccGLEnableVertexAttribs(kCCVertexAttribFlag_Position | kCCVertexAttribFlag_Color);
glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_FLOAT, GL_FALSE, 0, colors);
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)nVertices);
//Gradient
float gradientAlpha = 0.2;
nVertices = 0;
vertices[nVertices] = CGPointMake(0, 0);
colors[nVertices++] = (ccColor4F){0,0,0,0};
vertices[nVertices] = CGPointMake(textureSize, 0);
colors[nVertices++] = (ccColor4F){0,0,0,0};
vertices[nVertices] = CGPointMake(0, textureSize);
colors[nVertices++] = (ccColor4F){0,0,0,gradientAlpha};
vertices[nVertices] = CGPointMake(textureSize, textureSize);
colors[nVertices++] = (ccColor4F){0,0,0,gradientAlpha};
glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_FLOAT, GL_FALSE, 0, colors);
glDrawArrays(GL_TRIANGLE_STRIP,0, (GLsizei)nVertices);
// Highlighting
float borderWidth = textureSize/8;
float borderAlpha = 0.1f;
nVertices = 0;
vertices[nVertices] = CGPointMake(0, 0);
colors [nVertices++] = (ccColor4F){1,1,1,borderAlpha};
vertices[nVertices] = CGPointMake(textureSize*CC_CONTENT_SCALE_FACTOR(),0);
colors [nVertices++] = (ccColor4F){1,1,1,borderAlpha};
vertices[nVertices] = CGPointMake(0, borderWidth*CC_CONTENT_SCALE_FACTOR());
colors [nVertices++] = (ccColor4F){0,0,0,0};
vertices[nVertices] = CGPointMake(textureSize*CC_CONTENT_SCALE_FACTOR(),borderWidth*CC_CONTENT_SCALE_FACTOR());
colors [nVertices++] = (ccColor4F){0,0,0,0};
glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_FLOAT, GL_FALSE, 0, colors);
glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA);
glDrawArrays(GL_TRIANGLE_STRIP, 0, (GLsizei)nVertices);
//Noise
CCSprite *noise = [CCSprite spriteWithFile:@"noise.png"];
[noise setBlendFunc:(ccBlendFunc){GL_DST_COLOR, GL_ZERO}];
noise.position = ccp(textureSize/2, textureSize/2);
[noise visit];
[rt end];
// Return texture sprite
return [CCSprite spriteWithTexture:rt.sprite.texture];
}
Getting TexCoords for bounding the stripes to the hill:
- (void)resetHillVertices {
CGSize winSize = [CCDirector sharedDirector].winSize;
static int prevFromKeyPointI = -1;
static int prevToKeyPointI = -1;
// key points interval for drawing
while (_hillKeyPoints[_fromKeyPointI+1].x < _offsetX-winSize.width/self.scale) {
_fromKeyPointI++;
}
while (_hillKeyPoints[_toKeyPointI].x < _offsetX+winSize.width*3/2/self.scale) {
_toKeyPointI++;
}
if (prevFromKeyPointI != _fromKeyPointI || prevToKeyPointI != _toKeyPointI) {
_nHillVertices = 0;
_nBorderVertices = 0;
CGPoint p0, p1, pt0, pt1;
p0 = _hillKeyPoints[_fromKeyPointI];
for (int i=_fromKeyPointI+1; i<_toKeyPointI+1; i++) {
p1 = _hillKeyPoints[i];
// triangle strip between p0 and p1
int hSegments = floorf((p1.x-p0.x)/kHillSegmentWidth);
float dx = (p1.x - p0.x) / hSegments;
float da = M_PI / hSegments;
float ymid = (p0.y + p1.y) / 2;
float ampl = (p0.y - p1.y) / 2;
pt0 = p0;
_borderVertices[_nBorderVertices++] = pt0;
for (int j=1; j<hSegments+1; j++) {
pt1.x = p0.x + j*dx;
pt1.y = ymid + ampl * cosf(da*j);
_borderVertices[_nBorderVertices++] = pt1;
_hillVertices[_nHillVertices] = CGPointMake(pt0.x, 0);
_hillTexCoords[_nHillVertices++] = CGPointMake(pt0.x/512, 1.0f);
_hillVertices[_nHillVertices] = CGPointMake(pt1.x, 0);
_hillTexCoords[_nHillVertices++] = CGPointMake(pt1.x/512, 1.0f);
_hillVertices[_nHillVertices] = CGPointMake(pt0.x, pt0.y);
_hillTexCoords[_nHillVertices++] = CGPointMake(pt0.x/512, 0);
_hillVertices[_nHillVertices] = CGPointMake(pt1.x, pt1.y);
_hillTexCoords[_nHillVertices++] = CGPointMake(pt1.x/512, 0);
pt0 = pt1;
}
p0 = p1;
}
prevFromKeyPointI = _fromKeyPointI;
prevToKeyPointI = _toKeyPointI;
[self resetBox2DBody];
}
}
Drawing the texture:
- (void) draw {
self.shaderProgram = [[CCShaderCache sharedShaderCache] programForKey:kCCShader_PositionTexture];
CC_NODE_DRAW_SETUP();
ccGLBlendFunc( CC_BLEND_SRC, CC_BLEND_DST ); //TB 25-08-12: Allows change of blend function
ccGLEnableVertexAttribs(kCCVertexAttribFlag_Position | kCCVertexAttribFlag_TexCoords);
ccGLBindTexture2D(_stripes.texture.name);
// Assign the vertices array to the 'position' attribute
glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, _hillVertices);
// Assign the texCoords array to the 'TexCoords' attribute
glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, 0, _hillTexCoords);
glDrawArrays(GL_TRIANGLE_STRIP, 0, (GLsizei)_nHillVertices);
}
The problem I'm having is this: after a certain number of repeats the texture starts to degrade in quality, like so:
Is there any way to get the texture to repeat without degradation?
EDIT 1:
I've veen doing more analysis into how the texture degrades, it turns out it doesn't do it continuously, but degrades with power of 2 repetitions so it degrades for the first time on the first repeat then after 2 repeats, then 4, 8, 16, 32 and so on... It also seems that the vertical bands that start to appear that can be seen in the image double in width each time the image degrades in quality. Also on each degradation the frame rate of the game decreases substantially so I'm starting to think this is probably a memory issue.
EDIT 2:
My best guess at why this is happening so far is because the -draw method for the terrain is continually making GL_TRAINGLE_STRIP, and not deleting them once they are off-screen causing a build up in the memory usage of the terrain, causing the degradation and frame rate drop.
UPDATE 1
I have solved two of the problems that were occurring with my texture generation...
Solving Misalignment
IN the sprite generation method this:
float x1 = -textureSize;
float x2;
float y1 = textureSize;
float y2 = 0;
float dx = textureSize / nStripes * 2;
to this:
float x1 = -winSize.width;
float x2;
float y1 = winSize.height;
float y2 = 0;
float dx = winSize.width / nStripes * 2;
I realised that this was totally unrelated to the main error, rather it was due to my stripes for some reason not appearing at a 45 degree angle, which causes them to misalign on repeat. I tried to think of reasons for this, and finally fixed it by assuming that the textures coordinate origin was at the top left corner of the screen as opposed to the top left corner of the texture.
Solving Degradation (Kind of)
I had an inkling that the image degradation was occurring due to the large amounts of repetitions of the texture, due to a similar reason as this Although I may be wrong on that front!
To solve this in the resetHillVertices I set it up so the texCoords are always between 0 and 1 meaning that the texture bound to the hills is always the first repetition of the texture. I implemented this like so:
for (int j=1; j<hSegments+1; j++) {
pt1.x = p0.x + j*dx;
pt1.y = ymid + ampl * cosf(da*j);
_borderVertices[_nBorderVertices++] = pt1;
float xTex0 = pt0.x/512;
float xTex1 = pt1.x/512;
while (xTex0 > 1) { // makes sure texture coordinates are always within the first repetition of texture
xTex0 -= 1;
}
while (xTex1 > 1) {
xTex1 -= 1;
}
_hillVertices[_nHillVertices] = CGPointMake(pt0.x, 0);
_hillTexCoords[_nHillVertices++] = CGPointMake(xTex0, 1.0);
_hillVertices[_nHillVertices] = CGPointMake(pt1.x, 0);
_hillTexCoords[_nHillVertices++] = CGPointMake(xTex1, 1.0);
_hillVertices[_nHillVertices] = CGPointMake(pt0.x, pt0.y);
_hillTexCoords[_nHillVertices++] = CGPointMake(xTex0,