summary refs log tree commit diff stats
path: root/libs/cocos2d/CCRibbon.m
diff options
context:
space:
mode:
Diffstat (limited to 'libs/cocos2d/CCRibbon.m')
-rwxr-xr-xlibs/cocos2d/CCRibbon.m383
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:&params];
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