diff options
Diffstat (limited to 'libs/cocos2d/CCRibbon.m')
-rwxr-xr-x | libs/cocos2d/CCRibbon.m | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/libs/cocos2d/CCRibbon.m b/libs/cocos2d/CCRibbon.m new file mode 100755 index 0000000..2d9acaa --- /dev/null +++ b/libs/cocos2d/CCRibbon.m | |||
@@ -0,0 +1,383 @@ | |||
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 | location.x *= CC_CONTENT_SCALE_FACTOR(); | ||
136 | location.y *= CC_CONTENT_SCALE_FACTOR(); | ||
137 | |||
138 | w = w*0.5f; | ||
139 | // if this is the first point added, cache it and return | ||
140 | if (!pastFirstPoint_) | ||
141 | { | ||
142 | lastWidth_ = w; | ||
143 | lastLocation_ = location; | ||
144 | pastFirstPoint_ = YES; | ||
145 | return; | ||
146 | } | ||
147 | |||
148 | CGPoint sub = ccpSub(lastLocation_, location); | ||
149 | float r = ccpToAngle(sub) + (float)M_PI_2; | ||
150 | CGPoint p1 = ccpAdd([self rotatePoint:ccp(-w, 0) rotation:r], location); | ||
151 | CGPoint p2 = ccpAdd([self rotatePoint:ccp(w, 0) rotation:r], location); | ||
152 | float len = sqrtf(powf(lastLocation_.x - location.x, 2) + powf(lastLocation_.y - location.y, 2)); | ||
153 | float tend = texVPos_ + len/textureLength_; | ||
154 | CCRibbonSegment* seg; | ||
155 | // grab last segment | ||
156 | seg = [segments_ lastObject]; | ||
157 | // lets kill old segments | ||
158 | for (CCRibbonSegment* seg2 in segments_) | ||
159 | { | ||
160 | if (seg2 != seg && seg2->finished) | ||
161 | { | ||
162 | [deletedSegments_ addObject:seg2]; | ||
163 | } | ||
164 | } | ||
165 | [segments_ removeObjectsInArray:deletedSegments_]; | ||
166 | // is the segment full? | ||
167 | if (seg->end >= 50) | ||
168 | [segments_ removeObjectsInArray:deletedSegments_]; | ||
169 | // grab last segment and append to it if it's not full | ||
170 | seg = [segments_ lastObject]; | ||
171 | // is the segment full? | ||
172 | if (seg->end >= 50) | ||
173 | { | ||
174 | CCRibbonSegment* newSeg; | ||
175 | // grab it from the cache if we can | ||
176 | if ([deletedSegments_ count] > 0) | ||
177 | { | ||
178 | newSeg = [deletedSegments_ objectAtIndex:0]; | ||
179 | [newSeg retain]; // will be released later | ||
180 | [deletedSegments_ removeObject:newSeg]; | ||
181 | [newSeg reset]; | ||
182 | } | ||
183 | else | ||
184 | { | ||
185 | newSeg = [[CCRibbonSegment alloc] init]; // will be released later | ||
186 | } | ||
187 | |||
188 | newSeg->creationTime[0] = seg->creationTime[seg->end - 1]; | ||
189 | int v = (seg->end-1)*6; | ||
190 | int c = (seg->end-1)*4; | ||
191 | newSeg->verts[0] = seg->verts[v]; | ||
192 | newSeg->verts[1] = seg->verts[v+1]; | ||
193 | newSeg->verts[2] = seg->verts[v+2]; | ||
194 | newSeg->verts[3] = seg->verts[v+3]; | ||
195 | newSeg->verts[4] = seg->verts[v+4]; | ||
196 | newSeg->verts[5] = seg->verts[v+5]; | ||
197 | |||
198 | newSeg->coords[0] = seg->coords[c]; | ||
199 | newSeg->coords[1] = seg->coords[c+1]; | ||
200 | newSeg->coords[2] = seg->coords[c+2]; | ||
201 | newSeg->coords[3] = seg->coords[c+3]; | ||
202 | newSeg->end++; | ||
203 | seg = newSeg; | ||
204 | [segments_ addObject:seg]; | ||
205 | [newSeg release]; // it was retained before | ||
206 | |||
207 | } | ||
208 | if (seg->end == 0) | ||
209 | { | ||
210 | // first edge has to get rotation from the first real polygon | ||
211 | CGPoint lp1 = ccpAdd([self rotatePoint:ccp(-lastWidth_, 0) rotation:r], lastLocation_); | ||
212 | CGPoint lp2 = ccpAdd([self rotatePoint:ccp(+lastWidth_, 0) rotation:r], lastLocation_); | ||
213 | seg->creationTime[0] = curTime_ - delta_; | ||
214 | seg->verts[0] = lp1.x; | ||
215 | seg->verts[1] = lp1.y; | ||
216 | seg->verts[2] = 0.0f; | ||
217 | seg->verts[3] = lp2.x; | ||
218 | seg->verts[4] = lp2.y; | ||
219 | seg->verts[5] = 0.0f; | ||
220 | seg->coords[0] = 0.0f; | ||
221 | seg->coords[1] = texVPos_; | ||
222 | seg->coords[2] = 1.0f; | ||
223 | seg->coords[3] = texVPos_; | ||
224 | seg->end++; | ||
225 | } | ||
226 | |||
227 | int v = seg->end*6; | ||
228 | int c = seg->end*4; | ||
229 | // add new vertex | ||
230 | seg->creationTime[seg->end] = curTime_; | ||
231 | seg->verts[v] = p1.x; | ||
232 | seg->verts[v+1] = p1.y; | ||
233 | seg->verts[v+2] = 0.0f; | ||
234 | seg->verts[v+3] = p2.x; | ||
235 | seg->verts[v+4] = p2.y; | ||
236 | seg->verts[v+5] = 0.0f; | ||
237 | |||
238 | |||
239 | seg->coords[c] = 0.0f; | ||
240 | seg->coords[c+1] = tend; | ||
241 | seg->coords[c+2] = 1.0f; | ||
242 | seg->coords[c+3] = tend; | ||
243 | |||
244 | texVPos_ = tend; | ||
245 | lastLocation_ = location; | ||
246 | lastPoint1_ = p1; | ||
247 | lastPoint2_ = p2; | ||
248 | lastWidth_ = w; | ||
249 | seg->end++; | ||
250 | } | ||
251 | |||
252 | -(void) draw | ||
253 | { | ||
254 | [super draw]; | ||
255 | |||
256 | if ([segments_ count] > 0) | ||
257 | { | ||
258 | // Default GL states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY | ||
259 | // Needed states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_TEXTURE_COORD_ARRAY | ||
260 | // Unneeded states: GL_COLOR_ARRAY | ||
261 | glDisableClientState(GL_COLOR_ARRAY); | ||
262 | |||
263 | glBindTexture(GL_TEXTURE_2D, [texture_ name]); | ||
264 | |||
265 | BOOL newBlend = blendFunc_.src != CC_BLEND_SRC || blendFunc_.dst != CC_BLEND_DST; | ||
266 | if( newBlend ) | ||
267 | glBlendFunc( blendFunc_.src, blendFunc_.dst ); | ||
268 | |||
269 | for (CCRibbonSegment* seg in segments_) | ||
270 | [seg draw:curTime_ fadeTime:fadeTime_ color:color_]; | ||
271 | |||
272 | if( newBlend ) | ||
273 | glBlendFunc(CC_BLEND_SRC, CC_BLEND_DST); | ||
274 | |||
275 | // restore default GL state | ||
276 | glEnableClientState( GL_COLOR_ARRAY ); | ||
277 | } | ||
278 | } | ||
279 | |||
280 | #pragma mark Ribbon - CocosNodeTexture protocol | ||
281 | -(void) setTexture:(CCTexture2D*) texture | ||
282 | { | ||
283 | [texture_ release]; | ||
284 | texture_ = [texture retain]; | ||
285 | [self setContentSizeInPixels: texture.contentSizeInPixels]; | ||
286 | /* XXX Don't update blending function in Ribbons */ | ||
287 | } | ||
288 | |||
289 | -(CCTexture2D*) texture | ||
290 | { | ||
291 | return texture_; | ||
292 | } | ||
293 | |||
294 | @end | ||
295 | |||
296 | |||
297 | #pragma mark - | ||
298 | #pragma mark RibbonSegment | ||
299 | |||
300 | @implementation CCRibbonSegment | ||
301 | |||
302 | -(id)init | ||
303 | { | ||
304 | self = [super init]; | ||
305 | if (self) | ||
306 | { | ||
307 | [self reset]; | ||
308 | } | ||
309 | return self; | ||
310 | } | ||
311 | |||
312 | - (NSString*) description | ||
313 | { | ||
314 | return [NSString stringWithFormat:@"<%@ = %08X | end = %i, begin = %i>", [self class], self, end, begin]; | ||
315 | } | ||
316 | |||
317 | - (void) dealloc | ||
318 | { | ||
319 | CCLOGINFO(@"cocos2d: deallocing %@", self); | ||
320 | [super dealloc]; | ||
321 | } | ||
322 | |||
323 | -(void)reset | ||
324 | { | ||
325 | end = 0; | ||
326 | begin = 0; | ||
327 | finished = NO; | ||
328 | } | ||
329 | |||
330 | -(void)draw:(float)curTime fadeTime:(float)fadeTime color:(ccColor4B)color | ||
331 | { | ||
332 | GLubyte r = color.r; | ||
333 | GLubyte g = color.g; | ||
334 | GLubyte b = color.b; | ||
335 | GLubyte a = color.a; | ||
336 | |||
337 | if (begin < 50) | ||
338 | { | ||
339 | // the motion streak class will call update and cause time to change, thus, if curTime_ != 0 | ||
340 | // we have to generate alpha for the ribbon each frame. | ||
341 | if (curTime == 0) | ||
342 | { | ||
343 | // no alpha over time, so just set the color | ||
344 | glColor4ub(r,g,b,a); | ||
345 | } | ||
346 | else | ||
347 | { | ||
348 | // generate alpha/color for each point | ||
349 | glEnableClientState(GL_COLOR_ARRAY); | ||
350 | uint i = begin; | ||
351 | for (; i < end; ++i) | ||
352 | { | ||
353 | int idx = i*8; | ||
354 | colors[idx] = r; | ||
355 | colors[idx+1] = g; | ||
356 | colors[idx+2] = b; | ||
357 | colors[idx+4] = r; | ||
358 | colors[idx+5] = g; | ||
359 | colors[idx+6] = b; | ||
360 | float alive = ((curTime - creationTime[i]) / fadeTime); | ||
361 | if (alive > 1) | ||
362 | { | ||
363 | begin++; | ||
364 | colors[idx+3] = 0; | ||
365 | colors[idx+7] = 0; | ||
366 | } | ||
367 | else | ||
368 | { | ||
369 | colors[idx+3] = (GLubyte)(255.f - (alive * 255.f)); | ||
370 | colors[idx+7] = colors[idx+3]; | ||
371 | } | ||
372 | } | ||
373 | glColorPointer(4, GL_UNSIGNED_BYTE, 0, &colors[begin*8]); | ||
374 | } | ||
375 | glVertexPointer(3, GL_FLOAT, 0, &verts[begin*6]); | ||
376 | glTexCoordPointer(2, GL_FLOAT, 0, &coords[begin*4]); | ||
377 | glDrawArrays(GL_TRIANGLE_STRIP, 0, (end - begin) * 2); | ||
378 | } | ||
379 | else | ||
380 | finished = YES; | ||
381 | } | ||
382 | @end | ||
383 | |||