PageRenderTime 113ms CodeModel.GetById 20ms app.highlight 87ms RepoModel.GetById 1ms app.codeStats 0ms

/libs/cocos2d/CCRibbon.m

http://github.com/kstenerud/ObjectAL-for-iPhone
Objective C | 380 lines | 267 code | 46 blank | 67 comment | 24 complexity | f7d9fe6b906d11cdc59f5f69b77a3f12 MD5 | raw file
  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	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