diff options
Diffstat (limited to 'libs/cocos2d/CCSpriteBatchNode.m')
-rwxr-xr-x | libs/cocos2d/CCSpriteBatchNode.m | 503 |
1 files changed, 503 insertions, 0 deletions
diff --git a/libs/cocos2d/CCSpriteBatchNode.m b/libs/cocos2d/CCSpriteBatchNode.m new file mode 100755 index 0000000..7c8b05b --- /dev/null +++ b/libs/cocos2d/CCSpriteBatchNode.m | |||
@@ -0,0 +1,503 @@ | |||
1 | /* | ||
2 | * cocos2d for iPhone: http://www.cocos2d-iphone.org | ||
3 | * | ||
4 | * Copyright (C) 2009 Matt Oswald | ||
5 | * | ||
6 | * Copyright (c) 2009-2010 Ricardo Quesada | ||
7 | * Copyright (c) 2011 Zynga Inc. | ||
8 | * | ||
9 | * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
10 | * of this software and associated documentation files (the "Software"), to deal | ||
11 | * in the Software without restriction, including without limitation the rights | ||
12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
13 | * copies of the Software, and to permit persons to whom the Software is | ||
14 | * furnished to do so, subject to the following conditions: | ||
15 | * | ||
16 | * The above copyright notice and this permission notice shall be included in | ||
17 | * all copies or substantial portions of the Software. | ||
18 | * | ||
19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
25 | * THE SOFTWARE. | ||
26 | * | ||
27 | */ | ||
28 | |||
29 | |||
30 | #import "ccConfig.h" | ||
31 | #import "CCSprite.h" | ||
32 | #import "CCSpriteBatchNode.h" | ||
33 | #import "CCGrid.h" | ||
34 | #import "CCDrawingPrimitives.h" | ||
35 | #import "CCTextureCache.h" | ||
36 | #import "Support/CGPointExtension.h" | ||
37 | |||
38 | const NSUInteger defaultCapacity = 29; | ||
39 | |||
40 | #pragma mark - | ||
41 | #pragma mark CCSpriteBatchNode | ||
42 | |||
43 | static SEL selUpdate = NULL; | ||
44 | |||
45 | @interface CCSpriteBatchNode (private) | ||
46 | -(void) updateBlendFunc; | ||
47 | @end | ||
48 | |||
49 | @implementation CCSpriteBatchNode | ||
50 | |||
51 | @synthesize textureAtlas = textureAtlas_; | ||
52 | @synthesize blendFunc = blendFunc_; | ||
53 | @synthesize descendants = descendants_; | ||
54 | |||
55 | |||
56 | +(void) initialize | ||
57 | { | ||
58 | if ( self == [CCSpriteBatchNode class] ) { | ||
59 | selUpdate = @selector(updateTransform); | ||
60 | } | ||
61 | } | ||
62 | /* | ||
63 | * creation with CCTexture2D | ||
64 | */ | ||
65 | +(id)batchNodeWithTexture:(CCTexture2D *)tex | ||
66 | { | ||
67 | return [[[self alloc] initWithTexture:tex capacity:defaultCapacity] autorelease]; | ||
68 | } | ||
69 | +(id)spriteSheetWithTexture:(CCTexture2D *)tex // XXX DEPRECATED | ||
70 | { | ||
71 | return [self batchNodeWithTexture:tex]; | ||
72 | } | ||
73 | |||
74 | +(id)batchNodeWithTexture:(CCTexture2D *)tex capacity:(NSUInteger)capacity | ||
75 | { | ||
76 | return [[[self alloc] initWithTexture:tex capacity:capacity] autorelease]; | ||
77 | } | ||
78 | +(id)spriteSheetWithTexture:(CCTexture2D *)tex capacity:(NSUInteger)capacity // XXX DEPRECATED | ||
79 | { | ||
80 | return [self batchNodeWithTexture:tex capacity:capacity]; | ||
81 | } | ||
82 | |||
83 | /* | ||
84 | * creation with File Image | ||
85 | */ | ||
86 | +(id)batchNodeWithFile:(NSString*)fileImage capacity:(NSUInteger)capacity | ||
87 | { | ||
88 | return [[[self alloc] initWithFile:fileImage capacity:capacity] autorelease]; | ||
89 | } | ||
90 | +(id)spriteSheetWithFile:(NSString*)fileImage capacity:(NSUInteger)capacity // XXX DEPRECATED | ||
91 | { | ||
92 | return [self batchNodeWithFile:fileImage capacity:capacity]; | ||
93 | } | ||
94 | |||
95 | +(id)batchNodeWithFile:(NSString*) imageFile | ||
96 | { | ||
97 | return [[[self alloc] initWithFile:imageFile capacity:defaultCapacity] autorelease]; | ||
98 | } | ||
99 | +(id)spriteSheetWithFile:(NSString*) imageFile // XXX DEPRECATED | ||
100 | { | ||
101 | return [self batchNodeWithFile:imageFile]; | ||
102 | } | ||
103 | |||
104 | |||
105 | /* | ||
106 | * init with CCTexture2D | ||
107 | */ | ||
108 | -(id)initWithTexture:(CCTexture2D *)tex capacity:(NSUInteger)capacity | ||
109 | { | ||
110 | if( (self=[super init])) { | ||
111 | |||
112 | blendFunc_.src = CC_BLEND_SRC; | ||
113 | blendFunc_.dst = CC_BLEND_DST; | ||
114 | textureAtlas_ = [[CCTextureAtlas alloc] initWithTexture:tex capacity:capacity]; | ||
115 | |||
116 | [self updateBlendFunc]; | ||
117 | |||
118 | // no lazy alloc in this node | ||
119 | children_ = [[CCArray alloc] initWithCapacity:capacity]; | ||
120 | descendants_ = [[CCArray alloc] initWithCapacity:capacity]; | ||
121 | } | ||
122 | |||
123 | return self; | ||
124 | } | ||
125 | |||
126 | /* | ||
127 | * init with FileImage | ||
128 | */ | ||
129 | -(id)initWithFile:(NSString *)fileImage capacity:(NSUInteger)capacity | ||
130 | { | ||
131 | CCTexture2D *tex = [[CCTextureCache sharedTextureCache] addImage:fileImage]; | ||
132 | return [self initWithTexture:tex capacity:capacity]; | ||
133 | } | ||
134 | |||
135 | - (NSString*) description | ||
136 | { | ||
137 | return [NSString stringWithFormat:@"<%@ = %08X | Tag = %i>", [self class], self, tag_ ]; | ||
138 | } | ||
139 | |||
140 | -(void)dealloc | ||
141 | { | ||
142 | [textureAtlas_ release]; | ||
143 | [descendants_ release]; | ||
144 | |||
145 | [super dealloc]; | ||
146 | } | ||
147 | |||
148 | #pragma mark CCSpriteBatchNode - composition | ||
149 | |||
150 | // override visit. | ||
151 | // Don't call visit on it's children | ||
152 | -(void) visit | ||
153 | { | ||
154 | |||
155 | // CAREFUL: | ||
156 | // This visit is almost identical to CocosNode#visit | ||
157 | // with the exception that it doesn't call visit on it's children | ||
158 | // | ||
159 | // The alternative is to have a void CCSprite#visit, but | ||
160 | // although this is less mantainable, is faster | ||
161 | // | ||
162 | if (!visible_) | ||
163 | return; | ||
164 | |||
165 | glPushMatrix(); | ||
166 | |||
167 | if ( grid_ && grid_.active) { | ||
168 | [grid_ beforeDraw]; | ||
169 | [self transformAncestors]; | ||
170 | } | ||
171 | |||
172 | [self transform]; | ||
173 | |||
174 | [self draw]; | ||
175 | |||
176 | if ( grid_ && grid_.active) | ||
177 | [grid_ afterDraw:self]; | ||
178 | |||
179 | glPopMatrix(); | ||
180 | } | ||
181 | |||
182 | // XXX deprecated | ||
183 | -(CCSprite*) createSpriteWithRect:(CGRect)rect | ||
184 | { | ||
185 | CCSprite *sprite = [CCSprite spriteWithTexture:textureAtlas_.texture rect:rect]; | ||
186 | [sprite useBatchNode:self]; | ||
187 | |||
188 | return sprite; | ||
189 | } | ||
190 | |||
191 | // XXX deprecated | ||
192 | -(void) initSprite:(CCSprite*)sprite rect:(CGRect)rect | ||
193 | { | ||
194 | [sprite initWithTexture:textureAtlas_.texture rect:rect]; | ||
195 | [sprite useBatchNode:self]; | ||
196 | } | ||
197 | |||
198 | // override addChild: | ||
199 | -(void) addChild:(CCSprite*)child z:(NSInteger)z tag:(NSInteger) aTag | ||
200 | { | ||
201 | NSAssert( child != nil, @"Argument must be non-nil"); | ||
202 | NSAssert( [child isKindOfClass:[CCSprite class]], @"CCSpriteBatchNode only supports CCSprites as children"); | ||
203 | NSAssert( child.texture.name == textureAtlas_.texture.name, @"CCSprite is not using the same texture id"); | ||
204 | |||
205 | [super addChild:child z:z tag:aTag]; | ||
206 | |||
207 | NSUInteger index = [self atlasIndexForChild:child atZ:z]; | ||
208 | [self insertChild:child inAtlasAtIndex:index]; | ||
209 | } | ||
210 | |||
211 | // override reorderChild | ||
212 | -(void) reorderChild:(CCSprite*)child z:(NSInteger)z | ||
213 | { | ||
214 | NSAssert( child != nil, @"Child must be non-nil"); | ||
215 | NSAssert( [children_ containsObject:child], @"Child doesn't belong to Sprite" ); | ||
216 | |||
217 | if( z == child.zOrder ) | ||
218 | return; | ||
219 | |||
220 | // XXX: Instead of removing/adding, it is more efficient to reorder manually | ||
221 | [child retain]; | ||
222 | [self removeChild:child cleanup:NO]; | ||
223 | [self addChild:child z:z]; | ||
224 | [child release]; | ||
225 | } | ||
226 | |||
227 | // override removeChild: | ||
228 | -(void)removeChild: (CCSprite *)sprite cleanup:(BOOL)doCleanup | ||
229 | { | ||
230 | // explicit nil handling | ||
231 | if (sprite == nil) | ||
232 | return; | ||
233 | |||
234 | NSAssert([children_ containsObject:sprite], @"CCSpriteBatchNode doesn't contain the sprite. Can't remove it"); | ||
235 | |||
236 | // cleanup before removing | ||
237 | [self removeSpriteFromAtlas:sprite]; | ||
238 | |||
239 | [super removeChild:sprite cleanup:doCleanup]; | ||
240 | } | ||
241 | |||
242 | -(void)removeChildAtIndex:(NSUInteger)index cleanup:(BOOL)doCleanup | ||
243 | { | ||
244 | [self removeChild:(CCSprite *)[children_ objectAtIndex:index] cleanup:doCleanup]; | ||
245 | } | ||
246 | |||
247 | -(void)removeAllChildrenWithCleanup:(BOOL)doCleanup | ||
248 | { | ||
249 | // Invalidate atlas index. issue #569 | ||
250 | [children_ makeObjectsPerformSelector:@selector(useSelfRender)]; | ||
251 | |||
252 | [super removeAllChildrenWithCleanup:doCleanup]; | ||
253 | |||
254 | [descendants_ removeAllObjects]; | ||
255 | [textureAtlas_ removeAllQuads]; | ||
256 | } | ||
257 | |||
258 | #pragma mark CCSpriteBatchNode - draw | ||
259 | -(void) draw | ||
260 | { | ||
261 | [super draw]; | ||
262 | |||
263 | // Optimization: Fast Dispatch | ||
264 | if( textureAtlas_.totalQuads == 0 ) | ||
265 | return; | ||
266 | |||
267 | CCSprite *child; | ||
268 | ccArray *array = descendants_->data; | ||
269 | |||
270 | NSUInteger i = array->num; | ||
271 | id *arr = array->arr; | ||
272 | |||
273 | if( i > 0 ) { | ||
274 | |||
275 | while (i-- > 0) { | ||
276 | child = *arr++; | ||
277 | |||
278 | // fast dispatch | ||
279 | child->updateMethod(child, selUpdate); | ||
280 | |||
281 | #if CC_SPRITEBATCHNODE_DEBUG_DRAW | ||
282 | //Issue #528 | ||
283 | CGRect rect = [child boundingBox]; | ||
284 | CGPoint vertices[4]={ | ||
285 | ccp(rect.origin.x,rect.origin.y), | ||
286 | ccp(rect.origin.x+rect.size.width,rect.origin.y), | ||
287 | ccp(rect.origin.x+rect.size.width,rect.origin.y+rect.size.height), | ||
288 | ccp(rect.origin.x,rect.origin.y+rect.size.height), | ||
289 | }; | ||
290 | ccDrawPoly(vertices, 4, YES); | ||
291 | #endif // CC_SPRITEBATCHNODE_DEBUG_DRAW | ||
292 | } | ||
293 | } | ||
294 | |||
295 | // Default GL states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY | ||
296 | // Needed states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY | ||
297 | // Unneeded states: - | ||
298 | |||
299 | BOOL newBlend = blendFunc_.src != CC_BLEND_SRC || blendFunc_.dst != CC_BLEND_DST; | ||
300 | if( newBlend ) | ||
301 | glBlendFunc( blendFunc_.src, blendFunc_.dst ); | ||
302 | |||
303 | [textureAtlas_ drawQuads]; | ||
304 | if( newBlend ) | ||
305 | glBlendFunc(CC_BLEND_SRC, CC_BLEND_DST); | ||
306 | } | ||
307 | |||
308 | #pragma mark CCSpriteBatchNode - private | ||
309 | -(void) increaseAtlasCapacity | ||
310 | { | ||
311 | // if we're going beyond the current TextureAtlas's capacity, | ||
312 | // all the previously initialized sprites will need to redo their texture coords | ||
313 | // this is likely computationally expensive | ||
314 | NSUInteger quantity = (textureAtlas_.capacity + 1) * 4 / 3; | ||
315 | |||
316 | CCLOG(@"cocos2d: CCSpriteBatchNode: resizing TextureAtlas capacity from [%lu] to [%lu].", | ||
317 | (long)textureAtlas_.capacity, | ||
318 | (long)quantity); | ||
319 | |||
320 | |||
321 | if( ! [textureAtlas_ resizeCapacity:quantity] ) { | ||
322 | // serious problems | ||
323 | CCLOG(@"cocos2d: WARNING: Not enough memory to resize the atlas"); | ||
324 | NSAssert(NO,@"XXX: SpriteSheet#increaseAtlasCapacity SHALL handle this assert"); | ||
325 | } | ||
326 | } | ||
327 | |||
328 | |||
329 | #pragma mark CCSpriteBatchNode - Atlas Index Stuff | ||
330 | |||
331 | -(NSUInteger) rebuildIndexInOrder:(CCSprite*)node atlasIndex:(NSUInteger)index | ||
332 | { | ||
333 | CCSprite *sprite; | ||
334 | CCARRAY_FOREACH(node.children, sprite){ | ||
335 | if( sprite.zOrder < 0 ) | ||
336 | index = [self rebuildIndexInOrder:sprite atlasIndex:index]; | ||
337 | } | ||
338 | |||
339 | // ignore self (batch node) | ||
340 | if( ! [node isEqual:self]) { | ||
341 | node.atlasIndex = index; | ||
342 | index++; | ||
343 | } | ||
344 | |||
345 | CCARRAY_FOREACH(node.children, sprite){ | ||
346 | if( sprite.zOrder >= 0 ) | ||
347 | index = [self rebuildIndexInOrder:sprite atlasIndex:index]; | ||
348 | } | ||
349 | |||
350 | return index; | ||
351 | } | ||
352 | |||
353 | -(NSUInteger) highestAtlasIndexInChild:(CCSprite*)sprite | ||
354 | { | ||
355 | CCArray *array = [sprite children]; | ||
356 | NSUInteger count = [array count]; | ||
357 | if( count == 0 ) | ||
358 | return sprite.atlasIndex; | ||
359 | else | ||
360 | return [self highestAtlasIndexInChild:[array lastObject]]; | ||
361 | } | ||
362 | |||
363 | -(NSUInteger) lowestAtlasIndexInChild:(CCSprite*)sprite | ||
364 | { | ||
365 | CCArray *array = [sprite children]; | ||
366 | NSUInteger count = [array count]; | ||
367 | if( count == 0 ) | ||
368 | return sprite.atlasIndex; | ||
369 | else | ||
370 | return [self lowestAtlasIndexInChild:[array objectAtIndex:0] ]; | ||
371 | } | ||
372 | |||
373 | |||
374 | -(NSUInteger)atlasIndexForChild:(CCSprite*)sprite atZ:(NSInteger)z | ||
375 | { | ||
376 | CCArray *brothers = [[sprite parent] children]; | ||
377 | NSUInteger childIndex = [brothers indexOfObject:sprite]; | ||
378 | |||
379 | // ignore parent Z if parent is batchnode | ||
380 | BOOL ignoreParent = ( sprite.parent == self ); | ||
381 | CCSprite *previous = nil; | ||
382 | if( childIndex > 0 ) | ||
383 | previous = [brothers objectAtIndex:childIndex-1]; | ||
384 | |||
385 | // first child of the sprite sheet | ||
386 | if( ignoreParent ) { | ||
387 | if( childIndex == 0 ) | ||
388 | return 0; | ||
389 | // else | ||
390 | return [self highestAtlasIndexInChild: previous] + 1; | ||
391 | } | ||
392 | |||
393 | // parent is a CCSprite, so, it must be taken into account | ||
394 | |||
395 | // first child of an CCSprite ? | ||
396 | if( childIndex == 0 ) | ||
397 | { | ||
398 | CCSprite *p = (CCSprite*) sprite.parent; | ||
399 | |||
400 | // less than parent and brothers | ||
401 | if( z < 0 ) | ||
402 | return p.atlasIndex; | ||
403 | else | ||
404 | return p.atlasIndex+1; | ||
405 | |||
406 | } else { | ||
407 | // previous & sprite belong to the same branch | ||
408 | if( ( previous.zOrder < 0 && z < 0 )|| (previous.zOrder >= 0 && z >= 0) ) | ||
409 | return [self highestAtlasIndexInChild:previous] + 1; | ||
410 | |||
411 | // else (previous < 0 and sprite >= 0 ) | ||
412 | CCSprite *p = (CCSprite*) sprite.parent; | ||
413 | return p.atlasIndex + 1; | ||
414 | } | ||
415 | |||
416 | NSAssert( NO, @"Should not happen. Error calculating Z on Batch Node"); | ||
417 | return 0; | ||
418 | } | ||
419 | |||
420 | #pragma mark CCSpriteBatchNode - add / remove / reorder helper methods | ||
421 | // add child helper | ||
422 | -(void) insertChild:(CCSprite*)sprite inAtlasAtIndex:(NSUInteger)index | ||
423 | { | ||
424 | [sprite useBatchNode:self]; | ||
425 | [sprite setAtlasIndex:index]; | ||
426 | [sprite setDirty: YES]; | ||
427 | |||
428 | if(textureAtlas_.totalQuads == textureAtlas_.capacity) | ||
429 | [self increaseAtlasCapacity]; | ||
430 | |||
431 | ccV3F_C4B_T2F_Quad quad = [sprite quad]; | ||
432 | [textureAtlas_ insertQuad:&quad atIndex:index]; | ||
433 | |||
434 | ccArray *descendantsData = descendants_->data; | ||
435 | |||
436 | ccArrayInsertObjectAtIndex(descendantsData, sprite, index); | ||
437 | |||
438 | // update indices | ||
439 | NSUInteger i = index+1; | ||
440 | CCSprite *child; | ||
441 | for(; i<descendantsData->num; i++){ | ||
442 | child = descendantsData->arr[i]; | ||
443 | child.atlasIndex = child.atlasIndex + 1; | ||
444 | } | ||
445 | |||
446 | // add children recursively | ||
447 | CCARRAY_FOREACH(sprite.children, child){ | ||
448 | NSUInteger idx = [self atlasIndexForChild:child atZ: child.zOrder]; | ||
449 | [self insertChild:child inAtlasAtIndex:idx]; | ||
450 | } | ||
451 | } | ||
452 | |||
453 | // remove child helper | ||
454 | -(void) removeSpriteFromAtlas:(CCSprite*)sprite | ||
455 | { | ||
456 | // remove from TextureAtlas | ||
457 | [textureAtlas_ removeQuadAtIndex:sprite.atlasIndex]; | ||
458 | |||
459 | // Cleanup sprite. It might be reused (issue #569) | ||
460 | [sprite useSelfRender]; | ||
461 | |||
462 | ccArray *descendantsData = descendants_->data; | ||
463 | NSUInteger index = ccArrayGetIndexOfObject(descendantsData, sprite); | ||
464 | if( index != NSNotFound ) { | ||
465 | ccArrayRemoveObjectAtIndex(descendantsData, index); | ||
466 | |||
467 | // update all sprites beyond this one | ||
468 | NSUInteger count = descendantsData->num; | ||
469 | |||
470 | for(; index < count; index++) | ||
471 | { | ||
472 | CCSprite *s = descendantsData->arr[index]; | ||
473 | s.atlasIndex = s.atlasIndex - 1; | ||
474 | } | ||
475 | } | ||
476 | |||
477 | // remove children recursively | ||
478 | CCSprite *child; | ||
479 | CCARRAY_FOREACH(sprite.children, child) | ||
480 | [self removeSpriteFromAtlas:child]; | ||
481 | } | ||
482 | |||
483 | #pragma mark CCSpriteBatchNode - CocosNodeTexture protocol | ||
484 | |||
485 | -(void) updateBlendFunc | ||
486 | { | ||
487 | if( ! [textureAtlas_.texture hasPremultipliedAlpha] ) { | ||
488 | blendFunc_.src = GL_SRC_ALPHA; | ||
489 | blendFunc_.dst = GL_ONE_MINUS_SRC_ALPHA; | ||
490 | } | ||
491 | } | ||
492 | |||
493 | -(void) setTexture:(CCTexture2D*)texture | ||
494 | { | ||
495 | textureAtlas_.texture = texture; | ||
496 | [self updateBlendFunc]; | ||
497 | } | ||
498 | |||
499 | -(CCTexture2D*) texture | ||
500 | { | ||
501 | return textureAtlas_.texture; | ||
502 | } | ||
503 | @end | ||