/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. * A ribbon is a dynamically generated list of polygons drawn as a single or series
  26. * of triangle strips. The primary use of Ribbon is as the drawing class of Motion Streak,
  27. * but it is quite useful on it's own. When manually drawing a ribbon, you can call addPointAt
  28. * and pass in the parameters for the next location in the ribbon. The system will automatically
  29. * generate new polygons, texture them accourding to your texture width, etc, etc.
  30. *
  31. * Ribbon data is stored in a RibbonSegment class. This class statically allocates enough verticies and
  32. * texture coordinates for 50 locations (100 verts or 48 triangles). The ribbon class will allocate
  33. * new segments when they are needed, and reuse old ones if available. The idea is to avoid constantly
  34. * allocating new memory and prefer a more static method. However, since there is no way to determine
  35. * the maximum size of some ribbons (motion streaks), a truely static allocation is not possible.
  36. *
  37. */
  38. #import "CCRibbon.h"
  39. #import "CCTextureCache.h"
  40. #import "Support/CGPointExtension.h"
  41. #import "ccMacros.h"
  42. //
  43. // Ribbon
  44. //
  45. @implementation CCRibbon
  46. @synthesize blendFunc=blendFunc_;
  47. @synthesize color=color_;
  48. @synthesize textureLength = textureLength_;
  49. +(id)ribbonWithWidth:(float)w image:(NSString*)path length:(float)l color:(ccColor4B)color fade:(float)fade
  50. {
  51. self = [[[self alloc] initWithWidth:w image:path length:l color:color fade:fade] autorelease];
  52. return self;
  53. }
  54. -(id)initWithWidth:(float)w image:(NSString*)path length:(float)l color:(ccColor4B)color fade:(float)fade
  55. {
  56. self = [super init];
  57. if (self)
  58. {
  59. segments_ = [[NSMutableArray alloc] init];
  60. deletedSegments_ = [[NSMutableArray alloc] init];
  61. /* 1 initial segment */
  62. CCRibbonSegment* seg = [[CCRibbonSegment alloc] init];
  63. [segments_ addObject:seg];
  64. [seg release];
  65. textureLength_ = l;
  66. color_ = color;
  67. fadeTime_ = fade;
  68. lastLocation_ = CGPointZero;
  69. lastWidth_ = w/2;
  70. texVPos_ = 0.0f;
  71. curTime_ = 0;
  72. pastFirstPoint_ = NO;
  73. /* XXX:
  74. Ribbon, by default uses this blend function, which might not be correct
  75. if you are using premultiplied alpha images,
  76. but 99% you might want to use this blending function regarding of the texture
  77. */
  78. blendFunc_.src = GL_SRC_ALPHA;
  79. blendFunc_.dst = GL_ONE_MINUS_SRC_ALPHA;
  80. self.texture = [[CCTextureCache sharedTextureCache] addImage:path];
  81. /* default texture parameter */
  82. ccTexParams params = { GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT };
  83. [texture_ setTexParameters:&params];
  84. }
  85. return self;
  86. }
  87. -(void)dealloc
  88. {
  89. [segments_ release];
  90. [deletedSegments_ release];
  91. [texture_ release];
  92. [super dealloc];
  93. }
  94. // rotates a point around 0, 0
  95. -(CGPoint)rotatePoint:(CGPoint)vec rotation:(float)a
  96. {
  97. float xtemp = (vec.x * cosf(a)) - (vec.y * sinf(a));
  98. vec.y = (vec.x * sinf(a)) + (vec.y * cosf(a));
  99. vec.x = xtemp;
  100. return vec;
  101. }
  102. -(void)update:(ccTime)delta
  103. {
  104. curTime_+= delta;
  105. delta_ = delta;
  106. }
  107. -(float)sideOfLine:(CGPoint)p l1:(CGPoint)l1 l2:(CGPoint)l2
  108. {
  109. CGPoint vp = ccpPerp(ccpSub(l1, l2));
  110. CGPoint vx = ccpSub(p, l1);
  111. return ccpDot(vx, vp);
  112. }
  113. // adds a new segment to the ribbon
  114. -(void)addPointAt:(CGPoint)location width:(float)w
  115. {
  116. w=w*0.5f;
  117. // if this is the first point added, cache it and return
  118. if (!pastFirstPoint_)
  119. {
  120. lastWidth_ = w;
  121. lastLocation_ = location;
  122. pastFirstPoint_ = YES;
  123. return;
  124. }
  125. CGPoint sub = ccpSub(lastLocation_, location);
  126. float r = ccpToAngle(sub) + (float)M_PI_2;
  127. CGPoint p1 = ccpAdd([self rotatePoint:ccp(-w, 0) rotation:r], location);
  128. CGPoint p2 = ccpAdd([self rotatePoint:ccp(w, 0) rotation:r], location);
  129. float len = sqrtf(powf(lastLocation_.x - location.x, 2) + powf(lastLocation_.y - location.y, 2));
  130. float tend = texVPos_ + len/textureLength_;
  131. CCRibbonSegment* seg;
  132. // grab last segment
  133. seg = [segments_ lastObject];
  134. // lets kill old segments
  135. for (CCRibbonSegment* seg2 in segments_)
  136. {
  137. if (seg2 != seg && seg2->finished)
  138. {
  139. [deletedSegments_ addObject:seg2];
  140. }
  141. }
  142. [segments_ removeObjectsInArray:deletedSegments_];
  143. // is the segment full?
  144. if (seg->end >= 50)
  145. [segments_ removeObjectsInArray:deletedSegments_];
  146. // grab last segment and append to it if it's not full
  147. seg = [segments_ lastObject];
  148. // is the segment full?
  149. if (seg->end >= 50)
  150. {
  151. CCRibbonSegment* newSeg;
  152. // grab it from the cache if we can
  153. if ([deletedSegments_ count] > 0)
  154. {
  155. newSeg = [deletedSegments_ objectAtIndex:0];
  156. [newSeg retain]; // will be released later
  157. [deletedSegments_ removeObject:newSeg];
  158. [newSeg reset];
  159. }
  160. else
  161. {
  162. newSeg = [[CCRibbonSegment alloc] init]; // will be released later
  163. }
  164. newSeg->creationTime[0] = seg->creationTime[seg->end - 1];
  165. int v = (seg->end-1)*6;
  166. int c = (seg->end-1)*4;
  167. newSeg->verts[0] = seg->verts[v];
  168. newSeg->verts[1] = seg->verts[v+1];
  169. newSeg->verts[2] = seg->verts[v+2];
  170. newSeg->verts[3] = seg->verts[v+3];
  171. newSeg->verts[4] = seg->verts[v+4];
  172. newSeg->verts[5] = seg->verts[v+5];
  173. newSeg->coords[0] = seg->coords[c];
  174. newSeg->coords[1] = seg->coords[c+1];
  175. newSeg->coords[2] = seg->coords[c+2];
  176. newSeg->coords[3] = seg->coords[c+3];
  177. newSeg->end++;
  178. seg = newSeg;
  179. [segments_ addObject:seg];
  180. [newSeg release]; // it was retained before
  181. }
  182. if (seg->end == 0)
  183. {
  184. // first edge has to get rotation from the first real polygon
  185. CGPoint lp1 = ccpAdd([self rotatePoint:ccp(-lastWidth_, 0) rotation:r], lastLocation_);
  186. CGPoint lp2 = ccpAdd([self rotatePoint:ccp(+lastWidth_, 0) rotation:r], lastLocation_);
  187. seg->creationTime[0] = curTime_ - delta_;
  188. seg->verts[0] = lp1.x;
  189. seg->verts[1] = lp1.y;
  190. seg->verts[2] = 0.0f;
  191. seg->verts[3] = lp2.x;
  192. seg->verts[4] = lp2.y;
  193. seg->verts[5] = 0.0f;
  194. seg->coords[0] = 0.0f;
  195. seg->coords[1] = texVPos_;
  196. seg->coords[2] = 1.0f;
  197. seg->coords[3] = texVPos_;
  198. seg->end++;
  199. }
  200. int v = seg->end*6;
  201. int c = seg->end*4;
  202. // add new vertex
  203. seg->creationTime[seg->end] = curTime_;
  204. seg->verts[v] = p1.x;
  205. seg->verts[v+1] = p1.y;
  206. seg->verts[v+2] = 0.0f;
  207. seg->verts[v+3] = p2.x;
  208. seg->verts[v+4] = p2.y;
  209. seg->verts[v+5] = 0.0f;
  210. seg->coords[c] = 0.0f;
  211. seg->coords[c+1] = tend;
  212. seg->coords[c+2] = 1.0f;
  213. seg->coords[c+3] = tend;
  214. texVPos_ = tend;
  215. lastLocation_ = location;
  216. lastPoint1_ = p1;
  217. lastPoint2_ = p2;
  218. lastWidth_ = w;
  219. seg->end++;
  220. }
  221. -(void) draw
  222. {
  223. if ([segments_ count] > 0)
  224. {
  225. // Default GL states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY
  226. // Needed states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_TEXTURE_COORD_ARRAY
  227. // Unneeded states: GL_COLOR_ARRAY
  228. glDisableClientState(GL_COLOR_ARRAY);
  229. glBindTexture(GL_TEXTURE_2D, [texture_ name]);
  230. BOOL newBlend = NO;
  231. if( blendFunc_.src != CC_BLEND_SRC || blendFunc_.dst != CC_BLEND_DST ) {
  232. newBlend = YES;
  233. glBlendFunc( blendFunc_.src, blendFunc_.dst );
  234. }
  235. for (CCRibbonSegment* seg in segments_)
  236. [seg draw:curTime_ fadeTime:fadeTime_ color:color_];
  237. if( newBlend )
  238. glBlendFunc(CC_BLEND_SRC, CC_BLEND_DST);
  239. // restore default GL state
  240. glEnableClientState( GL_COLOR_ARRAY );
  241. }
  242. }
  243. #pragma mark Ribbon - CocosNodeTexture protocol
  244. -(void) setTexture:(CCTexture2D*) texture
  245. {
  246. [texture_ release];
  247. texture_ = [texture retain];
  248. [self setContentSize: texture.contentSize];
  249. /* XXX Don't update blending function in Ribbons */
  250. }
  251. -(CCTexture2D*) texture
  252. {
  253. return texture_;
  254. }
  255. @end
  256. #pragma mark -
  257. #pragma mark RibbonSegment
  258. @implementation CCRibbonSegment
  259. -(id)init
  260. {
  261. self = [super init];
  262. if (self)
  263. {
  264. [self reset];
  265. }
  266. return self;
  267. }
  268. - (NSString*) description
  269. {
  270. return [NSString stringWithFormat:@"<%@ = %08X | end = %i, begin = %i>", [self class], self, end, begin];
  271. }
  272. - (void) dealloc
  273. {
  274. CCLOGINFO(@"cocos2d: deallocing %@", self);
  275. [super dealloc];
  276. }
  277. -(void)reset
  278. {
  279. end = 0;
  280. begin = 0;
  281. finished = NO;
  282. }
  283. -(void)draw:(float)curTime fadeTime:(float)fadeTime color:(ccColor4B)color
  284. {
  285. GLubyte r = color.r;
  286. GLubyte g = color.g;
  287. GLubyte b = color.b;
  288. GLubyte a = color.a;
  289. if (begin < 50)
  290. {
  291. // the motion streak class will call update and cause time to change, thus, if curTime_ != 0
  292. // we have to generate alpha for the ribbon each frame.
  293. if (curTime == 0)
  294. {
  295. // no alpha over time, so just set the color
  296. glColor4ub(r,g,b,a);
  297. }
  298. else
  299. {
  300. // generate alpha/color for each point
  301. glEnableClientState(GL_COLOR_ARRAY);
  302. uint i = begin;
  303. for (; i < end; ++i)
  304. {
  305. int idx = i*8;
  306. colors[idx] = r;
  307. colors[idx+1] = g;
  308. colors[idx+2] = b;
  309. colors[idx+4] = r;
  310. colors[idx+5] = g;
  311. colors[idx+6] = b;
  312. float alive = ((curTime - creationTime[i]) / fadeTime);
  313. if (alive > 1)
  314. {
  315. begin++;
  316. colors[idx+3] = 0;
  317. colors[idx+7] = 0;
  318. }
  319. else
  320. {
  321. colors[idx+3] = (GLubyte)(255.f - (alive * 255.f));
  322. colors[idx+7] = colors[idx+3];
  323. }
  324. }
  325. glColorPointer(4, GL_UNSIGNED_BYTE, 0, &colors[begin*8]);
  326. }
  327. glVertexPointer(3, GL_FLOAT, 0, &verts[begin*6]);
  328. glTexCoordPointer(2, GL_FLOAT, 0, &coords[begin*4]);
  329. glDrawArrays(GL_TRIANGLE_STRIP, 0, (end - begin) * 2);
  330. }
  331. else
  332. finished = YES;
  333. }
  334. @end