From 9cd57b731ab1c666d4a1cb725538fdc137763d12 Mon Sep 17 00:00:00 2001 From: Starla Insigna Date: Sat, 30 Jul 2011 11:19:14 -0400 Subject: Initial commit (version 0.2.1) --- libs/cocos2d/CCSpriteBatchNode.m | 503 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 503 insertions(+) create mode 100755 libs/cocos2d/CCSpriteBatchNode.m (limited to 'libs/cocos2d/CCSpriteBatchNode.m') 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 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (C) 2009 Matt Oswald + * + * Copyright (c) 2009-2010 Ricardo Quesada + * Copyright (c) 2011 Zynga Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + + +#import "ccConfig.h" +#import "CCSprite.h" +#import "CCSpriteBatchNode.h" +#import "CCGrid.h" +#import "CCDrawingPrimitives.h" +#import "CCTextureCache.h" +#import "Support/CGPointExtension.h" + +const NSUInteger defaultCapacity = 29; + +#pragma mark - +#pragma mark CCSpriteBatchNode + +static SEL selUpdate = NULL; + +@interface CCSpriteBatchNode (private) +-(void) updateBlendFunc; +@end + +@implementation CCSpriteBatchNode + +@synthesize textureAtlas = textureAtlas_; +@synthesize blendFunc = blendFunc_; +@synthesize descendants = descendants_; + + ++(void) initialize +{ + if ( self == [CCSpriteBatchNode class] ) { + selUpdate = @selector(updateTransform); + } +} +/* + * creation with CCTexture2D + */ ++(id)batchNodeWithTexture:(CCTexture2D *)tex +{ + return [[[self alloc] initWithTexture:tex capacity:defaultCapacity] autorelease]; +} ++(id)spriteSheetWithTexture:(CCTexture2D *)tex // XXX DEPRECATED +{ + return [self batchNodeWithTexture:tex]; +} + ++(id)batchNodeWithTexture:(CCTexture2D *)tex capacity:(NSUInteger)capacity +{ + return [[[self alloc] initWithTexture:tex capacity:capacity] autorelease]; +} ++(id)spriteSheetWithTexture:(CCTexture2D *)tex capacity:(NSUInteger)capacity // XXX DEPRECATED +{ + return [self batchNodeWithTexture:tex capacity:capacity]; +} + +/* + * creation with File Image + */ ++(id)batchNodeWithFile:(NSString*)fileImage capacity:(NSUInteger)capacity +{ + return [[[self alloc] initWithFile:fileImage capacity:capacity] autorelease]; +} ++(id)spriteSheetWithFile:(NSString*)fileImage capacity:(NSUInteger)capacity // XXX DEPRECATED +{ + return [self batchNodeWithFile:fileImage capacity:capacity]; +} + ++(id)batchNodeWithFile:(NSString*) imageFile +{ + return [[[self alloc] initWithFile:imageFile capacity:defaultCapacity] autorelease]; +} ++(id)spriteSheetWithFile:(NSString*) imageFile // XXX DEPRECATED +{ + return [self batchNodeWithFile:imageFile]; +} + + +/* + * init with CCTexture2D + */ +-(id)initWithTexture:(CCTexture2D *)tex capacity:(NSUInteger)capacity +{ + if( (self=[super init])) { + + blendFunc_.src = CC_BLEND_SRC; + blendFunc_.dst = CC_BLEND_DST; + textureAtlas_ = [[CCTextureAtlas alloc] initWithTexture:tex capacity:capacity]; + + [self updateBlendFunc]; + + // no lazy alloc in this node + children_ = [[CCArray alloc] initWithCapacity:capacity]; + descendants_ = [[CCArray alloc] initWithCapacity:capacity]; + } + + return self; +} + +/* + * init with FileImage + */ +-(id)initWithFile:(NSString *)fileImage capacity:(NSUInteger)capacity +{ + CCTexture2D *tex = [[CCTextureCache sharedTextureCache] addImage:fileImage]; + return [self initWithTexture:tex capacity:capacity]; +} + +- (NSString*) description +{ + return [NSString stringWithFormat:@"<%@ = %08X | Tag = %i>", [self class], self, tag_ ]; +} + +-(void)dealloc +{ + [textureAtlas_ release]; + [descendants_ release]; + + [super dealloc]; +} + +#pragma mark CCSpriteBatchNode - composition + +// override visit. +// Don't call visit on it's children +-(void) visit +{ + + // CAREFUL: + // This visit is almost identical to CocosNode#visit + // with the exception that it doesn't call visit on it's children + // + // The alternative is to have a void CCSprite#visit, but + // although this is less mantainable, is faster + // + if (!visible_) + return; + + glPushMatrix(); + + if ( grid_ && grid_.active) { + [grid_ beforeDraw]; + [self transformAncestors]; + } + + [self transform]; + + [self draw]; + + if ( grid_ && grid_.active) + [grid_ afterDraw:self]; + + glPopMatrix(); +} + +// XXX deprecated +-(CCSprite*) createSpriteWithRect:(CGRect)rect +{ + CCSprite *sprite = [CCSprite spriteWithTexture:textureAtlas_.texture rect:rect]; + [sprite useBatchNode:self]; + + return sprite; +} + +// XXX deprecated +-(void) initSprite:(CCSprite*)sprite rect:(CGRect)rect +{ + [sprite initWithTexture:textureAtlas_.texture rect:rect]; + [sprite useBatchNode:self]; +} + +// override addChild: +-(void) addChild:(CCSprite*)child z:(NSInteger)z tag:(NSInteger) aTag +{ + NSAssert( child != nil, @"Argument must be non-nil"); + NSAssert( [child isKindOfClass:[CCSprite class]], @"CCSpriteBatchNode only supports CCSprites as children"); + NSAssert( child.texture.name == textureAtlas_.texture.name, @"CCSprite is not using the same texture id"); + + [super addChild:child z:z tag:aTag]; + + NSUInteger index = [self atlasIndexForChild:child atZ:z]; + [self insertChild:child inAtlasAtIndex:index]; +} + +// override reorderChild +-(void) reorderChild:(CCSprite*)child z:(NSInteger)z +{ + NSAssert( child != nil, @"Child must be non-nil"); + NSAssert( [children_ containsObject:child], @"Child doesn't belong to Sprite" ); + + if( z == child.zOrder ) + return; + + // XXX: Instead of removing/adding, it is more efficient to reorder manually + [child retain]; + [self removeChild:child cleanup:NO]; + [self addChild:child z:z]; + [child release]; +} + +// override removeChild: +-(void)removeChild: (CCSprite *)sprite cleanup:(BOOL)doCleanup +{ + // explicit nil handling + if (sprite == nil) + return; + + NSAssert([children_ containsObject:sprite], @"CCSpriteBatchNode doesn't contain the sprite. Can't remove it"); + + // cleanup before removing + [self removeSpriteFromAtlas:sprite]; + + [super removeChild:sprite cleanup:doCleanup]; +} + +-(void)removeChildAtIndex:(NSUInteger)index cleanup:(BOOL)doCleanup +{ + [self removeChild:(CCSprite *)[children_ objectAtIndex:index] cleanup:doCleanup]; +} + +-(void)removeAllChildrenWithCleanup:(BOOL)doCleanup +{ + // Invalidate atlas index. issue #569 + [children_ makeObjectsPerformSelector:@selector(useSelfRender)]; + + [super removeAllChildrenWithCleanup:doCleanup]; + + [descendants_ removeAllObjects]; + [textureAtlas_ removeAllQuads]; +} + +#pragma mark CCSpriteBatchNode - draw +-(void) draw +{ + [super draw]; + + // Optimization: Fast Dispatch + if( textureAtlas_.totalQuads == 0 ) + return; + + CCSprite *child; + ccArray *array = descendants_->data; + + NSUInteger i = array->num; + id *arr = array->arr; + + if( i > 0 ) { + + while (i-- > 0) { + child = *arr++; + + // fast dispatch + child->updateMethod(child, selUpdate); + +#if CC_SPRITEBATCHNODE_DEBUG_DRAW + //Issue #528 + CGRect rect = [child boundingBox]; + CGPoint vertices[4]={ + ccp(rect.origin.x,rect.origin.y), + ccp(rect.origin.x+rect.size.width,rect.origin.y), + ccp(rect.origin.x+rect.size.width,rect.origin.y+rect.size.height), + ccp(rect.origin.x,rect.origin.y+rect.size.height), + }; + ccDrawPoly(vertices, 4, YES); +#endif // CC_SPRITEBATCHNODE_DEBUG_DRAW + } + } + + // Default GL states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY + // Needed states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY + // Unneeded states: - + + BOOL newBlend = blendFunc_.src != CC_BLEND_SRC || blendFunc_.dst != CC_BLEND_DST; + if( newBlend ) + glBlendFunc( blendFunc_.src, blendFunc_.dst ); + + [textureAtlas_ drawQuads]; + if( newBlend ) + glBlendFunc(CC_BLEND_SRC, CC_BLEND_DST); +} + +#pragma mark CCSpriteBatchNode - private +-(void) increaseAtlasCapacity +{ + // if we're going beyond the current TextureAtlas's capacity, + // all the previously initialized sprites will need to redo their texture coords + // this is likely computationally expensive + NSUInteger quantity = (textureAtlas_.capacity + 1) * 4 / 3; + + CCLOG(@"cocos2d: CCSpriteBatchNode: resizing TextureAtlas capacity from [%lu] to [%lu].", + (long)textureAtlas_.capacity, + (long)quantity); + + + if( ! [textureAtlas_ resizeCapacity:quantity] ) { + // serious problems + CCLOG(@"cocos2d: WARNING: Not enough memory to resize the atlas"); + NSAssert(NO,@"XXX: SpriteSheet#increaseAtlasCapacity SHALL handle this assert"); + } +} + + +#pragma mark CCSpriteBatchNode - Atlas Index Stuff + +-(NSUInteger) rebuildIndexInOrder:(CCSprite*)node atlasIndex:(NSUInteger)index +{ + CCSprite *sprite; + CCARRAY_FOREACH(node.children, sprite){ + if( sprite.zOrder < 0 ) + index = [self rebuildIndexInOrder:sprite atlasIndex:index]; + } + + // ignore self (batch node) + if( ! [node isEqual:self]) { + node.atlasIndex = index; + index++; + } + + CCARRAY_FOREACH(node.children, sprite){ + if( sprite.zOrder >= 0 ) + index = [self rebuildIndexInOrder:sprite atlasIndex:index]; + } + + return index; +} + +-(NSUInteger) highestAtlasIndexInChild:(CCSprite*)sprite +{ + CCArray *array = [sprite children]; + NSUInteger count = [array count]; + if( count == 0 ) + return sprite.atlasIndex; + else + return [self highestAtlasIndexInChild:[array lastObject]]; +} + +-(NSUInteger) lowestAtlasIndexInChild:(CCSprite*)sprite +{ + CCArray *array = [sprite children]; + NSUInteger count = [array count]; + if( count == 0 ) + return sprite.atlasIndex; + else + return [self lowestAtlasIndexInChild:[array objectAtIndex:0] ]; +} + + +-(NSUInteger)atlasIndexForChild:(CCSprite*)sprite atZ:(NSInteger)z +{ + CCArray *brothers = [[sprite parent] children]; + NSUInteger childIndex = [brothers indexOfObject:sprite]; + + // ignore parent Z if parent is batchnode + BOOL ignoreParent = ( sprite.parent == self ); + CCSprite *previous = nil; + if( childIndex > 0 ) + previous = [brothers objectAtIndex:childIndex-1]; + + // first child of the sprite sheet + if( ignoreParent ) { + if( childIndex == 0 ) + return 0; + // else + return [self highestAtlasIndexInChild: previous] + 1; + } + + // parent is a CCSprite, so, it must be taken into account + + // first child of an CCSprite ? + if( childIndex == 0 ) + { + CCSprite *p = (CCSprite*) sprite.parent; + + // less than parent and brothers + if( z < 0 ) + return p.atlasIndex; + else + return p.atlasIndex+1; + + } else { + // previous & sprite belong to the same branch + if( ( previous.zOrder < 0 && z < 0 )|| (previous.zOrder >= 0 && z >= 0) ) + return [self highestAtlasIndexInChild:previous] + 1; + + // else (previous < 0 and sprite >= 0 ) + CCSprite *p = (CCSprite*) sprite.parent; + return p.atlasIndex + 1; + } + + NSAssert( NO, @"Should not happen. Error calculating Z on Batch Node"); + return 0; +} + +#pragma mark CCSpriteBatchNode - add / remove / reorder helper methods +// add child helper +-(void) insertChild:(CCSprite*)sprite inAtlasAtIndex:(NSUInteger)index +{ + [sprite useBatchNode:self]; + [sprite setAtlasIndex:index]; + [sprite setDirty: YES]; + + if(textureAtlas_.totalQuads == textureAtlas_.capacity) + [self increaseAtlasCapacity]; + + ccV3F_C4B_T2F_Quad quad = [sprite quad]; + [textureAtlas_ insertQuad:&quad atIndex:index]; + + ccArray *descendantsData = descendants_->data; + + ccArrayInsertObjectAtIndex(descendantsData, sprite, index); + + // update indices + NSUInteger i = index+1; + CCSprite *child; + for(; inum; i++){ + child = descendantsData->arr[i]; + child.atlasIndex = child.atlasIndex + 1; + } + + // add children recursively + CCARRAY_FOREACH(sprite.children, child){ + NSUInteger idx = [self atlasIndexForChild:child atZ: child.zOrder]; + [self insertChild:child inAtlasAtIndex:idx]; + } +} + +// remove child helper +-(void) removeSpriteFromAtlas:(CCSprite*)sprite +{ + // remove from TextureAtlas + [textureAtlas_ removeQuadAtIndex:sprite.atlasIndex]; + + // Cleanup sprite. It might be reused (issue #569) + [sprite useSelfRender]; + + ccArray *descendantsData = descendants_->data; + NSUInteger index = ccArrayGetIndexOfObject(descendantsData, sprite); + if( index != NSNotFound ) { + ccArrayRemoveObjectAtIndex(descendantsData, index); + + // update all sprites beyond this one + NSUInteger count = descendantsData->num; + + for(; index < count; index++) + { + CCSprite *s = descendantsData->arr[index]; + s.atlasIndex = s.atlasIndex - 1; + } + } + + // remove children recursively + CCSprite *child; + CCARRAY_FOREACH(sprite.children, child) + [self removeSpriteFromAtlas:child]; +} + +#pragma mark CCSpriteBatchNode - CocosNodeTexture protocol + +-(void) updateBlendFunc +{ + if( ! [textureAtlas_.texture hasPremultipliedAlpha] ) { + blendFunc_.src = GL_SRC_ALPHA; + blendFunc_.dst = GL_ONE_MINUS_SRC_ALPHA; + } +} + +-(void) setTexture:(CCTexture2D*)texture +{ + textureAtlas_.texture = texture; + [self updateBlendFunc]; +} + +-(CCTexture2D*) texture +{ + return textureAtlas_.texture; +} +@end -- cgit 1.4.1