/libs/cocos2d/CCRibbon.m
Objective C | 380 lines | 267 code | 46 blank | 67 comment | 24 complexity | f7d9fe6b906d11cdc59f5f69b77a3f12 MD5 | raw file
Possible License(s): Apache-2.0
1/* 2 * cocos2d for iPhone: http://www.cocos2d-iphone.org 3 * 4 * Copyright (c) 2008, 2009 Jason Booth 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 * THE SOFTWARE. 23 */ 24 25/* 26 * A ribbon is a dynamically generated list of polygons drawn as a single or series 27 * of triangle strips. The primary use of Ribbon is as the drawing class of Motion Streak, 28 * but it is quite useful on it's own. When manually drawing a ribbon, you can call addPointAt 29 * and pass in the parameters for the next location in the ribbon. The system will automatically 30 * generate new polygons, texture them accourding to your texture width, etc, etc. 31 * 32 * Ribbon data is stored in a RibbonSegment class. This class statically allocates enough verticies and 33 * texture coordinates for 50 locations (100 verts or 48 triangles). The ribbon class will allocate 34 * new segments when they are needed, and reuse old ones if available. The idea is to avoid constantly 35 * allocating new memory and prefer a more static method. However, since there is no way to determine 36 * the maximum size of some ribbons (motion streaks), a truely static allocation is not possible. 37 * 38 */ 39 40 41#import "CCRibbon.h" 42#import "CCTextureCache.h" 43#import "Support/CGPointExtension.h" 44#import "ccMacros.h" 45 46// 47// Ribbon 48// 49@implementation CCRibbon 50@synthesize blendFunc=blendFunc_; 51@synthesize color=color_; 52@synthesize textureLength = textureLength_; 53 54+(id)ribbonWithWidth:(float)w image:(NSString*)path length:(float)l color:(ccColor4B)color fade:(float)fade 55{ 56 self = [[[self alloc] initWithWidth:w image:path length:l color:color fade:fade] autorelease]; 57 return self; 58} 59 60-(id)initWithWidth:(float)w image:(NSString*)path length:(float)l color:(ccColor4B)color fade:(float)fade 61{ 62 self = [super init]; 63 if (self) 64 { 65 66 segments_ = [[NSMutableArray alloc] init]; 67 deletedSegments_ = [[NSMutableArray alloc] init]; 68 69 /* 1 initial segment */ 70 CCRibbonSegment* seg = [[CCRibbonSegment alloc] init]; 71 [segments_ addObject:seg]; 72 [seg release]; 73 74 textureLength_ = l; 75 76 color_ = color; 77 fadeTime_ = fade; 78 lastLocation_ = CGPointZero; 79 lastWidth_ = w/2; 80 texVPos_ = 0.0f; 81 82 curTime_ = 0; 83 pastFirstPoint_ = NO; 84 85 /* XXX: 86 Ribbon, by default uses this blend function, which might not be correct 87 if you are using premultiplied alpha images, 88 but 99% you might want to use this blending function regarding of the texture 89 */ 90 blendFunc_.src = GL_SRC_ALPHA; 91 blendFunc_.dst = GL_ONE_MINUS_SRC_ALPHA; 92 93 self.texture = [[CCTextureCache sharedTextureCache] addImage:path]; 94 95 /* default texture parameter */ 96 ccTexParams params = { GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT }; 97 [texture_ setTexParameters:¶ms]; 98 } 99 return self; 100} 101 102-(void)dealloc 103{ 104 [segments_ release]; 105 [deletedSegments_ release]; 106 [texture_ release]; 107 [super dealloc]; 108} 109 110// rotates a point around 0, 0 111-(CGPoint)rotatePoint:(CGPoint)vec rotation:(float)a 112{ 113 float xtemp = (vec.x * cosf(a)) - (vec.y * sinf(a)); 114 vec.y = (vec.x * sinf(a)) + (vec.y * cosf(a)); 115 vec.x = xtemp; 116 return vec; 117} 118 119-(void)update:(ccTime)delta 120{ 121 curTime_+= delta; 122 delta_ = delta; 123} 124 125-(float)sideOfLine:(CGPoint)p l1:(CGPoint)l1 l2:(CGPoint)l2 126{ 127 CGPoint vp = ccpPerp(ccpSub(l1, l2)); 128 CGPoint vx = ccpSub(p, l1); 129 return ccpDot(vx, vp); 130} 131 132// adds a new segment to the ribbon 133-(void)addPointAt:(CGPoint)location width:(float)w 134{ 135 w=w*0.5f; 136 // if this is the first point added, cache it and return 137 if (!pastFirstPoint_) 138 { 139 lastWidth_ = w; 140 lastLocation_ = location; 141 pastFirstPoint_ = YES; 142 return; 143 } 144 145 CGPoint sub = ccpSub(lastLocation_, location); 146 float r = ccpToAngle(sub) + (float)M_PI_2; 147 CGPoint p1 = ccpAdd([self rotatePoint:ccp(-w, 0) rotation:r], location); 148 CGPoint p2 = ccpAdd([self rotatePoint:ccp(w, 0) rotation:r], location); 149 float len = sqrtf(powf(lastLocation_.x - location.x, 2) + powf(lastLocation_.y - location.y, 2)); 150 float tend = texVPos_ + len/textureLength_; 151 CCRibbonSegment* seg; 152 // grab last segment 153 seg = [segments_ lastObject]; 154 // lets kill old segments 155 for (CCRibbonSegment* seg2 in segments_) 156 { 157 if (seg2 != seg && seg2->finished) 158 { 159 [deletedSegments_ addObject:seg2]; 160 } 161 } 162 [segments_ removeObjectsInArray:deletedSegments_]; 163 // is the segment full? 164 if (seg->end >= 50) 165 [segments_ removeObjectsInArray:deletedSegments_]; 166 // grab last segment and append to it if it's not full 167 seg = [segments_ lastObject]; 168 // is the segment full? 169 if (seg->end >= 50) 170 { 171 CCRibbonSegment* newSeg; 172 // grab it from the cache if we can 173 if ([deletedSegments_ count] > 0) 174 { 175 newSeg = [deletedSegments_ objectAtIndex:0]; 176 [newSeg retain]; // will be released later 177 [deletedSegments_ removeObject:newSeg]; 178 [newSeg reset]; 179 } 180 else 181 { 182 newSeg = [[CCRibbonSegment alloc] init]; // will be released later 183 } 184 185 newSeg->creationTime[0] = seg->creationTime[seg->end - 1]; 186 int v = (seg->end-1)*6; 187 int c = (seg->end-1)*4; 188 newSeg->verts[0] = seg->verts[v]; 189 newSeg->verts[1] = seg->verts[v+1]; 190 newSeg->verts[2] = seg->verts[v+2]; 191 newSeg->verts[3] = seg->verts[v+3]; 192 newSeg->verts[4] = seg->verts[v+4]; 193 newSeg->verts[5] = seg->verts[v+5]; 194 195 newSeg->coords[0] = seg->coords[c]; 196 newSeg->coords[1] = seg->coords[c+1]; 197 newSeg->coords[2] = seg->coords[c+2]; 198 newSeg->coords[3] = seg->coords[c+3]; 199 newSeg->end++; 200 seg = newSeg; 201 [segments_ addObject:seg]; 202 [newSeg release]; // it was retained before 203 204 } 205 if (seg->end == 0) 206 { 207 // first edge has to get rotation from the first real polygon 208 CGPoint lp1 = ccpAdd([self rotatePoint:ccp(-lastWidth_, 0) rotation:r], lastLocation_); 209 CGPoint lp2 = ccpAdd([self rotatePoint:ccp(+lastWidth_, 0) rotation:r], lastLocation_); 210 seg->creationTime[0] = curTime_ - delta_; 211 seg->verts[0] = lp1.x; 212 seg->verts[1] = lp1.y; 213 seg->verts[2] = 0.0f; 214 seg->verts[3] = lp2.x; 215 seg->verts[4] = lp2.y; 216 seg->verts[5] = 0.0f; 217 seg->coords[0] = 0.0f; 218 seg->coords[1] = texVPos_; 219 seg->coords[2] = 1.0f; 220 seg->coords[3] = texVPos_; 221 seg->end++; 222 } 223 224 int v = seg->end*6; 225 int c = seg->end*4; 226 // add new vertex 227 seg->creationTime[seg->end] = curTime_; 228 seg->verts[v] = p1.x; 229 seg->verts[v+1] = p1.y; 230 seg->verts[v+2] = 0.0f; 231 seg->verts[v+3] = p2.x; 232 seg->verts[v+4] = p2.y; 233 seg->verts[v+5] = 0.0f; 234 235 236 seg->coords[c] = 0.0f; 237 seg->coords[c+1] = tend; 238 seg->coords[c+2] = 1.0f; 239 seg->coords[c+3] = tend; 240 241 texVPos_ = tend; 242 lastLocation_ = location; 243 lastPoint1_ = p1; 244 lastPoint2_ = p2; 245 lastWidth_ = w; 246 seg->end++; 247} 248 249-(void) draw 250{ 251 if ([segments_ count] > 0) 252 { 253 // Default GL states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY 254 // Needed states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_TEXTURE_COORD_ARRAY 255 // Unneeded states: GL_COLOR_ARRAY 256 glDisableClientState(GL_COLOR_ARRAY); 257 258 glBindTexture(GL_TEXTURE_2D, [texture_ name]); 259 260 BOOL newBlend = NO; 261 if( blendFunc_.src != CC_BLEND_SRC || blendFunc_.dst != CC_BLEND_DST ) { 262 newBlend = YES; 263 glBlendFunc( blendFunc_.src, blendFunc_.dst ); 264 } 265 266 for (CCRibbonSegment* seg in segments_) 267 [seg draw:curTime_ fadeTime:fadeTime_ color:color_]; 268 269 if( newBlend ) 270 glBlendFunc(CC_BLEND_SRC, CC_BLEND_DST); 271 272 // restore default GL state 273 glEnableClientState( GL_COLOR_ARRAY ); 274 } 275} 276 277#pragma mark Ribbon - CocosNodeTexture protocol 278-(void) setTexture:(CCTexture2D*) texture 279{ 280 [texture_ release]; 281 texture_ = [texture retain]; 282 [self setContentSize: texture.contentSize]; 283 /* XXX Don't update blending function in Ribbons */ 284} 285 286-(CCTexture2D*) texture 287{ 288 return texture_; 289} 290 291@end 292 293 294#pragma mark - 295#pragma mark RibbonSegment 296 297@implementation CCRibbonSegment 298 299-(id)init 300{ 301 self = [super init]; 302 if (self) 303 { 304 [self reset]; 305 } 306 return self; 307} 308 309- (NSString*) description 310{ 311 return [NSString stringWithFormat:@"<%@ = %08X | end = %i, begin = %i>", [self class], self, end, begin]; 312} 313 314- (void) dealloc 315{ 316 CCLOGINFO(@"cocos2d: deallocing %@", self); 317 [super dealloc]; 318} 319 320-(void)reset 321{ 322 end = 0; 323 begin = 0; 324 finished = NO; 325} 326 327-(void)draw:(float)curTime fadeTime:(float)fadeTime color:(ccColor4B)color 328{ 329 GLubyte r = color.r; 330 GLubyte g = color.g; 331 GLubyte b = color.b; 332 GLubyte a = color.a; 333 334 if (begin < 50) 335 { 336 // the motion streak class will call update and cause time to change, thus, if curTime_ != 0 337 // we have to generate alpha for the ribbon each frame. 338 if (curTime == 0) 339 { 340 // no alpha over time, so just set the color 341 glColor4ub(r,g,b,a); 342 } 343 else 344 { 345 // generate alpha/color for each point 346 glEnableClientState(GL_COLOR_ARRAY); 347 uint i = begin; 348 for (; i < end; ++i) 349 { 350 int idx = i*8; 351 colors[idx] = r; 352 colors[idx+1] = g; 353 colors[idx+2] = b; 354 colors[idx+4] = r; 355 colors[idx+5] = g; 356 colors[idx+6] = b; 357 float alive = ((curTime - creationTime[i]) / fadeTime); 358 if (alive > 1) 359 { 360 begin++; 361 colors[idx+3] = 0; 362 colors[idx+7] = 0; 363 } 364 else 365 { 366 colors[idx+3] = (GLubyte)(255.f - (alive * 255.f)); 367 colors[idx+7] = colors[idx+3]; 368 } 369 } 370 glColorPointer(4, GL_UNSIGNED_BYTE, 0, &colors[begin*8]); 371 } 372 glVertexPointer(3, GL_FLOAT, 0, &verts[begin*6]); 373 glTexCoordPointer(2, GL_FLOAT, 0, &coords[begin*4]); 374 glDrawArrays(GL_TRIANGLE_STRIP, 0, (end - begin) * 2); 375 } 376 else 377 finished = YES; 378} 379@end 380