/* * cocos2d for iPhone: http://www.cocos2d-iphone.org * * Copyright (c) 2008-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. * */ // ideas taken from: // . The ocean spray in your face [Jeff Lander] // http://www.double.co.nz/dust/col0798.pdf // . Building an Advanced Particle System [John van der Burg] // http://www.gamasutra.com/features/20000623/vanderburg_01.htm // . LOVE game engine // http://love2d.org/ // // // Radius mode support, from 71 squared // http://particledesigner.71squared.com/ // // IMPORTANT: Particle Designer is supported by cocos2d, but // 'Radius Mode' in Particle Designer uses a fixed emit rate of 30 hz. Since that can't be guarateed in cocos2d, // cocos2d uses a another approach, but the results are almost identical. // // opengl #import "Platforms/CCGL.h" // cocos2d #import "ccConfig.h" #if CC_ENABLE_PROFILERS #import "Support/CCProfiling.h" #endif #import "CCParticleSystem.h" #import "CCTextureCache.h" #import "ccMacros.h" // support #import "Support/OpenGL_Internal.h" #import "Support/CGPointExtension.h" #import "Support/base64.h" #import "Support/ZipUtils.h" #import "Support/CCFileUtils.h" @implementation CCParticleSystem @synthesize active, duration; @synthesize sourcePosition, posVar; @synthesize particleCount; @synthesize life, lifeVar; @synthesize angle, angleVar; @synthesize startColor, startColorVar, endColor, endColorVar; @synthesize startSpin, startSpinVar, endSpin, endSpinVar; @synthesize emissionRate; @synthesize totalParticles; @synthesize startSize, startSizeVar; @synthesize endSize, endSizeVar; @synthesize blendFunc = blendFunc_; @synthesize positionType = positionType_; @synthesize autoRemoveOnFinish = autoRemoveOnFinish_; @synthesize emitterMode = emitterMode_; +(id) particleWithFile:(NSString*) plistFile { return [[[self alloc] initWithFile:plistFile] autorelease]; } -(id) init { NSAssert(NO, @"CCParticleSystem: Init not supported."); [self release]; return nil; } -(id) initWithFile:(NSString *)plistFile { NSString *path = [CCFileUtils fullPathFromRelativePath:plistFile]; NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path]; NSAssert( dict != nil, @"Particles: file not found"); return [self initWithDictionary:dict]; } -(id) initWithDictionary:(NSDictionary *)dictionary { NSUInteger maxParticles = [[dictionary valueForKey:@"maxParticles"] intValue]; // self, not super if ((self=[self initWithTotalParticles:maxParticles] ) ) { // angle angle = [[dictionary valueForKey:@"angle"] floatValue]; angleVar = [[dictionary valueForKey:@"angleVariance"] floatValue]; // duration duration = [[dictionary valueForKey:@"duration"] floatValue]; // blend function blendFunc_.src = [[dictionary valueForKey:@"blendFuncSource"] intValue]; blendFunc_.dst = [[dictionary valueForKey:@"blendFuncDestination"] intValue]; // color float r,g,b,a; r = [[dictionary valueForKey:@"startColorRed"] floatValue]; g = [[dictionary valueForKey:@"startColorGreen"] floatValue]; b = [[dictionary valueForKey:@"startColorBlue"] floatValue]; a = [[dictionary valueForKey:@"startColorAlpha"] floatValue]; startColor = (ccColor4F) {r,g,b,a}; r = [[dictionary valueForKey:@"startColorVarianceRed"] floatValue]; g = [[dictionary valueForKey:@"startColorVarianceGreen"] floatValue]; b = [[dictionary valueForKey:@"startColorVarianceBlue"] floatValue]; a = [[dictionary valueForKey:@"startColorVarianceAlpha"] floatValue]; startColorVar = (ccColor4F) {r,g,b,a}; r = [[dictionary valueForKey:@"finishColorRed"] floatValue]; g = [[dictionary valueForKey:@"finishColorGreen"] floatValue]; b = [[dictionary valueForKey:@"finishColorBlue"] floatValue]; a = [[dictionary valueForKey:@"finishColorAlpha"] floatValue]; endColor = (ccColor4F) {r,g,b,a}; r = [[dictionary valueForKey:@"finishColorVarianceRed"] floatValue]; g = [[dictionary valueForKey:@"finishColorVarianceGreen"] floatValue]; b = [[dictionary valueForKey:@"finishColorVarianceBlue"] floatValue]; a = [[dictionary valueForKey:@"finishColorVarianceAlpha"] floatValue]; endColorVar = (ccColor4F) {r,g,b,a}; // particle size startSize = [[dictionary valueForKey:@"startParticleSize"] floatValue]; startSizeVar = [[dictionary valueForKey:@"startParticleSizeVariance"] floatValue]; endSize = [[dictionary valueForKey:@"finishParticleSize"] floatValue]; endSizeVar = [[dictionary valueForKey:@"finishParticleSizeVariance"] floatValue]; // position float x = [[dictionary valueForKey:@"sourcePositionx"] floatValue]; float y = [[dictionary valueForKey:@"sourcePositiony"] floatValue]; self.position = ccp(x,y); posVar.x = [[dictionary valueForKey:@"sourcePositionVariancex"] floatValue]; posVar.y = [[dictionary valueForKey:@"sourcePositionVariancey"] floatValue]; // Spinning startSpin = [[dictionary valueForKey:@"rotationStart"] floatValue]; startSpinVar = [[dictionary valueForKey:@"rotationStartVariance"] floatValue]; endSpin = [[dictionary valueForKey:@"rotationEnd"] floatValue]; endSpinVar = [[dictionary valueForKey:@"rotationEndVariance"] floatValue]; emitterMode_ = [[dictionary valueForKey:@"emitterType"] intValue]; // Mode A: Gravity + tangential accel + radial accel if( emitterMode_ == kCCParticleModeGravity ) { // gravity mode.A.gravity.x = [[dictionary valueForKey:@"gravityx"] floatValue]; mode.A.gravity.y = [[dictionary valueForKey:@"gravityy"] floatValue]; // // speed mode.A.speed = [[dictionary valueForKey:@"speed"] floatValue]; mode.A.speedVar = [[dictionary valueForKey:@"speedVariance"] floatValue]; // radial acceleration NSString *tmp = [dictionary valueForKey:@"radialAcceleration"]; mode.A.radialAccel = tmp ? [tmp floatValue] : 0; tmp = [dictionary valueForKey:@"radialAccelVariance"]; mode.A.radialAccelVar = tmp ? [tmp floatValue] : 0; // tangential acceleration tmp = [dictionary valueForKey:@"tangentialAcceleration"]; mode.A.tangentialAccel = tmp ? [tmp floatValue] : 0; tmp = [dictionary valueForKey:@"tangentialAccelVariance"]; mode.A.tangentialAccelVar = tmp ? [tmp floatValue] : 0; } // or Mode B: radius movement else if( emitterMode_ == kCCParticleModeRadius ) { float maxRadius = [[dictionary valueForKey:@"maxRadius"] floatValue]; float maxRadiusVar = [[dictionary valueForKey:@"maxRadiusVariance"] floatValue]; float minRadius = [[dictionary valueForKey:@"minRadius"] floatValue]; mode.B.startRadius = maxRadius; mode.B.startRadiusVar = maxRadiusVar; mode.B.endRadius = minRadius; mode.B.endRadiusVar = 0; mode.B.rotatePerSecond = [[dictionary valueForKey:@"rotatePerSecond"] floatValue]; mode.B.rotatePerSecondVar = [[dictionary valueForKey:@"rotatePerSecondVariance"] floatValue]; } else { NSAssert( NO, @"Invalid emitterType in config file"); } // life span life = [[dictionary valueForKey:@"particleLifespan"] floatValue]; lifeVar = [[dictionary valueForKey:@"particleLifespanVariance"] floatValue]; // emission Rate emissionRate = totalParticles/life; // texture // Try to get the texture from the cache NSString *textureName = [dictionary valueForKey:@"textureFileName"]; CCTexture2D *tex = [[CCTextureCache sharedTextureCache] addImage:textureName]; if( tex ) self.texture = tex; else { NSString *textureData = [dictionary valueForKey:@"textureImageData"]; NSAssert( textureData, @"CCParticleSystem: Couldn't load texture"); // if it fails, try to get it from the base64-gzipped data unsigned char *buffer = NULL; int len = base64Decode((unsigned char*)[textureData UTF8String], (unsigned int)[textureData length], &buffer); NSAssert( buffer != NULL, @"CCParticleSystem: error decoding textureImageData"); unsigned char *deflated = NULL; NSUInteger deflatedLen = ccInflateMemory(buffer, len, &deflated); free( buffer ); NSAssert( deflated != NULL, @"CCParticleSystem: error ungzipping textureImageData"); NSData *data = [[NSData alloc] initWithBytes:deflated length:deflatedLen]; #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED UIImage *image = [[UIImage alloc] initWithData:data]; #elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) NSBitmapImageRep *image = [[NSBitmapImageRep alloc] initWithData:data]; #endif free(deflated); deflated = NULL; self.texture = [[CCTextureCache sharedTextureCache] addCGImage:[image CGImage] forKey:textureName]; [data release]; [image release]; } NSAssert( [self texture] != NULL, @"CCParticleSystem: error loading the texture"); } return self; } -(id) initWithTotalParticles:(NSUInteger) numberOfParticles { if( (self=[super init]) ) { totalParticles = numberOfParticles; particles = calloc( totalParticles, sizeof(tCCParticle) ); if( ! particles ) { NSLog(@"Particle system: not enough memory"); [self release]; return nil; } // default, active active = YES; // default blend function blendFunc_ = (ccBlendFunc) { CC_BLEND_SRC, CC_BLEND_DST }; // default movement type; positionType_ = kCCPositionTypeFree; // by default be in mode A: emitterMode_ = kCCParticleModeGravity; // default: modulate // XXX: not used // colorModulate = YES; autoRemoveOnFinish_ = NO; // profiling #if CC_ENABLE_PROFILERS _profilingTimer = [[CCProfiler timerWithName:@"particle system" andInstance:self] retain]; #endif // Optimization: compile udpateParticle method updateParticleSel = @selector(updateQuadWithParticle:newPosition:); updateParticleImp = (CC_UPDATE_PARTICLE_IMP) [self methodForSelector:updateParticleSel]; // udpate after action in run! [self scheduleUpdateWithPriority:1]; } return self; } -(void) dealloc { free( particles ); [texture_ release]; // profiling #if CC_ENABLE_PROFILERS [CCProfiler releaseTimer:_profilingTimer]; #endif [super dealloc]; } -(BOOL) addParticle { if( [self isFull] ) return NO; tCCParticle * particle = &particles[ particleCount ]; [self initParticle: particle]; particleCount++; return YES; } -(void) initParticle: (tCCParticle*) particle { // timeToLive // no negative life. prevent division by 0 particle->timeToLive = life + lifeVar * CCRANDOM_MINUS1_1(); particle->timeToLive = MAX(0, particle->timeToLive); // position particle->pos.x = sourcePosition.x + posVar.x * CCRANDOM_MINUS1_1(); particle->pos.x *= CC_CONTENT_SCALE_FACTOR(); particle->pos.y = sourcePosition.y + posVar.y * CCRANDOM_MINUS1_1(); particle->pos.y *= CC_CONTENT_SCALE_FACTOR(); // Color ccColor4F start; start.r = clampf( startColor.r + startColorVar.r * CCRANDOM_MINUS1_1(), 0, 1); start.g = clampf( startColor.g + startColorVar.g * CCRANDOM_MINUS1_1(), 0, 1); start.b = clampf( startColor.b + startColorVar.b * CCRANDOM_MINUS1_1(), 0, 1); start.a = clampf( startColor.a + startColorVar.a * CCRANDOM_MINUS1_1(), 0, 1); ccColor4F end; end.r = clampf( endColor.r + endColorVar.r * CCRANDOM_MINUS1_1(), 0, 1); end.g = clampf( endColor.g + endColorVar.g * CCRANDOM_MINUS1_1(), 0, 1); end.b = clampf( endColor.b + endColorVar.b * CCRANDOM_MINUS1_1(), 0, 1); end.a = clampf( endColor.a + endColorVar.a * CCRANDOM_MINUS1_1(), 0, 1); particle->color = start; particle->deltaColor.r = (end.r - start.r) / particle->timeToLive; particle->deltaColor.g = (end.g - start.g) / particle->timeToLive; particle->deltaColor.b = (end.b - start.b) / particle->timeToLive; particle->deltaColor.a = (end.a - start.a) / particle->timeToLive; // size float startS = startSize + startSizeVar * CCRANDOM_MINUS1_1(); startS = MAX(0, startS); // No negative value startS *= CC_CONTENT_SCALE_FACTOR(); particle->size = startS; if( endSize == kCCParticleStartSizeEqualToEndSize ) particle->deltaSize = 0; else { float endS = endSize + endSizeVar * CCRANDOM_MINUS1_1(); endS = MAX(0, endS); // No negative values endS *= CC_CONTENT_SCALE_FACTOR(); particle->deltaSize = (endS - startS) / particle->timeToLive; } // rotation float startA = startSpin + startSpinVar * CCRANDOM_MINUS1_1(); float endA = endSpin + endSpinVar * CCRANDOM_MINUS1_1(); particle->rotation = startA; particle->deltaRotation = (endA - startA) / particle->timeToLive; // position if( positionType_ == kCCPositionTypeFree ) { CGPoint p = [self convertToWorldSpace:CGPointZero]; particle->startPos = ccpMult( p, CC_CONTENT_SCALE_FACTOR() ); } else if( positionType_ == kCCPositionTypeRelative ) { particle->startPos = ccpMult( position_, CC_CONTENT_SCALE_FACTOR() ); } // direction float a = CC_DEGREES_TO_RADIANS( angle + angleVar * CCRANDOM_MINUS1_1() ); // Mode Gravity: A if( emitterMode_ == kCCParticleModeGravity ) { CGPoint v = {cosf( a ), sinf( a )}; float s = mode.A.speed + mode.A.speedVar * CCRANDOM_MINUS1_1(); s *= CC_CONTENT_SCALE_FACTOR(); // direction particle->mode.A.dir = ccpMult( v, s ); // radial accel particle->mode.A.radialAccel = mode.A.radialAccel + mode.A.radialAccelVar * CCRANDOM_MINUS1_1(); particle->mode.A.radialAccel *= CC_CONTENT_SCALE_FACTOR(); // tangential accel particle->mode.A.tangentialAccel = mode.A.tangentialAccel + mode.A.tangentialAccelVar * CCRANDOM_MINUS1_1(); particle->mode.A.tangentialAccel *= CC_CONTENT_SCALE_FACTOR(); } // Mode Radius: B else { // Set the default diameter of the particle from the source position float startRadius = mode.B.startRadius + mode.B.startRadiusVar * CCRANDOM_MINUS1_1(); float endRadius = mode.B.endRadius + mode.B.endRadiusVar * CCRANDOM_MINUS1_1(); startRadius *= CC_CONTENT_SCALE_FACTOR(); endRadius *= CC_CONTENT_SCALE_FACTOR(); particle->mode.B.radius = startRadius; if( mode.B.endRadius == kCCParticleStartRadiusEqualToEndRadius ) particle->mode.B.deltaRadius = 0; else particle->mode.B.deltaRadius = (endRadius - startRadius) / particle->timeToLive; particle->mode.B.angle = a; particle->mode.B.degreesPerSecond = CC_DEGREES_TO_RADIANS(mode.B.rotatePerSecond + mode.B.rotatePerSecondVar * CCRANDOM_MINUS1_1()); } } -(void) stopSystem { active = NO; elapsed = duration; emitCounter = 0; } -(void) resetSystem { active = YES; elapsed = 0; for(particleIdx = 0; particleIdx < particleCount; ++particleIdx) { tCCParticle *p = &particles[particleIdx]; p->timeToLive = 0; } } -(BOOL) isFull { return (particleCount == totalParticles); } #pragma mark ParticleSystem - MainLoop -(void) update: (ccTime) dt { if( active && emissionRate ) { float rate = 1.0f / emissionRate; emitCounter += dt; while( particleCount < totalParticles && emitCounter > rate ) { [self addParticle]; emitCounter -= rate; } elapsed += dt; if(duration != -1 && duration < elapsed) [self stopSystem]; } particleIdx = 0; #if CC_ENABLE_PROFILERS CCProfilingBeginTimingBlock(_profilingTimer); #endif CGPoint currentPosition = CGPointZero; if( positionType_ == kCCPositionTypeFree ) { currentPosition = [self convertToWorldSpace:CGPointZero]; currentPosition.x *= CC_CONTENT_SCALE_FACTOR(); currentPosition.y *= CC_CONTENT_SCALE_FACTOR(); } else if( positionType_ == kCCPositionTypeRelative ) { currentPosition = position_; currentPosition.x *= CC_CONTENT_SCALE_FACTOR(); currentPosition.y *= CC_CONTENT_SCALE_FACTOR(); } while( particleIdx < particleCount ) { tCCParticle *p = &particles[particleIdx]; // life p->timeToLive -= dt; if( p->timeToLive > 0 ) { // Mode A: gravity, direction, tangential accel & radial accel if( emitterMode_ == kCCParticleModeGravity ) { CGPoint tmp, radial, tangential; radial = CGPointZero; // radial acceleration if(p->pos.x || p->pos.y) radial = ccpNormalize(p->pos); tangential = radial; radial = ccpMult(radial, p->mode.A.radialAccel); // tangential acceleration float newy = tangential.x; tangential.x = -tangential.y; tangential.y = newy; tangential = ccpMult(tangential, p->mode.A.tangentialAccel); // (gravity + radial + tangential) * dt tmp = ccpAdd( ccpAdd( radial, tangential), mode.A.gravity); tmp = ccpMult( tmp, dt); p->mode.A.dir = ccpAdd( p->mode.A.dir, tmp); tmp = ccpMult(p->mode.A.dir, dt); p->pos = ccpAdd( p->pos, tmp ); } // Mode B: radius movement else { // Update the angle and radius of the particle. p->mode.B.angle += p->mode.B.degreesPerSecond * dt; p->mode.B.radius += p->mode.B.deltaRadius * dt; p->pos.x = - cosf(p->mode.B.angle) * p->mode.B.radius; p->pos.y = - sinf(p->mode.B.angle) * p->mode.B.radius; } // color p->color.r += (p->deltaColor.r * dt); p->color.g += (p->deltaColor.g * dt); p->color.b += (p->deltaColor.b * dt); p->color.a += (p->deltaColor.a * dt); // size p->size += (p->deltaSize * dt); p->size = MAX( 0, p->size ); // angle p->rotation += (p->deltaRotation * dt); // // update values in quad // CGPoint newPos; if( positionType_ == kCCPositionTypeFree || positionType_ == kCCPositionTypeRelative ) { CGPoint diff = ccpSub( currentPosition, p->startPos ); newPos = ccpSub(p->pos, diff); } else newPos = p->pos; updateParticleImp(self, updateParticleSel, p, newPos); // update particle counter particleIdx++; } else { // life < 0 if( particleIdx != particleCount-1 ) particles[particleIdx] = particles[particleCount-1]; particleCount--; if( particleCount == 0 && autoRemoveOnFinish_ ) { [self unscheduleUpdate]; [parent_ removeChild:self cleanup:YES]; return; } } } #if CC_ENABLE_PROFILERS CCProfilingEndTimingBlock(_profilingTimer); #endif #ifdef CC_USES_VBO [self postStep]; #endif } -(void) updateQuadWithParticle:(tCCParticle*)particle newPosition:(CGPoint)pos; { // should be overriden } -(void) postStep { // should be overriden } #pragma mark ParticleSystem - CCTexture protocol -(void) setTexture:(CCTexture2D*) texture { [texture_ release]; texture_ = [texture retain]; // If the new texture has No premultiplied alpha, AND the blendFunc hasn't been changed, then update it if( texture_ && ! [texture hasPremultipliedAlpha] && ( blendFunc_.src == CC_BLEND_SRC && blendFunc_.dst == CC_BLEND_DST ) ) { blendFunc_.src = GL_SRC_ALPHA; blendFunc_.dst = GL_ONE_MINUS_SRC_ALPHA; } } -(CCTexture2D*) texture { return texture_; } #pragma mark ParticleSystem - Additive Blending -(void) setBlendAdditive:(BOOL)additive { if( additive ) { blendFunc_.src = GL_SRC_ALPHA; blendFunc_.dst = GL_ONE; } else { if( texture_ && ! [texture_ hasPremultipliedAlpha] ) { blendFunc_.src = GL_SRC_ALPHA; blendFunc_.dst = GL_ONE_MINUS_SRC_ALPHA; } else { blendFunc_.src = CC_BLEND_SRC; blendFunc_.dst = CC_BLEND_DST; } } } -(BOOL) blendAdditive { return( blendFunc_.src == GL_SRC_ALPHA && blendFunc_.dst == GL_ONE); } #pragma mark ParticleSystem - Properties of Gravity Mode -(void) setTangentialAccel:(float)t { NSAssert( emitterMode_ == kCCParticleModeGravity, @"Particle Mode should be Gravity"); mode.A.tangentialAccel = t; } -(float) tangentialAccel { NSAssert( emitterMode_ == kCCParticleModeGravity, @"Particle Mode should be Gravity"); return mode.A.tangentialAccel; } -(void) setTangentialAccelVar:(float)t { NSAssert( emitterMode_ == kCCParticleModeGravity, @"Particle Mode should be Gravity"); mode.A.tangentialAccelVar = t; } -(float) tangentialAccelVar { NSAssert( emitterMode_ == kCCParticleModeGravity, @"Particle Mode should be Gravity"); return mode.A.tangentialAccelVar; } -(void) setRadialAccel:(float)t { NSAssert( emitterMode_ == kCCParticleModeGravity, @"Particle Mode should be Gravity"); mode.A.radialAccel = t; } -(float) radialAccel { NSAssert( emitterMode_ == kCCParticleModeGravity, @"Particle Mode should be Gravity"); return mode.A.radialAccel; } -(void) setRadialAccelVar:(float)t { NSAssert( emitterMode_ == kCCParticleModeGravity, @"Particle Mode should be Gravity"); mode.A.radialAccelVar = t; } -(float) radialAccelVar { NSAssert( emitterMode_ == kCCParticleModeGravity, @"Particle Mode should be Gravity"); return mode.A.radialAccelVar; } -(void) setGravity:(CGPoint)g { NSAssert( emitterMode_ == kCCParticleModeGravity, @"Particle Mode should be Gravity"); mode.A.gravity = g; } -(CGPoint) gravity { NSAssert( emitterMode_ == kCCParticleModeGravity, @"Particle Mode should be Gravity"); return mode.A.gravity; } -(void) setSpeed:(float)speed { NSAssert( emitterMode_ == kCCParticleModeGravity, @"Particle Mode should be Gravity"); mode.A.speed = speed; } -(float) speed { NSAssert( emitterMode_ == kCCParticleModeGravity, @"Particle Mode should be Gravity"); return mode.A.speed; } -(void) setSpeedVar:(float)speedVar { NSAssert( emitterMode_ == kCCParticleModeGravity, @"Particle Mode should be Gravity"); mode.A.speedVar = speedVar; } -(float) speedVar { NSAssert( emitterMode_ == kCCParticleModeGravity, @"Particle Mode should be Gravity"); return mode.A.speedVar; } #pragma mark ParticleSystem - Properties of Radius Mode -(void) setStartRadius:(float)startRadius { NSAssert( emitterMode_ == kCCParticleModeRadius, @"Particle Mode should be Radius"); mode.B.startRadius = startRadius; } -(float) startRadius { NSAssert( emitterMode_ == kCCParticleModeRadius, @"Particle Mode should be Radius"); return mode.B.startRadius; } -(void) setStartRadiusVar:(float)startRadiusVar { NSAssert( emitterMode_ == kCCParticleModeRadius, @"Particle Mode should be Radius"); mode.B.startRadiusVar = startRadiusVar; } -(float) startRadiusVar { NSAssert( emitterMode_ == kCCParticleModeRadius, @"Particle Mode should be Radius"); return mode.B.startRadiusVar; } -(void) setEndRadius:(float)endRadius { NSAssert( emitterMode_ == kCCParticleModeRadius, @"Particle Mode should be Radius"); mode.B.endRadius = endRadius; } -(float) endRadius { NSAssert( emitterMode_ == kCCParticleModeRadius, @"Particle Mode should be Radius"); return mode.B.endRadius; } -(void) setEndRadiusVar:(float)endRadiusVar { NSAssert( emitterMode_ == kCCParticleModeRadius, @"Particle Mode should be Radius"); mode.B.endRadiusVar = endRadiusVar; } -(float) endRadiusVar { NSAssert( emitterMode_ == kCCParticleModeRadius, @"Particle Mode should be Radius"); return mode.B.endRadiusVar; } -(void) setRotatePerSecond:(float)degrees { NSAssert( emitterMode_ == kCCParticleModeRadius, @"Particle Mode should be Radius"); mode.B.rotatePerSecond = degrees; } -(float) rotatePerSecond { NSAssert( emitterMode_ == kCCParticleModeRadius, @"Particle Mode should be Radius"); return mode.B.rotatePerSecond; } -(void) setRotatePerSecondVar:(float)degrees { NSAssert( emitterMode_ == kCCParticleModeRadius, @"Particle Mode should be Radius"); mode.B.rotatePerSecondVar = degrees; } -(float) rotatePerSecondVar { NSAssert( emitterMode_ == kCCParticleModeRadius, @"Particle Mode should be Radius"); return mode.B.rotatePerSecondVar; } @end