From: dsc Date: Wed, 8 Jun 2011 23:08:20 +0000 (-0700) Subject: Adds Cocos libraries. X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=8dfcc7d594350b2c7564494f6692f2772e972768;p=tanks-ios.git Adds Cocos libraries. --- diff --git a/libs/CocosDenshion/CDAudioManager.h b/libs/CocosDenshion/CDAudioManager.h new file mode 100644 index 0000000..2475929 --- /dev/null +++ b/libs/CocosDenshion/CDAudioManager.h @@ -0,0 +1,243 @@ +/* + Copyright (c) 2010 Steve Oldmeadow + + 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. + + $Id$ + */ + +#import "CocosDenshion.h" +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 30000 + #import +#else + #import "CDXMacOSXSupport.h" +#endif + +/** Different modes of the engine */ +typedef enum { + kAMM_FxOnly, //!Other apps will be able to play audio + kAMM_FxPlusMusic, //!Only this app will play audio + kAMM_FxPlusMusicIfNoOtherAudio, //!If another app is playing audio at start up then allow it to continue and don't play music + kAMM_MediaPlayback, //!This app takes over audio e.g music player app + kAMM_PlayAndRecord //!App takes over audio and has input and output +} tAudioManagerMode; + +/** Possible states of the engine */ +typedef enum { + kAMStateUninitialised, //!Audio manager has not been initialised - do not use + kAMStateInitialising, //!Audio manager is in the process of initialising - do not use + kAMStateInitialised //!Audio manager is initialised - safe to use +} tAudioManagerState; + +typedef enum { + kAMRBDoNothing, //Audio manager will not do anything on resign or becoming active + kAMRBStopPlay, //Background music is stopped on resign and resumed on become active + kAMRBStop //Background music is stopped on resign but not resumed - maybe because you want to do this from within your game +} tAudioManagerResignBehavior; + +/** Notifications */ +extern NSString * const kCDN_AudioManagerInitialised; + +@interface CDAsynchInitialiser : NSOperation {} +@end + +/** CDAudioManager supports two long audio source channels called left and right*/ +typedef enum { + kASC_Left = 0, + kASC_Right = 1 +} tAudioSourceChannel; + +typedef enum { + kLAS_Init, + kLAS_Loaded, + kLAS_Playing, + kLAS_Paused, + kLAS_Stopped, +} tLongAudioSourceState; + +@class CDLongAudioSource; +@protocol CDLongAudioSourceDelegate +@optional +/** The audio source completed playing */ +- (void) cdAudioSourceDidFinishPlaying:(CDLongAudioSource *) audioSource; +/** The file used to load the audio source has changed */ +- (void) cdAudioSourceFileDidChange:(CDLongAudioSource *) audioSource; +@end + +/** + CDLongAudioSource represents an audio source that has a long duration which makes + it costly to load into memory for playback as an effect using CDSoundEngine. Examples + include background music and narration tracks. The audio file may or may not be compressed. + Bear in mind that current iDevices can only use hardware to decode a single compressed + audio file at a time and playing multiple compressed files will result in a performance drop + as software decompression will take place. + @since v0.99 + */ +@interface CDLongAudioSource : NSObject { + AVAudioPlayer *audioSourcePlayer; + NSString *audioSourceFilePath; + NSInteger numberOfLoops; + float volume; + id delegate; + BOOL mute; + BOOL enabled_; + BOOL backgroundMusic; +@public + BOOL systemPaused;//Used for auto resign handling + NSTimeInterval systemPauseLocation;//Used for auto resign handling +@protected + tLongAudioSourceState state; +} +@property (readonly) AVAudioPlayer *audioSourcePlayer; +@property (readonly) NSString *audioSourceFilePath; +@property (readwrite, nonatomic) NSInteger numberOfLoops; +@property (readwrite, nonatomic) float volume; +@property (assign) id delegate; +/* This long audio source functions as background music */ +@property (readwrite, nonatomic) BOOL backgroundMusic; + +/** Loads the file into the audio source */ +-(void) load:(NSString*) filePath; +/** Plays the audio source */ +-(void) play; +/** Stops playing the audio soruce */ +-(void) stop; +/** Pauses the audio source */ +-(void) pause; +/** Rewinds the audio source */ +-(void) rewind; +/** Resumes playing the audio source if it was paused */ +-(void) resume; +/** Returns whether or not the audio source is playing */ +-(BOOL) isPlaying; + +@end + +/** + CDAudioManager manages audio requirements for a game. It provides access to a CDSoundEngine object + for playing sound effects. It provides access to two CDLongAudioSource object (left and right channel) + for playing long duration audio such as background music and narration tracks. Additionally it manages + the audio session to take care of things like audio session interruption and interacting with the audio + of other apps that are running on the device. + + Requirements: + - Firmware: OS 2.2 or greater + - Files: CDAudioManager.*, CocosDenshion.* + - Frameworks: OpenAL, AudioToolbox, AVFoundation + @since v0.8 + */ +@interface CDAudioManager : NSObject { + CDSoundEngine *soundEngine; + CDLongAudioSource *backgroundMusic; + NSMutableArray *audioSourceChannels; + NSString* _audioSessionCategory; + BOOL _audioWasPlayingAtStartup; + tAudioManagerMode _mode; + SEL backgroundMusicCompletionSelector; + id backgroundMusicCompletionListener; + BOOL willPlayBackgroundMusic; + BOOL _mute; + BOOL _resigned; + BOOL _interrupted; + BOOL _audioSessionActive; + BOOL enabled_; + + //For handling resign/become active + BOOL _isObservingAppEvents; + tAudioManagerResignBehavior _resignBehavior; +} + +@property (readonly) CDSoundEngine *soundEngine; +@property (readonly) CDLongAudioSource *backgroundMusic; +@property (readonly) BOOL willPlayBackgroundMusic; + +/** Returns the shared singleton */ ++ (CDAudioManager *) sharedManager; ++ (tAudioManagerState) sharedManagerState; +/** Configures the shared singleton with a mode*/ ++ (void) configure: (tAudioManagerMode) mode; +/** Initializes the engine asynchronously with a mode */ ++ (void) initAsynchronously: (tAudioManagerMode) mode; +/** Initializes the engine synchronously with a mode, channel definition and a total number of channels */ +- (id) init: (tAudioManagerMode) mode; +-(void) audioSessionInterrupted; +-(void) audioSessionResumed; +-(void) setResignBehavior:(tAudioManagerResignBehavior) resignBehavior autoHandle:(BOOL) autoHandle; +/** Returns true is audio is muted at a hardware level e.g user has ringer switch set to off */ +-(BOOL) isDeviceMuted; +/** Returns true if another app is playing audio such as the iPod music player */ +-(BOOL) isOtherAudioPlaying; +/** Sets the way the audio manager interacts with the operating system such as whether it shares output with other apps or obeys the mute switch */ +-(void) setMode:(tAudioManagerMode) mode; +/** Shuts down the shared audio manager instance so that it can be reinitialised */ ++(void) end; + +/** Call if you want to use built in resign behavior but need to do some additional audio processing on resign active. */ +- (void) applicationWillResignActive; +/** Call if you want to use built in resign behavior but need to do some additional audio processing on become active. */ +- (void) applicationDidBecomeActive; + +//New AVAudioPlayer API +/** Loads the data from the specified file path to the channel's audio source */ +-(CDLongAudioSource*) audioSourceLoad:(NSString*) filePath channel:(tAudioSourceChannel) channel; +/** Retrieves the audio source for the specified channel */ +-(CDLongAudioSource*) audioSourceForChannel:(tAudioSourceChannel) channel; + +//Legacy AVAudioPlayer API +/** Plays music in background. The music can be looped or not + It is recommended to use .aac files as background music since they are decoded by the device (hardware). + */ +-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop; +/** Preloads a background music */ +-(void) preloadBackgroundMusic:(NSString*) filePath; +/** Stops playing the background music */ +-(void) stopBackgroundMusic; +/** Pauses the background music */ +-(void) pauseBackgroundMusic; +/** Rewinds the background music */ +-(void) rewindBackgroundMusic; +/** Resumes playing the background music */ +-(void) resumeBackgroundMusic; +/** Returns whether or not the background music is playing */ +-(BOOL) isBackgroundMusicPlaying; + +-(void) setBackgroundMusicCompletionListener:(id) listener selector:(SEL) selector; + +@end + +/** Fader for long audio source objects */ +@interface CDLongAudioSourceFader : CDPropertyModifier{} +@end + +static const int kCDNoBuffer = -1; + +/** Allows buffers to be associated with file names */ +@interface CDBufferManager:NSObject{ + NSMutableDictionary* loadedBuffers; + NSMutableArray *freedBuffers; + CDSoundEngine *soundEngine; + int nextBufferId; +} + +-(id) initWithEngine:(CDSoundEngine *) theSoundEngine; +-(int) bufferForFile:(NSString*) filePath create:(BOOL) create; +-(void) releaseBufferForFile:(NSString *) filePath; + +@end + diff --git a/libs/CocosDenshion/CDAudioManager.m b/libs/CocosDenshion/CDAudioManager.m new file mode 100644 index 0000000..0929f3c --- /dev/null +++ b/libs/CocosDenshion/CDAudioManager.m @@ -0,0 +1,887 @@ +/* + Copyright (c) 2010 Steve Oldmeadow + + 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. + + $Id$ + */ + + +#import "CDAudioManager.h" + +NSString * const kCDN_AudioManagerInitialised = @"kCDN_AudioManagerInitialised"; + +//NSOperation object used to asynchronously initialise +@implementation CDAsynchInitialiser + +-(void) main { + [super main]; + [CDAudioManager sharedManager]; +} + +@end + +@implementation CDLongAudioSource + +@synthesize audioSourcePlayer, audioSourceFilePath, delegate, backgroundMusic; + +-(id) init { + if ((self = [super init])) { + state = kLAS_Init; + volume = 1.0f; + mute = NO; + enabled_ = YES; + } + return self; +} + +-(void) dealloc { + CDLOGINFO(@"Denshion::CDLongAudioSource - deallocating %@", self); + [audioSourcePlayer release]; + [audioSourceFilePath release]; + [super dealloc]; +} + +-(void) load:(NSString*) filePath { + //We have alread loaded a file previously, check if we are being asked to load the same file + if (state == kLAS_Init || ![filePath isEqualToString:audioSourceFilePath]) { + CDLOGINFO(@"Denshion::CDLongAudioSource - Loading new audio source %@",filePath); + //New file + if (state != kLAS_Init) { + [audioSourceFilePath release];//Release old file path + [audioSourcePlayer release];//Release old AVAudioPlayer, they can't be reused + } + audioSourceFilePath = [filePath copy]; + NSError *error = nil; + NSString *path = [CDUtilities fullPathFromRelativePath:audioSourceFilePath]; + audioSourcePlayer = [(AVAudioPlayer*)[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error]; + if (error == nil) { + [audioSourcePlayer prepareToPlay]; + audioSourcePlayer.delegate = self; + if (delegate && [delegate respondsToSelector:@selector(cdAudioSourceFileDidChange:)]) { + //Tell our delegate the file has changed + [delegate cdAudioSourceFileDidChange:self]; + } + } else { + CDLOG(@"Denshion::CDLongAudioSource - Error initialising audio player: %@",error); + } + } else { + //Same file - just return it to a consistent state + [self pause]; + [self rewind]; + } + audioSourcePlayer.volume = volume; + audioSourcePlayer.numberOfLoops = numberOfLoops; + state = kLAS_Loaded; +} + +-(void) play { + if (enabled_) { + self->systemPaused = NO; + [audioSourcePlayer play]; + } else { + CDLOGINFO(@"Denshion::CDLongAudioSource long audio source didn't play because it is disabled"); + } +} + +-(void) stop { + [audioSourcePlayer stop]; +} + +-(void) pause { + [audioSourcePlayer pause]; +} + +-(void) rewind { + [audioSourcePlayer setCurrentTime:0]; +} + +-(void) resume { + [audioSourcePlayer play]; +} + +-(BOOL) isPlaying { + if (state != kLAS_Init) { + return [audioSourcePlayer isPlaying]; + } else { + return NO; + } +} + +-(void) setVolume:(float) newVolume +{ + volume = newVolume; + if (state != kLAS_Init && !mute) { + audioSourcePlayer.volume = newVolume; + } +} + +-(float) volume +{ + return volume; +} + +#pragma mark Audio Interrupt Protocol +-(BOOL) mute +{ + return mute; +} + +-(void) setMute:(BOOL) muteValue +{ + if (mute != muteValue) { + if (mute) { + //Turn sound back on + audioSourcePlayer.volume = volume; + } else { + audioSourcePlayer.volume = 0.0f; + } + mute = muteValue; + } +} + +-(BOOL) enabled +{ + return enabled_; +} + +-(void) setEnabled:(BOOL)enabledValue +{ + if (enabledValue != enabled_) { + enabled_ = enabledValue; + if (!enabled_) { + //"Stop" the sounds + [self pause]; + [self rewind]; + } + } +} + +-(NSInteger) numberOfLoops { + return numberOfLoops; +} + +-(void) setNumberOfLoops:(NSInteger) loopCount +{ + audioSourcePlayer.numberOfLoops = loopCount; + numberOfLoops = loopCount; +} + +- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag { + CDLOGINFO(@"Denshion::CDLongAudioSource - audio player finished"); +#if TARGET_IPHONE_SIMULATOR + CDLOGINFO(@"Denshion::CDLongAudioSource - workaround for OpenAL clobbered audio issue"); + //This is a workaround for an issue in all simulators (tested to 3.1.2). Problem is + //that OpenAL audio playback is clobbered when an AVAudioPlayer stops. Workaround + //is to keep the player playing on an endless loop with 0 volume and then when + //it is played again reset the volume and set loop count appropriately. + //NB: this workaround is not foolproof but it is good enough for most situations. + player.numberOfLoops = -1; + player.volume = 0; + [player play]; +#endif + if (delegate && [delegate respondsToSelector:@selector(cdAudioSourceDidFinishPlaying:)]) { + [delegate cdAudioSourceDidFinishPlaying:self]; + } +} + +-(void)audioPlayerBeginInterruption:(AVAudioPlayer *)player { + CDLOGINFO(@"Denshion::CDLongAudioSource - audio player interrupted"); +} + +-(void)audioPlayerEndInterruption:(AVAudioPlayer *)player { + CDLOGINFO(@"Denshion::CDLongAudioSource - audio player resumed"); + if (self.backgroundMusic) { + //Check if background music can play as rules may have changed during + //the interruption. This is to address a specific issue in 4.x when + //fast task switching + if([CDAudioManager sharedManager].willPlayBackgroundMusic) { + [player play]; + } + } else { + [player play]; + } +} + +@end + + +@interface CDAudioManager (PrivateMethods) +-(BOOL) audioSessionSetActive:(BOOL) active; +-(BOOL) audioSessionSetCategory:(NSString*) category; +-(void) badAlContextHandler; +@end + + +@implementation CDAudioManager +#define BACKGROUND_MUSIC_CHANNEL kASC_Left + +@synthesize soundEngine, willPlayBackgroundMusic; +static CDAudioManager *sharedManager; +static tAudioManagerState _sharedManagerState = kAMStateUninitialised; +static tAudioManagerMode configuredMode; +static BOOL configured = FALSE; + +-(BOOL) audioSessionSetActive:(BOOL) active { + NSError *activationError = nil; + if ([[AVAudioSession sharedInstance] setActive:active error:&activationError]) { + _audioSessionActive = active; + CDLOGINFO(@"Denshion::CDAudioManager - Audio session set active %i succeeded", active); + return YES; + } else { + //Failed + CDLOG(@"Denshion::CDAudioManager - Audio session set active %i failed with error %@", active, activationError); + return NO; + } +} + +-(BOOL) audioSessionSetCategory:(NSString*) category { + NSError *categoryError = nil; + if ([[AVAudioSession sharedInstance] setCategory:category error:&categoryError]) { + CDLOGINFO(@"Denshion::CDAudioManager - Audio session set category %@ succeeded", category); + return YES; + } else { + //Failed + CDLOG(@"Denshion::CDAudioManager - Audio session set category %@ failed with error %@", category, categoryError); + return NO; + } +} + +// Init ++ (CDAudioManager *) sharedManager +{ + @synchronized(self) { + if (!sharedManager) { + if (!configured) { + //Set defaults here + configuredMode = kAMM_FxPlusMusicIfNoOtherAudio; + } + sharedManager = [[CDAudioManager alloc] init:configuredMode]; + _sharedManagerState = kAMStateInitialised;//This is only really relevant when using asynchronous initialisation + [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_AudioManagerInitialised object:nil]; + } + } + return sharedManager; +} + ++ (tAudioManagerState) sharedManagerState { + return _sharedManagerState; +} + +/** + * Call this to set up audio manager asynchronously. Initialisation is finished when sharedManagerState == kAMStateInitialised + */ ++ (void) initAsynchronously: (tAudioManagerMode) mode { + @synchronized(self) { + if (_sharedManagerState == kAMStateUninitialised) { + _sharedManagerState = kAMStateInitialising; + [CDAudioManager configure:mode]; + CDAsynchInitialiser *initOp = [[[CDAsynchInitialiser alloc] init] autorelease]; + NSOperationQueue *opQ = [[[NSOperationQueue alloc] init] autorelease]; + [opQ addOperation:initOp]; + } + } +} + ++ (id) alloc +{ + @synchronized(self) { + NSAssert(sharedManager == nil, @"Attempted to allocate a second instance of a singleton."); + return [super alloc]; + } + return nil; +} + +/* + * Call this method before accessing the shared manager in order to configure the shared audio manager + */ ++ (void) configure: (tAudioManagerMode) mode { + configuredMode = mode; + configured = TRUE; +} + +-(BOOL) isOtherAudioPlaying { + UInt32 isPlaying = 0; + UInt32 varSize = sizeof(isPlaying); + AudioSessionGetProperty (kAudioSessionProperty_OtherAudioIsPlaying, &varSize, &isPlaying); + return (isPlaying != 0); +} + +-(void) setMode:(tAudioManagerMode) mode { + + _mode = mode; + switch (_mode) { + + case kAMM_FxOnly: + //Share audio with other app + CDLOGINFO(@"Denshion::CDAudioManager - Audio will be shared"); + //_audioSessionCategory = kAudioSessionCategory_AmbientSound; + _audioSessionCategory = AVAudioSessionCategoryAmbient; + willPlayBackgroundMusic = NO; + break; + + case kAMM_FxPlusMusic: + //Use audio exclusively - if other audio is playing it will be stopped + CDLOGINFO(@"Denshion::CDAudioManager - Audio will be exclusive"); + //_audioSessionCategory = kAudioSessionCategory_SoloAmbientSound; + _audioSessionCategory = AVAudioSessionCategorySoloAmbient; + willPlayBackgroundMusic = YES; + break; + + case kAMM_MediaPlayback: + //Use audio exclusively, ignore mute switch and sleep + CDLOGINFO(@"Denshion::CDAudioManager - Media playback mode, audio will be exclusive"); + //_audioSessionCategory = kAudioSessionCategory_MediaPlayback; + _audioSessionCategory = AVAudioSessionCategoryPlayback; + willPlayBackgroundMusic = YES; + break; + + case kAMM_PlayAndRecord: + //Use audio exclusively, ignore mute switch and sleep, has inputs and outputs + CDLOGINFO(@"Denshion::CDAudioManager - Play and record mode, audio will be exclusive"); + //_audioSessionCategory = kAudioSessionCategory_PlayAndRecord; + _audioSessionCategory = AVAudioSessionCategoryPlayAndRecord; + willPlayBackgroundMusic = YES; + break; + + default: + //kAudioManagerFxPlusMusicIfNoOtherAudio + if ([self isOtherAudioPlaying]) { + CDLOGINFO(@"Denshion::CDAudioManager - Other audio is playing audio will be shared"); + //_audioSessionCategory = kAudioSessionCategory_AmbientSound; + _audioSessionCategory = AVAudioSessionCategoryAmbient; + willPlayBackgroundMusic = NO; + } else { + CDLOGINFO(@"Denshion::CDAudioManager - Other audio is not playing audio will be exclusive"); + //_audioSessionCategory = kAudioSessionCategory_SoloAmbientSound; + _audioSessionCategory = AVAudioSessionCategorySoloAmbient; + willPlayBackgroundMusic = YES; + } + + break; + } + + [self audioSessionSetCategory:_audioSessionCategory]; + +} + +/** + * This method is used to work around various bugs introduced in 4.x OS versions. In some circumstances the + * audio session is interrupted but never resumed, this results in the loss of OpenAL audio when following + * standard practices. If we detect this situation then we will attempt to resume the audio session ourselves. + * Known triggers: lock the device then unlock it (iOS 4.2 gm), playback a song using MPMediaPlayer (iOS 4.0) + */ +- (void) badAlContextHandler { + if (_interrupted && alcGetCurrentContext() == NULL) { + CDLOG(@"Denshion::CDAudioManager - bad OpenAL context detected, attempting to resume audio session"); + [self audioSessionResumed]; + } +} + +- (id) init: (tAudioManagerMode) mode { + if ((self = [super init])) { + + //Initialise the audio session + AVAudioSession* session = [AVAudioSession sharedInstance]; + session.delegate = self; + + _mode = mode; + backgroundMusicCompletionSelector = nil; + _isObservingAppEvents = FALSE; + _mute = NO; + _resigned = NO; + _interrupted = NO; + enabled_ = YES; + _audioSessionActive = NO; + [self setMode:mode]; + soundEngine = [[CDSoundEngine alloc] init]; + + //Set up audioSource channels + audioSourceChannels = [[NSMutableArray alloc] init]; + CDLongAudioSource *leftChannel = [[CDLongAudioSource alloc] init]; + leftChannel.backgroundMusic = YES; + CDLongAudioSource *rightChannel = [[CDLongAudioSource alloc] init]; + rightChannel.backgroundMusic = NO; + [audioSourceChannels insertObject:leftChannel atIndex:kASC_Left]; + [audioSourceChannels insertObject:rightChannel atIndex:kASC_Right]; + [leftChannel release]; + [rightChannel release]; + //Used to support legacy APIs + backgroundMusic = [self audioSourceForChannel:BACKGROUND_MUSIC_CHANNEL]; + backgroundMusic.delegate = self; + + //Add handler for bad al context messages, these are posted by the sound engine. + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(badAlContextHandler) name:kCDN_BadAlContext object:nil]; + + } + return self; +} + +-(void) dealloc { + CDLOGINFO(@"Denshion::CDAudioManager - deallocating"); + [self stopBackgroundMusic]; + [soundEngine release]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [self audioSessionSetActive:NO]; + [audioSourceChannels release]; + [super dealloc]; +} + +/** Retrieves the audio source for the specified channel */ +-(CDLongAudioSource*) audioSourceForChannel:(tAudioSourceChannel) channel +{ + return (CDLongAudioSource*)[audioSourceChannels objectAtIndex:channel]; +} + +/** Loads the data from the specified file path to the channel's audio source */ +-(CDLongAudioSource*) audioSourceLoad:(NSString*) filePath channel:(tAudioSourceChannel) channel +{ + CDLongAudioSource *audioSource = [self audioSourceForChannel:channel]; + if (audioSource) { + [audioSource load:filePath]; + } + return audioSource; +} + +-(BOOL) isBackgroundMusicPlaying { + return [self.backgroundMusic isPlaying]; +} + +//NB: originally I tried using a route change listener and intended to store the current route, +//however, on a 3gs running 3.1.2 no route change is generated when the user switches the +//ringer mute switch to off (i.e. enables sound) therefore polling is the only reliable way to +//determine ringer switch state +-(BOOL) isDeviceMuted { + +#if TARGET_IPHONE_SIMULATOR + //Calling audio route stuff on the simulator causes problems + return NO; +#else + CFStringRef newAudioRoute; + UInt32 propertySize = sizeof (CFStringRef); + + AudioSessionGetProperty ( + kAudioSessionProperty_AudioRoute, + &propertySize, + &newAudioRoute + ); + + if (newAudioRoute == NULL) { + //Don't expect this to happen but playing safe otherwise a null in the CFStringCompare will cause a crash + return YES; + } else { + CFComparisonResult newDeviceIsMuted = CFStringCompare ( + newAudioRoute, + (CFStringRef) @"", + 0 + ); + + return (newDeviceIsMuted == kCFCompareEqualTo); + } +#endif +} + +#pragma mark Audio Interrupt Protocol + +-(BOOL) mute { + return _mute; +} + +-(void) setMute:(BOOL) muteValue { + if (muteValue != _mute) { + _mute = muteValue; + [soundEngine setMute:muteValue]; + for( CDLongAudioSource *audioSource in audioSourceChannels) { + audioSource.mute = muteValue; + } + } +} + +-(BOOL) enabled { + return enabled_; +} + +-(void) setEnabled:(BOOL) enabledValue { + if (enabledValue != enabled_) { + enabled_ = enabledValue; + [soundEngine setEnabled:enabled_]; + for( CDLongAudioSource *audioSource in audioSourceChannels) { + audioSource.enabled = enabled_; + } + } +} + +-(CDLongAudioSource*) backgroundMusic +{ + return backgroundMusic; +} + +//Load background music ready for playing +-(void) preloadBackgroundMusic:(NSString*) filePath +{ + [self.backgroundMusic load:filePath]; +} + +-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop +{ + [self.backgroundMusic load:filePath]; + + if (!willPlayBackgroundMusic || _mute) { + CDLOGINFO(@"Denshion::CDAudioManager - play bgm aborted because audio is not exclusive or sound is muted"); + return; + } + + if (loop) { + [self.backgroundMusic setNumberOfLoops:-1]; + } else { + [self.backgroundMusic setNumberOfLoops:0]; + } + [self.backgroundMusic play]; +} + +-(void) stopBackgroundMusic +{ + [self.backgroundMusic stop]; +} + +-(void) pauseBackgroundMusic +{ + [self.backgroundMusic pause]; +} + +-(void) resumeBackgroundMusic +{ + if (!willPlayBackgroundMusic || _mute) { + CDLOGINFO(@"Denshion::CDAudioManager - resume bgm aborted because audio is not exclusive or sound is muted"); + return; + } + + [self.backgroundMusic resume]; +} + +-(void) rewindBackgroundMusic +{ + [self.backgroundMusic rewind]; +} + +-(void) setBackgroundMusicCompletionListener:(id) listener selector:(SEL) selector { + backgroundMusicCompletionListener = listener; + backgroundMusicCompletionSelector = selector; +} + +/* + * Call this method to have the audio manager automatically handle application resign and + * become active. Pass a tAudioManagerResignBehavior to indicate the desired behavior + * for resigning and becoming active again. + * + * If autohandle is YES then the applicationWillResignActive and applicationDidBecomActive + * methods are automatically called, otherwise you must call them yourself at the appropriate time. + * + * Based on idea of Dominique Bongard + */ +-(void) setResignBehavior:(tAudioManagerResignBehavior) resignBehavior autoHandle:(BOOL) autoHandle { + + if (!_isObservingAppEvents && autoHandle) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:@"UIApplicationWillResignActiveNotification" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:@"UIApplicationDidBecomeActiveNotification" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:@"UIApplicationWillTerminateNotification" object:nil]; + _isObservingAppEvents = TRUE; + } + _resignBehavior = resignBehavior; +} + +- (void) applicationWillResignActive { + self->_resigned = YES; + + //Set the audio sesssion to one that allows sharing so that other audio won't be clobbered on resume + [self audioSessionSetCategory:AVAudioSessionCategoryAmbient]; + + switch (_resignBehavior) { + + case kAMRBStopPlay: + + for( CDLongAudioSource *audioSource in audioSourceChannels) { + if (audioSource.isPlaying) { + audioSource->systemPaused = YES; + audioSource->systemPauseLocation = audioSource.audioSourcePlayer.currentTime; + [audioSource stop]; + } else { + //Music is either paused or stopped, if it is paused it will be restarted + //by OS so we will stop it. + audioSource->systemPaused = NO; + [audioSource stop]; + } + } + break; + + case kAMRBStop: + //Stop music regardless of whether it is playing or not because if it was paused + //then the OS would resume it + for( CDLongAudioSource *audioSource in audioSourceChannels) { + [audioSource stop]; + } + + default: + break; + + } + CDLOGINFO(@"Denshion::CDAudioManager - handled resign active"); +} + +//Called when application resigns active only if setResignBehavior has been called +- (void) applicationWillResignActive:(NSNotification *) notification +{ + [self applicationWillResignActive]; +} + +- (void) applicationDidBecomeActive { + + if (self->_resigned) { + _resigned = NO; + //Reset the mode incase something changed with audio while we were inactive + [self setMode:_mode]; + switch (_resignBehavior) { + + case kAMRBStopPlay: + + //Music had been stopped but stop maintains current time + //so playing again will continue from where music was before resign active. + //We check if music can be played because while we were inactive the user might have + //done something that should force music to not play such as starting a track in the iPod + if (self.willPlayBackgroundMusic) { + for( CDLongAudioSource *audioSource in audioSourceChannels) { + if (audioSource->systemPaused) { + [audioSource resume]; + audioSource->systemPaused = NO; + } + } + } + break; + + default: + break; + + } + CDLOGINFO(@"Denshion::CDAudioManager - audio manager handled become active"); + } +} + +//Called when application becomes active only if setResignBehavior has been called +- (void) applicationDidBecomeActive:(NSNotification *) notification +{ + [self applicationDidBecomeActive]; +} + +//Called when application terminates only if setResignBehavior has been called +- (void) applicationWillTerminate:(NSNotification *) notification +{ + CDLOGINFO(@"Denshion::CDAudioManager - audio manager handling terminate"); + [self stopBackgroundMusic]; +} + +/** The audio source completed playing */ +- (void) cdAudioSourceDidFinishPlaying:(CDLongAudioSource *) audioSource { + CDLOGINFO(@"Denshion::CDAudioManager - audio manager got told background music finished"); + if (backgroundMusicCompletionSelector != nil) { + [backgroundMusicCompletionListener performSelector:backgroundMusicCompletionSelector]; + } +} + +-(void) beginInterruption { + CDLOGINFO(@"Denshion::CDAudioManager - begin interruption"); + [self audioSessionInterrupted]; +} + +-(void) endInterruption { + CDLOGINFO(@"Denshion::CDAudioManager - end interruption"); + [self audioSessionResumed]; +} + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 +-(void) endInterruptionWithFlags:(NSUInteger)flags { + CDLOGINFO(@"Denshion::CDAudioManager - interruption ended with flags %i",flags); + if (flags == AVAudioSessionInterruptionFlags_ShouldResume) { + [self audioSessionResumed]; + } +} +#endif + +-(void)audioSessionInterrupted +{ + if (!_interrupted) { + CDLOGINFO(@"Denshion::CDAudioManager - Audio session interrupted"); + _interrupted = YES; + + // Deactivate the current audio session + [self audioSessionSetActive:NO]; + + if (alcGetCurrentContext() != NULL) { + CDLOGINFO(@"Denshion::CDAudioManager - Setting OpenAL context to NULL"); + + ALenum error = AL_NO_ERROR; + + // set the current context to NULL will 'shutdown' openAL + alcMakeContextCurrent(NULL); + + if((error = alGetError()) != AL_NO_ERROR) { + CDLOG(@"Denshion::CDAudioManager - Error making context current %x\n", error); + } + #pragma unused(error) + } + } +} + +-(void)audioSessionResumed +{ + if (_interrupted) { + CDLOGINFO(@"Denshion::CDAudioManager - Audio session resumed"); + _interrupted = NO; + + BOOL activationResult = NO; + // Reactivate the current audio session + activationResult = [self audioSessionSetActive:YES]; + + //This code is to handle a problem with iOS 4.0 and 4.01 where reactivating the session can fail if + //task switching is performed too rapidly. A test case that reliably reproduces the issue is to call the + //iPhone and then hang up after two rings (timing may vary ;)) + //Basically we keep waiting and trying to let the OS catch up with itself but the number of tries is + //limited. + if (!activationResult) { + CDLOG(@"Denshion::CDAudioManager - Failure reactivating audio session, will try wait-try cycle"); + int activateCount = 0; + while (!activationResult && activateCount < 10) { + [NSThread sleepForTimeInterval:0.5]; + activationResult = [self audioSessionSetActive:YES]; + activateCount++; + CDLOGINFO(@"Denshion::CDAudioManager - Reactivation attempt %i status = %i",activateCount,activationResult); + } + } + + if (alcGetCurrentContext() == NULL) { + CDLOGINFO(@"Denshion::CDAudioManager - Restoring OpenAL context"); + ALenum error = AL_NO_ERROR; + // Restore open al context + alcMakeContextCurrent([soundEngine openALContext]); + if((error = alGetError()) != AL_NO_ERROR) { + CDLOG(@"Denshion::CDAudioManager - Error making context current%x\n", error); + } + #pragma unused(error) + } + } +} + ++(void) end { + [sharedManager release]; + sharedManager = nil; +} + +@end + +/////////////////////////////////////////////////////////////////////////////////////// +@implementation CDLongAudioSourceFader + +-(void) _setTargetProperty:(float) newVal { + ((CDLongAudioSource*)target).volume = newVal; +} + +-(float) _getTargetProperty { + return ((CDLongAudioSource*)target).volume; +} + +-(void) _stopTarget { + //Pause instead of stop as stop releases resources and causes problems in the simulator + [((CDLongAudioSource*)target) pause]; +} + +-(Class) _allowableType { + return [CDLongAudioSource class]; +} + +@end +/////////////////////////////////////////////////////////////////////////////////////// +@implementation CDBufferManager + +-(id) initWithEngine:(CDSoundEngine *) theSoundEngine { + if ((self = [super init])) { + soundEngine = theSoundEngine; + loadedBuffers = [[NSMutableDictionary alloc] initWithCapacity:CD_BUFFERS_START]; + freedBuffers = [[NSMutableArray alloc] init]; + nextBufferId = 0; + } + return self; +} + +-(void) dealloc { + [loadedBuffers release]; + [freedBuffers release]; + [super dealloc]; +} + +-(int) bufferForFile:(NSString*) filePath create:(BOOL) create { + + NSNumber* soundId = (NSNumber*)[loadedBuffers objectForKey:filePath]; + if(soundId == nil) + { + if (create) { + NSNumber* bufferId = nil; + //First try to get a buffer from the free buffers + if ([freedBuffers count] > 0) { + bufferId = [[[freedBuffers lastObject] retain] autorelease]; + [freedBuffers removeLastObject]; + CDLOGINFO(@"Denshion::CDBufferManager reusing buffer id %i",[bufferId intValue]); + } else { + bufferId = [[NSNumber alloc] initWithInt:nextBufferId]; + [bufferId autorelease]; + CDLOGINFO(@"Denshion::CDBufferManager generating new buffer id %i",[bufferId intValue]); + nextBufferId++; + } + + if ([soundEngine loadBuffer:[bufferId intValue] filePath:filePath]) { + //File successfully loaded + CDLOGINFO(@"Denshion::CDBufferManager buffer loaded %@ %@",bufferId,filePath); + [loadedBuffers setObject:bufferId forKey:filePath]; + return [bufferId intValue]; + } else { + //File didn't load, put buffer id on free list + [freedBuffers addObject:bufferId]; + return kCDNoBuffer; + } + } else { + //No matching buffer was found + return kCDNoBuffer; + } + } else { + return [soundId intValue]; + } +} + +-(void) releaseBufferForFile:(NSString *) filePath { + int bufferId = [self bufferForFile:filePath create:NO]; + if (bufferId != kCDNoBuffer) { + [soundEngine unloadBuffer:bufferId]; + [loadedBuffers removeObjectForKey:filePath]; + NSNumber *freedBufferId = [[NSNumber alloc] initWithInt:bufferId]; + [freedBufferId autorelease]; + [freedBuffers addObject:freedBufferId]; + } +} +@end + + + diff --git a/libs/CocosDenshion/CDConfig.h b/libs/CocosDenshion/CDConfig.h new file mode 100644 index 0000000..2bd8f76 --- /dev/null +++ b/libs/CocosDenshion/CDConfig.h @@ -0,0 +1,60 @@ +/* + Copyright (c) 2010 Steve Oldmeadow + + 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. + + $Id$ + */ +#define COCOSDENSHION_VERSION "Aphex.rc" + + +/** + If enabled code useful for debugging such as parameter check assertions will be performed. + If you experience any problems you should enable this and test your code with a debug build. + */ +//#define CD_DEBUG 1 + +/** + The total number of sounds/buffers that can be loaded assuming memory is sufficient + */ +//Number of buffers slots that will be initially created +#define CD_BUFFERS_START 64 +//Number of buffers that will be added +#define CD_BUFFERS_INCREMENT 16 + +/** + If enabled, OpenAL code will use static buffers. When static buffers are used the audio + data is managed outside of OpenAL, this eliminates a memcpy operation which leads to + higher performance when loading sounds. + + However, the downside is that when the audio data is freed you must + be certain that it is no longer being accessed otherwise your app will crash. Testing on OS 2.2.1 + and 3.1.2 has shown that this may occur if a buffer is being used by a source with state = AL_PLAYING + when the buffer is deleted. If the data is freed too quickly after the source is stopped then + a crash will occur. The implemented workaround is that when static buffers are used the unloadBuffer code will wait for + any playing sources to finish playing before the associated buffer and data are deleted, however, this delay may negate any + performance gains that are achieved during loading. + + Performance tests on a 1st gen iPod running OS 2.2.1 loading the CocosDenshionDemo sounds were ~0.14 seconds without + static buffers and ~0.12 seconds when using static buffers. + + */ +//#define CD_USE_STATIC_BUFFERS 1 + + diff --git a/libs/CocosDenshion/CDOpenALSupport.h b/libs/CocosDenshion/CDOpenALSupport.h new file mode 100644 index 0000000..661c69e --- /dev/null +++ b/libs/CocosDenshion/CDOpenALSupport.h @@ -0,0 +1,77 @@ +/* + + Disclaimer: IMPORTANT: This Apple software is supplied to you by + Apple Inc. ("Apple") in consideration of your agreement to the + following terms, and your use, installation, modification or + redistribution of this Apple software constitutes acceptance of these + terms. If you do not agree with these terms, please do not use, + install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and + subject to these terms, Apple grants you a personal, non-exclusive + license, under Apple's copyrights in this original Apple software (the + "Apple Software"), to use, reproduce, modify and redistribute the Apple + Software, with or without modifications, in source and/or binary forms; + provided that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the following + text and disclaimers in all such redistributions of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. + may be used to endorse or promote products derived from the Apple + Software without specific prior written permission from Apple. Except + as expressly stated in this notice, no other rights or licenses, express + or implied, are granted by Apple herein, including but not limited to + any patent rights that may be infringed by your derivative works or by + other works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE + MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND + OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED + AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2009 Apple Inc. All Rights Reserved. + + $Id$ + */ + +/* + This file contains code from version 1.1 and 1.4 of MyOpenALSupport.h taken from Apple's oalTouch version. + The 1.4 version code is used for loading IMA4 files, however, this code causes very noticeable clicking + when used to load wave files that are looped so the 1.1 version code is used specifically for loading + wav files. + */ + +#ifndef __CD_OPENAL_H +#define __CD_OPENAL_H + +#ifdef __cplusplus +extern "C" { +#endif + + +#import +#import +#import + + +//Taken from oalTouch MyOpenALSupport 1.1 +void* CDloadWaveAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate); +void* CDloadCafAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate); +void* CDGetOpenALAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate); + +#ifdef __cplusplus +} +#endif + +#endif + + diff --git a/libs/CocosDenshion/CDOpenALSupport.m b/libs/CocosDenshion/CDOpenALSupport.m new file mode 100644 index 0000000..ab0df8e --- /dev/null +++ b/libs/CocosDenshion/CDOpenALSupport.m @@ -0,0 +1,246 @@ +/* + + Disclaimer: IMPORTANT: This Apple software is supplied to you by + Apple Inc. ("Apple") in consideration of your agreement to the + following terms, and your use, installation, modification or + redistribution of this Apple software constitutes acceptance of these + terms. If you do not agree with these terms, please do not use, + install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and + subject to these terms, Apple grants you a personal, non-exclusive + license, under Apple's copyrights in this original Apple software (the + "Apple Software"), to use, reproduce, modify and redistribute the Apple + Software, with or without modifications, in source and/or binary forms; + provided that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the following + text and disclaimers in all such redistributions of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. + may be used to endorse or promote products derived from the Apple + Software without specific prior written permission from Apple. Except + as expressly stated in this notice, no other rights or licenses, express + or implied, are granted by Apple herein, including but not limited to + any patent rights that may be infringed by your derivative works or by + other works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE + MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND + OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED + AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2009 Apple Inc. All Rights Reserved. + + $Id: CDOpenALSupport.h 16 2010-03-11 06:22:10Z steveoldmeadow $ + */ + +#import "CDOpenALSupport.h" +#import "CocosDenshion.h" +#import +#import + +//Taken from oalTouch MyOpenALSupport 1.1 +void* CDloadWaveAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate) +{ + OSStatus err = noErr; + UInt64 fileDataSize = 0; + AudioStreamBasicDescription theFileFormat; + UInt32 thePropertySize = sizeof(theFileFormat); + AudioFileID afid = 0; + void* theData = NULL; + + // Open a file with ExtAudioFileOpen() + err = AudioFileOpenURL(inFileURL, kAudioFileReadPermission, 0, &afid); + if(err) { CDLOG(@"MyGetOpenALAudioData: AudioFileOpenURL FAILED, Error = %ld\n", err); goto Exit; } + + // Get the audio data format + err = AudioFileGetProperty(afid, kAudioFilePropertyDataFormat, &thePropertySize, &theFileFormat); + if(err) { CDLOG(@"MyGetOpenALAudioData: AudioFileGetProperty(kAudioFileProperty_DataFormat) FAILED, Error = %ld\n", err); goto Exit; } + + if (theFileFormat.mChannelsPerFrame > 2) { + CDLOG(@"MyGetOpenALAudioData - Unsupported Format, channel count is greater than stereo\n"); goto Exit; + } + + if ((theFileFormat.mFormatID != kAudioFormatLinearPCM) || (!TestAudioFormatNativeEndian(theFileFormat))) { + CDLOG(@"MyGetOpenALAudioData - Unsupported Format, must be little-endian PCM\n"); goto Exit; + } + + if ((theFileFormat.mBitsPerChannel != 8) && (theFileFormat.mBitsPerChannel != 16)) { + CDLOG(@"MyGetOpenALAudioData - Unsupported Format, must be 8 or 16 bit PCM\n"); goto Exit; + } + + + thePropertySize = sizeof(fileDataSize); + err = AudioFileGetProperty(afid, kAudioFilePropertyAudioDataByteCount, &thePropertySize, &fileDataSize); + if(err) { CDLOG(@"MyGetOpenALAudioData: AudioFileGetProperty(kAudioFilePropertyAudioDataByteCount) FAILED, Error = %ld\n", err); goto Exit; } + + // Read all the data into memory + UInt32 dataSize = (UInt32)fileDataSize; + theData = malloc(dataSize); + if (theData) + { + AudioFileReadBytes(afid, false, 0, &dataSize, theData); + if(err == noErr) + { + // success + *outDataSize = (ALsizei)dataSize; + //This fix was added by me, however, 8 bit sounds have a clipping sound at the end so aren't really usable (SO) + if (theFileFormat.mBitsPerChannel == 16) { + *outDataFormat = (theFileFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; + } else { + *outDataFormat = (theFileFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO8 : AL_FORMAT_MONO8; + } + *outSampleRate = (ALsizei)theFileFormat.mSampleRate; + } + else + { + // failure + free (theData); + theData = NULL; // make sure to return NULL + CDLOG(@"MyGetOpenALAudioData: ExtAudioFileRead FAILED, Error = %ld\n", err); goto Exit; + } + } + +Exit: + // Dispose the ExtAudioFileRef, it is no longer needed + if (afid) AudioFileClose(afid); + return theData; +} + +//Taken from oalTouch MyOpenALSupport 1.4 +void* CDloadCafAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate) +{ + OSStatus status = noErr; + BOOL abort = NO; + SInt64 theFileLengthInFrames = 0; + AudioStreamBasicDescription theFileFormat; + UInt32 thePropertySize = sizeof(theFileFormat); + ExtAudioFileRef extRef = NULL; + void* theData = NULL; + AudioStreamBasicDescription theOutputFormat; + UInt32 dataSize = 0; + + // Open a file with ExtAudioFileOpen() + status = ExtAudioFileOpenURL(inFileURL, &extRef); + if (status != noErr) + { + CDLOG(@"MyGetOpenALAudioData: ExtAudioFileOpenURL FAILED, Error = %ld\n", status); + abort = YES; + } + if (abort) + goto Exit; + + // Get the audio data format + status = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileDataFormat, &thePropertySize, &theFileFormat); + if (status != noErr) + { + CDLOG(@"MyGetOpenALAudioData: ExtAudioFileGetProperty(kExtAudioFileProperty_FileDataFormat) FAILED, Error = %ld\n", status); + abort = YES; + } + if (abort) + goto Exit; + + if (theFileFormat.mChannelsPerFrame > 2) + { + CDLOG(@"MyGetOpenALAudioData - Unsupported Format, channel count is greater than stereo\n"); + abort = YES; + } + if (abort) + goto Exit; + + // Set the client format to 16 bit signed integer (native-endian) data + // Maintain the channel count and sample rate of the original source format + theOutputFormat.mSampleRate = theFileFormat.mSampleRate; + theOutputFormat.mChannelsPerFrame = theFileFormat.mChannelsPerFrame; + + theOutputFormat.mFormatID = kAudioFormatLinearPCM; + theOutputFormat.mBytesPerPacket = 2 * theOutputFormat.mChannelsPerFrame; + theOutputFormat.mFramesPerPacket = 1; + theOutputFormat.mBytesPerFrame = 2 * theOutputFormat.mChannelsPerFrame; + theOutputFormat.mBitsPerChannel = 16; + theOutputFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger; + + // Set the desired client (output) data format + status = ExtAudioFileSetProperty(extRef, kExtAudioFileProperty_ClientDataFormat, sizeof(theOutputFormat), &theOutputFormat); + if (status != noErr) + { + CDLOG(@"MyGetOpenALAudioData: ExtAudioFileSetProperty(kExtAudioFileProperty_ClientDataFormat) FAILED, Error = %ld\n", status); + abort = YES; + } + if (abort) + goto Exit; + + // Get the total frame count + thePropertySize = sizeof(theFileLengthInFrames); + status = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileLengthFrames, &thePropertySize, &theFileLengthInFrames); + if (status != noErr) + { + CDLOG(@"MyGetOpenALAudioData: ExtAudioFileGetProperty(kExtAudioFileProperty_FileLengthFrames) FAILED, Error = %ld\n", status); + abort = YES; + } + if (abort) + goto Exit; + + // Read all the data into memory + dataSize = (UInt32) theFileLengthInFrames * theOutputFormat.mBytesPerFrame; + theData = malloc(dataSize); + if (theData) + { + AudioBufferList theDataBuffer; + theDataBuffer.mNumberBuffers = 1; + theDataBuffer.mBuffers[0].mDataByteSize = dataSize; + theDataBuffer.mBuffers[0].mNumberChannels = theOutputFormat.mChannelsPerFrame; + theDataBuffer.mBuffers[0].mData = theData; + + // Read the data into an AudioBufferList + status = ExtAudioFileRead(extRef, (UInt32*)&theFileLengthInFrames, &theDataBuffer); + if(status == noErr) + { + // success + *outDataSize = (ALsizei)dataSize; + *outDataFormat = (theOutputFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; + *outSampleRate = (ALsizei)theOutputFormat.mSampleRate; + } + else + { + // failure + free (theData); + theData = NULL; // make sure to return NULL + CDLOG(@"MyGetOpenALAudioData: ExtAudioFileRead FAILED, Error = %ld\n", status); + abort = YES; + } + } + if (abort) + goto Exit; + +Exit: + // Dispose the ExtAudioFileRef, it is no longer needed + if (extRef) ExtAudioFileDispose(extRef); + return theData; +} + +void* CDGetOpenALAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate) { + + CFStringRef extension = CFURLCopyPathExtension(inFileURL); + CFComparisonResult isWavFile = 0; + if (extension != NULL) { + isWavFile = CFStringCompare (extension,(CFStringRef)@"wav", kCFCompareCaseInsensitive); + CFRelease(extension); + } + + if (isWavFile == kCFCompareEqualTo) { + return CDloadWaveAudioData(inFileURL, outDataSize, outDataFormat, outSampleRate); + } else { + return CDloadCafAudioData(inFileURL, outDataSize, outDataFormat, outSampleRate); + } +} + diff --git a/libs/CocosDenshion/CocosDenshion.h b/libs/CocosDenshion/CocosDenshion.h new file mode 100644 index 0000000..638d852 --- /dev/null +++ b/libs/CocosDenshion/CocosDenshion.h @@ -0,0 +1,440 @@ +/* + Copyright (c) 2010 Steve Oldmeadow + + 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. + + $Id$ + */ + + + +/** +@file +@b IMPORTANT +There are 3 different ways of using CocosDenshion. Depending on which you choose you +will need to include different files and frameworks. + +@par SimpleAudioEngine +This is recommended for basic audio requirements. If you just want to play some sound fx +and some background music and have no interest in learning the lower level workings then +this is the interface to use. + +Requirements: + - Firmware: OS 2.2 or greater + - Files: SimpleAudioEngine.*, CocosDenshion.* + - Frameworks: OpenAL, AudioToolbox, AVFoundation + +@par CDAudioManager +CDAudioManager is basically a thin wrapper around an AVAudioPlayer object used for playing +background music and a CDSoundEngine object used for playing sound effects. It manages the +audio session for you deals with audio session interruption. It is fairly low level and it +is expected you have some understanding of the underlying technologies. For example, for +many use cases regarding background music it is expected you will work directly with the +backgroundMusic AVAudioPlayer which is exposed as a property. + +Requirements: + - Firmware: OS 2.2 or greater + - Files: CDAudioManager.*, CocosDenshion.* + - Frameworks: OpenAL, AudioToolbox, AVFoundation + +@par CDSoundEngine +CDSoundEngine is a sound engine built upon OpenAL and derived from Apple's oalTouch +example. It can playback up to 32 sounds simultaneously with control over pitch, pan +and gain. It can be set up to handle audio session interruption automatically. You +may decide to use CDSoundEngine directly instead of CDAudioManager or SimpleAudioEngine +because you require OS 2.0 compatibility. + +Requirements: + - Firmware: OS 2.0 or greater + - Files: CocosDenshion.* + - Frameworks: OpenAL, AudioToolbox + +*/ + +#import +#import +#import +#import +#import "CDConfig.h" + + +#if !defined(CD_DEBUG) || CD_DEBUG == 0 +#define CDLOG(...) do {} while (0) +#define CDLOGINFO(...) do {} while (0) + +#elif CD_DEBUG == 1 +#define CDLOG(...) NSLog(__VA_ARGS__) +#define CDLOGINFO(...) do {} while (0) + +#elif CD_DEBUG > 1 +#define CDLOG(...) NSLog(__VA_ARGS__) +#define CDLOGINFO(...) NSLog(__VA_ARGS__) +#endif // CD_DEBUG + + +#import "CDOpenALSupport.h" + +//Tested source limit on 2.2.1 and 3.1.2 with up to 128 sources and appears to work. Older OS versions e.g 2.2 may support only 32 +#define CD_SOURCE_LIMIT 32 //Total number of sources we will ever want, may actually get less +#define CD_NO_SOURCE 0xFEEDFAC //Return value indicating playback failed i.e. no source +#define CD_IGNORE_AUDIO_SESSION 0xBEEFBEE //Used internally to indicate audio session will not be handled +#define CD_MUTE 0xFEEDBAB //Return value indicating sound engine is muted or non functioning +#define CD_NO_SOUND = -1; + +#define CD_SAMPLE_RATE_HIGH 44100 +#define CD_SAMPLE_RATE_MID 22050 +#define CD_SAMPLE_RATE_LOW 16000 +#define CD_SAMPLE_RATE_BASIC 8000 +#define CD_SAMPLE_RATE_DEFAULT 44100 + +extern NSString * const kCDN_BadAlContext; +extern NSString * const kCDN_AsynchLoadComplete; + +extern float const kCD_PitchDefault; +extern float const kCD_PitchLowerOneOctave; +extern float const kCD_PitchHigherOneOctave; +extern float const kCD_PanDefault; +extern float const kCD_PanFullLeft; +extern float const kCD_PanFullRight; +extern float const kCD_GainDefault; + +enum bufferState { + CD_BS_EMPTY = 0, + CD_BS_LOADED = 1, + CD_BS_FAILED = 2 +}; + +typedef struct _sourceGroup { + int startIndex; + int currentIndex; + int totalSources; + bool enabled; + bool nonInterruptible; + int *sourceStatuses;//pointer into array of source status information +} sourceGroup; + +typedef struct _bufferInfo { + ALuint bufferId; + int bufferState; + void* bufferData; + ALenum format; + ALsizei sizeInBytes; + ALsizei frequencyInHertz; +} bufferInfo; + +typedef struct _sourceInfo { + bool usable; + ALuint sourceId; + ALuint attachedBufferId; +} sourceInfo; + +#pragma mark CDAudioTransportProtocol + +@protocol CDAudioTransportProtocol +/** Play the audio */ +-(BOOL) play; +/** Pause the audio, retain resources */ +-(BOOL) pause; +/** Stop the audio, release resources */ +-(BOOL) stop; +/** Return playback to beginning */ +-(BOOL) rewind; +@end + +#pragma mark CDAudioInterruptProtocol + +@protocol CDAudioInterruptProtocol +/** Is audio mute */ +-(BOOL) mute; +/** If YES then audio is silenced but not stopped, calls to start new audio will proceed but silently */ +-(void) setMute:(BOOL) muteValue; +/** Is audio enabled */ +-(BOOL) enabled; +/** If NO then all audio is stopped and any calls to start new audio will be ignored */ +-(void) setEnabled:(BOOL) enabledValue; +@end + +#pragma mark CDUtilities +/** + Collection of utilities required by CocosDenshion + */ +@interface CDUtilities : NSObject +{ +} + +/** Fundamentally the same as the corresponding method is CCFileUtils but added to break binding to cocos2d */ ++(NSString*) fullPathFromRelativePath:(NSString*) relPath; + +@end + + +#pragma mark CDSoundEngine + +/** CDSoundEngine is built upon OpenAL and works with SDK 2.0. + CDSoundEngine is a sound engine built upon OpenAL and derived from Apple's oalTouch + example. It can playback up to 32 sounds simultaneously with control over pitch, pan + and gain. It can be set up to handle audio session interruption automatically. You + may decide to use CDSoundEngine directly instead of CDAudioManager or SimpleAudioEngine + because you require OS 2.0 compatibility. + + Requirements: + - Firmware: OS 2.0 or greater + - Files: CocosDenshion.* + - Frameworks: OpenAL, AudioToolbox + + @since v0.8 + */ +@class CDSoundSource; +@interface CDSoundEngine : NSObject { + + bufferInfo *_buffers; + sourceInfo *_sources; + sourceGroup *_sourceGroups; + ALCcontext *context; + NSUInteger _sourceGroupTotal; + UInt32 _audioSessionCategory; + BOOL _handleAudioSession; + ALfloat _preMuteGain; + NSObject *_mutexBufferLoad; + BOOL mute_; + BOOL enabled_; + + ALenum lastErrorCode_; + BOOL functioning_; + float asynchLoadProgress_; + BOOL getGainWorks_; + + //For managing dynamic allocation of sources and buffers + int sourceTotal_; + int bufferTotal; + +} + +@property (readwrite, nonatomic) ALfloat masterGain; +@property (readonly) ALenum lastErrorCode;//Last OpenAL error code that was generated +@property (readonly) BOOL functioning;//Is the sound engine functioning +@property (readwrite) float asynchLoadProgress; +@property (readonly) BOOL getGainWorks;//Does getting the gain for a source work +/** Total number of sources available */ +@property (readonly) int sourceTotal; +/** Total number of source groups that have been defined */ +@property (readonly) NSUInteger sourceGroupTotal; + +/** Sets the sample rate for the audio mixer. For best performance this should match the sample rate of your audio content */ ++(void) setMixerSampleRate:(Float32) sampleRate; + +/** Initializes the engine with a group definition and a total number of groups */ +-(id)init; + +/** Plays a sound in a channel group with a pitch, pan and gain. The sound could played looped or not */ +-(ALuint) playSound:(int) soundId sourceGroupId:(int)sourceGroupId pitch:(float) pitch pan:(float) pan gain:(float) gain loop:(BOOL) loop; + +/** Creates and returns a sound source object for the specified sound within the specified source group. + */ +-(CDSoundSource *) soundSourceForSound:(int) soundId sourceGroupId:(int) sourceGroupId; + +/** Stops playing a sound */ +- (void) stopSound:(ALuint) sourceId; +/** Stops playing a source group */ +- (void) stopSourceGroup:(int) sourceGroupId; +/** Stops all playing sounds */ +-(void) stopAllSounds; +-(void) defineSourceGroups:(NSArray*) sourceGroupDefinitions; +-(void) defineSourceGroups:(int[]) sourceGroupDefinitions total:(NSUInteger) total; +-(void) setSourceGroupNonInterruptible:(int) sourceGroupId isNonInterruptible:(BOOL) isNonInterruptible; +-(void) setSourceGroupEnabled:(int) sourceGroupId enabled:(BOOL) enabled; +-(BOOL) sourceGroupEnabled:(int) sourceGroupId; +-(BOOL) loadBufferFromData:(int) soundId soundData:(ALvoid*) soundData format:(ALenum) format size:(ALsizei) size freq:(ALsizei) freq; +-(BOOL) loadBuffer:(int) soundId filePath:(NSString*) filePath; +-(void) loadBuffersAsynchronously:(NSArray *) loadRequests; +-(BOOL) unloadBuffer:(int) soundId; +-(ALCcontext *) openALContext; + +/** Returns the duration of the buffer in seconds or a negative value if the buffer id is invalid */ +-(float) bufferDurationInSeconds:(int) soundId; +/** Returns the size of the buffer in bytes or a negative value if the buffer id is invalid */ +-(ALsizei) bufferSizeInBytes:(int) soundId; +/** Returns the sampling frequency of the buffer in hertz or a negative value if the buffer id is invalid */ +-(ALsizei) bufferFrequencyInHertz:(int) soundId; + +/** Used internally, never call unless you know what you are doing */ +-(void) _soundSourcePreRelease:(CDSoundSource *) soundSource; + +@end + +#pragma mark CDSoundSource +/** CDSoundSource is a wrapper around an OpenAL sound source. + It allows you to manipulate properties such as pitch, gain, pan and looping while the + sound is playing. CDSoundSource is based on the old CDSourceWrapper class but with much + added functionality. + + @since v1.0 + */ +@interface CDSoundSource : NSObject { + ALenum lastError; +@public + ALuint _sourceId; + ALuint _sourceIndex; + CDSoundEngine* _engine; + int _soundId; + float _preMuteGain; + BOOL enabled_; + BOOL mute_; +} +@property (readwrite, nonatomic) float pitch; +@property (readwrite, nonatomic) float gain; +@property (readwrite, nonatomic) float pan; +@property (readwrite, nonatomic) BOOL looping; +@property (readonly) BOOL isPlaying; +@property (readwrite, nonatomic) int soundId; +/** Returns the duration of the attached buffer in seconds or a negative value if the buffer is invalid */ +@property (readonly) float durationInSeconds; + +/** Stores the last error code that occurred. Check against AL_NO_ERROR */ +@property (readonly) ALenum lastError; +/** Do not init yourself, get an instance from the sourceForSound factory method on CDSoundEngine */ +-(id)init:(ALuint) theSourceId sourceIndex:(int) index soundEngine:(CDSoundEngine*) engine; + +@end + +#pragma mark CDAudioInterruptTargetGroup + +/** Container for objects that implement audio interrupt protocol i.e. they can be muted and enabled. + Setting mute and enabled for the group propagates to all children. + Designed to be used with your CDSoundSource objects to get them to comply with global enabled and mute settings + if that is what you want to do.*/ +@interface CDAudioInterruptTargetGroup : NSObject { + BOOL mute_; + BOOL enabled_; + NSMutableArray *children_; +} +-(void) addAudioInterruptTarget:(NSObject*) interruptibleTarget; +@end + +#pragma mark CDAsynchBufferLoader + +/** CDAsynchBufferLoader + TODO + */ +@interface CDAsynchBufferLoader : NSOperation { + NSArray *_loadRequests; + CDSoundEngine *_soundEngine; +} + +-(id) init:(NSArray *)loadRequests soundEngine:(CDSoundEngine *) theSoundEngine; + +@end + +#pragma mark CDBufferLoadRequest + +/** CDBufferLoadRequest */ +@interface CDBufferLoadRequest: NSObject +{ + NSString *filePath; + int soundId; + //id loader; +} + +@property (readonly) NSString *filePath; +@property (readonly) int soundId; + +- (id)init:(int) theSoundId filePath:(const NSString *) theFilePath; +@end + +/** Interpolation type */ +typedef enum { + kIT_Linear, //!Straight linear interpolation fade + kIT_SCurve, //!S curved interpolation + kIT_Exponential //!Exponential interpolation +} tCDInterpolationType; + +#pragma mark CDFloatInterpolator +@interface CDFloatInterpolator: NSObject +{ + float start; + float end; + float lastValue; + tCDInterpolationType interpolationType; +} +@property (readwrite, nonatomic) float start; +@property (readwrite, nonatomic) float end; +@property (readwrite, nonatomic) tCDInterpolationType interpolationType; + +/** Return a value between min and max based on t which represents fractional progress where 0 is the start + and 1 is the end */ +-(float) interpolate:(float) t; +-(id) init:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal; + +@end + +#pragma mark CDPropertyModifier + +/** Base class for classes that modify properties such as pitch, pan and gain */ +@interface CDPropertyModifier: NSObject +{ + CDFloatInterpolator *interpolator; + float startValue; + float endValue; + id target; + BOOL stopTargetWhenComplete; + +} +@property (readwrite, nonatomic) BOOL stopTargetWhenComplete; +@property (readwrite, nonatomic) float startValue; +@property (readwrite, nonatomic) float endValue; +@property (readwrite, nonatomic) tCDInterpolationType interpolationType; + +-(id) init:(id) theTarget interpolationType:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal; +/** Set to a fractional value between 0 and 1 where 0 equals the start and 1 equals the end*/ +-(void) modify:(float) t; + +-(void) _setTargetProperty:(float) newVal; +-(float) _getTargetProperty; +-(void) _stopTarget; +-(Class) _allowableType; + +@end + +#pragma mark CDSoundSourceFader + +/** Fader for CDSoundSource objects */ +@interface CDSoundSourceFader : CDPropertyModifier{} +@end + +#pragma mark CDSoundSourcePanner + +/** Panner for CDSoundSource objects */ +@interface CDSoundSourcePanner : CDPropertyModifier{} +@end + +#pragma mark CDSoundSourcePitchBender + +/** Pitch bender for CDSoundSource objects */ +@interface CDSoundSourcePitchBender : CDPropertyModifier{} +@end + +#pragma mark CDSoundEngineFader + +/** Fader for CDSoundEngine objects */ +@interface CDSoundEngineFader : CDPropertyModifier{} +@end + + + + diff --git a/libs/CocosDenshion/CocosDenshion.m b/libs/CocosDenshion/CocosDenshion.m new file mode 100644 index 0000000..8d94116 --- /dev/null +++ b/libs/CocosDenshion/CocosDenshion.m @@ -0,0 +1,1598 @@ +/* + Copyright (c) 2010 Steve Oldmeadow + + 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. + + $Id$ + */ + +#import "CocosDenshion.h" + +typedef ALvoid AL_APIENTRY (*alBufferDataStaticProcPtr) (const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq); +ALvoid alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq) +{ + static alBufferDataStaticProcPtr proc = NULL; + + if (proc == NULL) { + proc = (alBufferDataStaticProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alBufferDataStatic"); + } + + if (proc) + proc(bid, format, data, size, freq); + + return; +} + +typedef ALvoid AL_APIENTRY (*alcMacOSXMixerOutputRateProcPtr) (const ALdouble value); +ALvoid alcMacOSXMixerOutputRateProc(const ALdouble value) +{ + static alcMacOSXMixerOutputRateProcPtr proc = NULL; + + if (proc == NULL) { + proc = (alcMacOSXMixerOutputRateProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alcMacOSXMixerOutputRate"); + } + + if (proc) + proc(value); + + return; +} + +NSString * const kCDN_BadAlContext = @"kCDN_BadAlContext"; +NSString * const kCDN_AsynchLoadComplete = @"kCDN_AsynchLoadComplete"; +float const kCD_PitchDefault = 1.0f; +float const kCD_PitchLowerOneOctave = 0.5f; +float const kCD_PitchHigherOneOctave = 2.0f; +float const kCD_PanDefault = 0.0f; +float const kCD_PanFullLeft = -1.0f; +float const kCD_PanFullRight = 1.0f; +float const kCD_GainDefault = 1.0f; + +@interface CDSoundEngine (PrivateMethods) +-(BOOL) _initOpenAL; +-(void) _testGetGain; +-(void) _dumpSourceGroupsInfo; +-(void) _getSourceIndexForSourceGroup; +-(void) _freeSourceGroups; +-(BOOL) _setUpSourceGroups:(int[]) definitions total:(NSUInteger) total; +@end + +#pragma mark - +#pragma mark CDUtilities + +@implementation CDUtilities + ++(NSString*) fullPathFromRelativePath:(NSString*) relPath +{ + // do not convert an absolute path (starting with '/') + if(([relPath length] > 0) && ([relPath characterAtIndex:0] == '/')) + { + return relPath; + } + + NSMutableArray *imagePathComponents = [NSMutableArray arrayWithArray:[relPath pathComponents]]; + NSString *file = [imagePathComponents lastObject]; + + [imagePathComponents removeLastObject]; + NSString *imageDirectory = [NSString pathWithComponents:imagePathComponents]; + + NSString *fullpath = [[NSBundle mainBundle] pathForResource:file ofType:nil inDirectory:imageDirectory]; + if (fullpath == nil) + fullpath = relPath; + + return fullpath; +} + +@end + +#pragma mark - +#pragma mark CDSoundEngine + +@implementation CDSoundEngine + +static Float32 _mixerSampleRate; +static BOOL _mixerRateSet = NO; + +@synthesize lastErrorCode = lastErrorCode_; +@synthesize functioning = functioning_; +@synthesize asynchLoadProgress = asynchLoadProgress_; +@synthesize getGainWorks = getGainWorks_; +@synthesize sourceTotal = sourceTotal_; + ++ (void) setMixerSampleRate:(Float32) sampleRate { + _mixerRateSet = YES; + _mixerSampleRate = sampleRate; +} + +- (void) _testGetGain { + float testValue = 0.7f; + ALuint testSourceId = _sources[0].sourceId; + alSourcef(testSourceId, AL_GAIN, 0.0f);//Start from know value + alSourcef(testSourceId, AL_GAIN, testValue); + ALfloat gainVal; + alGetSourcef(testSourceId, AL_GAIN, &gainVal); + getGainWorks_ = (gainVal == testValue); +} + +//Generate sources one at a time until we fail +-(void) _generateSources { + + _sources = (sourceInfo*)malloc( sizeof(_sources[0]) * CD_SOURCE_LIMIT); + BOOL hasFailed = NO; + sourceTotal_ = 0; + alGetError();//Clear error + while (!hasFailed && sourceTotal_ < CD_SOURCE_LIMIT) { + alGenSources(1, &(_sources[sourceTotal_].sourceId)); + if (alGetError() == AL_NO_ERROR) { + //Now try attaching source to null buffer + alSourcei(_sources[sourceTotal_].sourceId, AL_BUFFER, 0); + if (alGetError() == AL_NO_ERROR) { + _sources[sourceTotal_].usable = true; + sourceTotal_++; + } else { + hasFailed = YES; + } + } else { + _sources[sourceTotal_].usable = false; + hasFailed = YES; + } + } + //Mark the rest of the sources as not usable + for (int i=sourceTotal_; i < CD_SOURCE_LIMIT; i++) { + _sources[i].usable = false; + } +} + +-(void) _generateBuffers:(int) startIndex endIndex:(int) endIndex { + if (_buffers) { + alGetError(); + for (int i=startIndex; i <= endIndex; i++) { + alGenBuffers(1, &_buffers[i].bufferId); + _buffers[i].bufferData = NULL; + if (alGetError() == AL_NO_ERROR) { + _buffers[i].bufferState = CD_BS_EMPTY; + } else { + _buffers[i].bufferState = CD_BS_FAILED; + CDLOG(@"Denshion::CDSoundEngine - buffer creation failed %i",i); + } + } + } +} + +/** + * Internal method called during init + */ +- (BOOL) _initOpenAL +{ + //ALenum error; + context = NULL; + ALCdevice *newDevice = NULL; + + //Set the mixer rate for the audio mixer + if (!_mixerRateSet) { + _mixerSampleRate = CD_SAMPLE_RATE_DEFAULT; + } + alcMacOSXMixerOutputRateProc(_mixerSampleRate); + CDLOGINFO(@"Denshion::CDSoundEngine - mixer output rate set to %0.2f",_mixerSampleRate); + + // Create a new OpenAL Device + // Pass NULL to specify the system's default output device + newDevice = alcOpenDevice(NULL); + if (newDevice != NULL) + { + // Create a new OpenAL Context + // The new context will render to the OpenAL Device just created + context = alcCreateContext(newDevice, 0); + if (context != NULL) + { + // Make the new context the Current OpenAL Context + alcMakeContextCurrent(context); + + // Create some OpenAL Buffer Objects + [self _generateBuffers:0 endIndex:bufferTotal-1]; + + // Create some OpenAL Source Objects + [self _generateSources]; + + } + } else { + return FALSE;//No device + } + alGetError();//Clear error + return TRUE; +} + +- (void) dealloc { + + ALCcontext *currentContext = NULL; + ALCdevice *device = NULL; + + [self stopAllSounds]; + + CDLOGINFO(@"Denshion::CDSoundEngine - Deallocing sound engine."); + [self _freeSourceGroups]; + + // Delete the Sources + CDLOGINFO(@"Denshion::CDSoundEngine - deleting sources."); + for (int i=0; i < sourceTotal_; i++) { + alSourcei(_sources[i].sourceId, AL_BUFFER, 0);//Detach from current buffer + alDeleteSources(1, &(_sources[i].sourceId)); + if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) { + CDLOG(@"Denshion::CDSoundEngine - Error deleting source! %x\n", lastErrorCode_); + } + } + + // Delete the Buffers + CDLOGINFO(@"Denshion::CDSoundEngine - deleting buffers."); + for (int i=0; i < bufferTotal; i++) { + alDeleteBuffers(1, &_buffers[i].bufferId); +#ifdef CD_USE_STATIC_BUFFERS + if (_buffers[i].bufferData) { + free(_buffers[i].bufferData); + } +#endif + } + CDLOGINFO(@"Denshion::CDSoundEngine - free buffers."); + free(_buffers); + currentContext = alcGetCurrentContext(); + //Get device for active context + device = alcGetContextsDevice(currentContext); + //Release context + CDLOGINFO(@"Denshion::CDSoundEngine - destroy context."); + alcDestroyContext(currentContext); + //Close device + CDLOGINFO(@"Denshion::CDSoundEngine - close device."); + alcCloseDevice(device); + CDLOGINFO(@"Denshion::CDSoundEngine - free sources."); + free(_sources); + + //Release mutexes + [_mutexBufferLoad release]; + + [super dealloc]; +} + +-(NSUInteger) sourceGroupTotal { + return _sourceGroupTotal; +} + +-(void) _freeSourceGroups +{ + CDLOGINFO(@"Denshion::CDSoundEngine freeing source groups"); + if(_sourceGroups) { + for (int i=0; i < _sourceGroupTotal; i++) { + if (_sourceGroups[i].sourceStatuses) { + free(_sourceGroups[i].sourceStatuses); + CDLOGINFO(@"Denshion::CDSoundEngine freed source statuses %i",i); + } + } + free(_sourceGroups); + } +} + +-(BOOL) _redefineSourceGroups:(int[]) definitions total:(NSUInteger) total +{ + if (_sourceGroups) { + //Stop all sounds + [self stopAllSounds]; + //Need to free source groups + [self _freeSourceGroups]; + } + return [self _setUpSourceGroups:definitions total:total]; +} + +-(BOOL) _setUpSourceGroups:(int[]) definitions total:(NSUInteger) total +{ + _sourceGroups = (sourceGroup *)malloc( sizeof(_sourceGroups[0]) * total); + if(!_sourceGroups) { + CDLOG(@"Denshion::CDSoundEngine - source groups memory allocation failed"); + return NO; + } + + _sourceGroupTotal = total; + int sourceCount = 0; + for (int i=0; i < _sourceGroupTotal; i++) { + + _sourceGroups[i].startIndex = 0; + _sourceGroups[i].currentIndex = _sourceGroups[i].startIndex; + _sourceGroups[i].enabled = false; + _sourceGroups[i].nonInterruptible = false; + _sourceGroups[i].totalSources = definitions[i]; + _sourceGroups[i].sourceStatuses = malloc(sizeof(_sourceGroups[i].sourceStatuses[0]) * _sourceGroups[i].totalSources); + if (_sourceGroups[i].sourceStatuses) { + for (int j=0; j < _sourceGroups[i].totalSources; j++) { + //First bit is used to indicate whether source is locked, index is shifted back 1 bit + _sourceGroups[i].sourceStatuses[j] = (sourceCount + j) << 1; + } + } + sourceCount += definitions[i]; + } + return YES; +} + +-(void) defineSourceGroups:(int[]) sourceGroupDefinitions total:(NSUInteger) total { + [self _redefineSourceGroups:sourceGroupDefinitions total:total]; +} + +-(void) defineSourceGroups:(NSArray*) sourceGroupDefinitions { + CDLOGINFO(@"Denshion::CDSoundEngine - source groups defined by NSArray."); + NSUInteger totalDefs = [sourceGroupDefinitions count]; + int* defs = (int *)malloc( sizeof(int) * totalDefs); + int currentIndex = 0; + for (id currentDef in sourceGroupDefinitions) { + if ([currentDef isKindOfClass:[NSNumber class]]) { + defs[currentIndex] = (int)[(NSNumber*)currentDef integerValue]; + CDLOGINFO(@"Denshion::CDSoundEngine - found definition %i.",defs[currentIndex]); + } else { + CDLOG(@"Denshion::CDSoundEngine - warning, did not understand source definition."); + defs[currentIndex] = 0; + } + currentIndex++; + } + [self _redefineSourceGroups:defs total:totalDefs]; + free(defs); +} + +- (id)init +{ + if ((self = [super init])) { + + //Create mutexes + _mutexBufferLoad = [[NSObject alloc] init]; + + asynchLoadProgress_ = 0.0f; + + bufferTotal = CD_BUFFERS_START; + _buffers = (bufferInfo *)malloc( sizeof(_buffers[0]) * bufferTotal); + + // Initialize our OpenAL environment + if ([self _initOpenAL]) { + //Set up the default source group - a single group that contains all the sources + int sourceDefs[1]; + sourceDefs[0] = self.sourceTotal; + [self _setUpSourceGroups:sourceDefs total:1]; + + functioning_ = YES; + //Synchronize premute gain + _preMuteGain = self.masterGain; + mute_ = NO; + enabled_ = YES; + //Test whether get gain works for sources + [self _testGetGain]; + } else { + //Something went wrong with OpenAL + functioning_ = NO; + } + } + + return self; +} + +/** + * Delete the buffer identified by soundId + * @return true if buffer deleted successfully, otherwise false + */ +- (BOOL) unloadBuffer:(int) soundId +{ + //Ensure soundId is within array bounds otherwise memory corruption will occur + if (soundId < 0 || soundId >= bufferTotal) { + CDLOG(@"Denshion::CDSoundEngine - soundId is outside array bounds, maybe you need to increase CD_MAX_BUFFERS"); + return FALSE; + } + + //Before a buffer can be deleted any sources that are attached to it must be stopped + for (int i=0; i < sourceTotal_; i++) { + //Note: tried getting the AL_BUFFER attribute of the source instead but doesn't + //appear to work on a device - just returned zero. + if (_buffers[soundId].bufferId == _sources[i].attachedBufferId) { + + CDLOG(@"Denshion::CDSoundEngine - Found attached source %i %i %i",i,_buffers[soundId].bufferId,_sources[i].sourceId); +#ifdef CD_USE_STATIC_BUFFERS + //When using static buffers a crash may occur if a source is playing with a buffer that is about + //to be deleted even though we stop the source and successfully delete the buffer. Crash is confirmed + //on 2.2.1 and 3.1.2, however, it will only occur if a source is used rapidly after having its prior + //data deleted. To avoid any possibility of the crash we wait for the source to finish playing. + ALint state; + + alGetSourcei(_sources[i].sourceId, AL_SOURCE_STATE, &state); + + if (state == AL_PLAYING) { + CDLOG(@"Denshion::CDSoundEngine - waiting for source to complete playing before removing buffer data"); + alSourcei(_sources[i].sourceId, AL_LOOPING, FALSE);//Turn off looping otherwise loops will never end + while (state == AL_PLAYING) { + alGetSourcei(_sources[i].sourceId, AL_SOURCE_STATE, &state); + usleep(10000); + } + } +#endif + //Stop source and detach + alSourceStop(_sources[i].sourceId); + if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) { + CDLOG(@"Denshion::CDSoundEngine - error stopping source: %x\n", lastErrorCode_); + } + + alSourcei(_sources[i].sourceId, AL_BUFFER, 0);//Attach to "NULL" buffer to detach + if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) { + CDLOG(@"Denshion::CDSoundEngine - error detaching buffer: %x\n", lastErrorCode_); + } else { + //Record that source is now attached to nothing + _sources[i].attachedBufferId = 0; + } + } + } + + alDeleteBuffers(1, &_buffers[soundId].bufferId); + if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) { + CDLOG(@"Denshion::CDSoundEngine - error deleting buffer: %x\n", lastErrorCode_); + _buffers[soundId].bufferState = CD_BS_FAILED; + return FALSE; + } else { +#ifdef CD_USE_STATIC_BUFFERS + //Free previous data, if alDeleteBuffer has returned without error then no + if (_buffers[soundId].bufferData) { + CDLOGINFO(@"Denshion::CDSoundEngine - freeing static data for soundId %i @ %i",soundId,_buffers[soundId].bufferData); + free(_buffers[soundId].bufferData);//Free the old data + _buffers[soundId].bufferData = NULL; + } +#endif + } + + alGenBuffers(1, &_buffers[soundId].bufferId); + if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) { + CDLOG(@"Denshion::CDSoundEngine - error regenerating buffer: %x\n", lastErrorCode_); + _buffers[soundId].bufferState = CD_BS_FAILED; + return FALSE; + } else { + //We now have an empty buffer + _buffers[soundId].bufferState = CD_BS_EMPTY; + CDLOGINFO(@"Denshion::CDSoundEngine - buffer %i successfully unloaded\n",soundId); + return TRUE; + } +} + +/** + * Load buffers asynchronously + * Check asynchLoadProgress for progress. asynchLoadProgress represents fraction of completion. When it equals 1.0 loading + * is complete. NB: asynchLoadProgress is simply based on the number of load requests, it does not take into account + * file sizes. + * @param An array of CDBufferLoadRequest objects + */ +- (void) loadBuffersAsynchronously:(NSArray *) loadRequests { + @synchronized(self) { + asynchLoadProgress_ = 0.0f; + CDAsynchBufferLoader *loaderOp = [[[CDAsynchBufferLoader alloc] init:loadRequests soundEngine:self] autorelease]; + NSOperationQueue *opQ = [[[NSOperationQueue alloc] init] autorelease]; + [opQ addOperation:loaderOp]; + } +} + +-(BOOL) _resizeBuffers:(int) increment { + + void * tmpBufferInfos = realloc( _buffers, sizeof(_buffers[0]) * (bufferTotal + increment) ); + + if(!tmpBufferInfos) { + free(tmpBufferInfos); + return NO; + } else { + _buffers = tmpBufferInfos; + int oldBufferTotal = bufferTotal; + bufferTotal = bufferTotal + increment; + [self _generateBuffers:oldBufferTotal endIndex:bufferTotal-1]; + return YES; + } +} + +-(BOOL) loadBufferFromData:(int) soundId soundData:(ALvoid*) soundData format:(ALenum) format size:(ALsizei) size freq:(ALsizei) freq { + + @synchronized(_mutexBufferLoad) { + + if (!functioning_) { + //OpenAL initialisation has previously failed + CDLOG(@"Denshion::CDSoundEngine - Loading buffer failed because sound engine state != functioning"); + return FALSE; + } + + //Ensure soundId is within array bounds otherwise memory corruption will occur + if (soundId < 0) { + CDLOG(@"Denshion::CDSoundEngine - soundId is negative"); + return FALSE; + } + + if (soundId >= bufferTotal) { + //Need to resize the buffers + int requiredIncrement = CD_BUFFERS_INCREMENT; + while (bufferTotal + requiredIncrement < soundId) { + requiredIncrement += CD_BUFFERS_INCREMENT; + } + CDLOGINFO(@"Denshion::CDSoundEngine - attempting to resize buffers by %i for sound %i",requiredIncrement,soundId); + if (![self _resizeBuffers:requiredIncrement]) { + CDLOG(@"Denshion::CDSoundEngine - buffer resize failed"); + return FALSE; + } + } + + if (soundData) + { + if (_buffers[soundId].bufferState != CD_BS_EMPTY) { + CDLOGINFO(@"Denshion::CDSoundEngine - non empty buffer, regenerating"); + if (![self unloadBuffer:soundId]) { + //Deletion of buffer failed, delete buffer routine has set buffer state and lastErrorCode + return NO; + } + } + +#ifdef CD_DEBUG + //Check that sample rate matches mixer rate and warn if they do not + if (freq != (int)_mixerSampleRate) { + CDLOGINFO(@"Denshion::CDSoundEngine - WARNING sample rate does not match mixer sample rate performance may not be optimal."); + } +#endif + +#ifdef CD_USE_STATIC_BUFFERS + alBufferDataStaticProc(_buffers[soundId].bufferId, format, soundData, size, freq); + _buffers[soundId].bufferData = data;//Save the pointer to the new data +#else + alBufferData(_buffers[soundId].bufferId, format, soundData, size, freq); +#endif + if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) { + CDLOG(@"Denshion::CDSoundEngine - error attaching audio to buffer: %x", lastErrorCode_); + _buffers[soundId].bufferState = CD_BS_FAILED; + return FALSE; + } + } else { + CDLOG(@"Denshion::CDSoundEngine Buffer data is null!"); + _buffers[soundId].bufferState = CD_BS_FAILED; + return FALSE; + } + + _buffers[soundId].format = format; + _buffers[soundId].sizeInBytes = size; + _buffers[soundId].frequencyInHertz = freq; + _buffers[soundId].bufferState = CD_BS_LOADED; + CDLOGINFO(@"Denshion::CDSoundEngine Buffer %i loaded format:%i freq:%i size:%i",soundId,format,freq,size); + return TRUE; + }//end mutex +} + +/** + * Load sound data for later play back. + * @return TRUE if buffer loaded okay for play back otherwise false + */ +- (BOOL) loadBuffer:(int) soundId filePath:(NSString*) filePath +{ + + ALvoid* data; + ALenum format; + ALsizei size; + ALsizei freq; + + CDLOGINFO(@"Denshion::CDSoundEngine - Loading openAL buffer %i %@", soundId, filePath); + + CFURLRef fileURL = nil; + NSString *path = [CDUtilities fullPathFromRelativePath:filePath]; + if (path) { + fileURL = (CFURLRef)[[NSURL fileURLWithPath:path] retain]; + } + + if (fileURL) + { + data = CDGetOpenALAudioData(fileURL, &size, &format, &freq); + CFRelease(fileURL); + BOOL result = [self loadBufferFromData:soundId soundData:data format:format size:size freq:freq]; +#ifndef CD_USE_STATIC_BUFFERS + free(data);//Data can be freed here because alBufferData performs a memcpy +#endif + return result; + } else { + CDLOG(@"Denshion::CDSoundEngine Could not find file!\n"); + //Don't change buffer state here as it will be the same as before method was called + return FALSE; + } +} + +-(BOOL) validateBufferId:(int) soundId { + if (soundId < 0 || soundId >= bufferTotal) { + CDLOGINFO(@"Denshion::CDSoundEngine - validateBufferId buffer outside range %i",soundId); + return NO; + } else if (_buffers[soundId].bufferState != CD_BS_LOADED) { + CDLOGINFO(@"Denshion::CDSoundEngine - validateBufferId invalide buffer state %i",soundId); + return NO; + } else { + return YES; + } +} + +-(float) bufferDurationInSeconds:(int) soundId { + if ([self validateBufferId:soundId]) { + float factor = 0.0f; + switch (_buffers[soundId].format) { + case AL_FORMAT_MONO8: + factor = 1.0f; + break; + case AL_FORMAT_MONO16: + factor = 0.5f; + break; + case AL_FORMAT_STEREO8: + factor = 0.5f; + break; + case AL_FORMAT_STEREO16: + factor = 0.25f; + break; + } + return (float)_buffers[soundId].sizeInBytes/(float)_buffers[soundId].frequencyInHertz * factor; + } else { + return -1.0f; + } +} + +-(ALsizei) bufferSizeInBytes:(int) soundId { + if ([self validateBufferId:soundId]) { + return _buffers[soundId].sizeInBytes; + } else { + return -1.0f; + } +} + +-(ALsizei) bufferFrequencyInHertz:(int) soundId { + if ([self validateBufferId:soundId]) { + return _buffers[soundId].frequencyInHertz; + } else { + return -1.0f; + } +} + +- (ALfloat) masterGain { + if (mute_) { + //When mute the real gain will always be 0 therefore return the preMuteGain value + return _preMuteGain; + } else { + ALfloat gain; + alGetListenerf(AL_GAIN, &gain); + return gain; + } +} + +/** + * Overall gain setting multiplier. e.g 0.5 is half the gain. + */ +- (void) setMasterGain:(ALfloat) newGainValue { + if (mute_) { + _preMuteGain = newGainValue; + } else { + alListenerf(AL_GAIN, newGainValue); + } +} + +#pragma mark CDSoundEngine AudioInterrupt protocol +- (BOOL) mute { + return mute_; +} + +/** + * Setting mute silences all sounds but playing sounds continue to advance playback + */ +- (void) setMute:(BOOL) newMuteValue { + + if (newMuteValue == mute_) { + return; + } + + mute_ = newMuteValue; + if (mute_) { + //Remember what the gain was + _preMuteGain = self.masterGain; + //Set gain to 0 - do not use the property as this will adjust preMuteGain when muted + alListenerf(AL_GAIN, 0.0f); + } else { + //Restore gain to what it was before being muted + self.masterGain = _preMuteGain; + } +} + +- (BOOL) enabled { + return enabled_; +} + +- (void) setEnabled:(BOOL)enabledValue +{ + if (enabled_ == enabledValue) { + return; + } + enabled_ = enabledValue; + if (enabled_ == NO) { + [self stopAllSounds]; + } +} + +-(void) _lockSource:(int) sourceIndex lock:(BOOL) lock { + BOOL found = NO; + for (int i=0; i < _sourceGroupTotal && !found; i++) { + if (_sourceGroups[i].sourceStatuses) { + for (int j=0; j < _sourceGroups[i].totalSources && !found; j++) { + //First bit is used to indicate whether source is locked, index is shifted back 1 bit + if((_sourceGroups[i].sourceStatuses[j] >> 1)==sourceIndex) { + if (lock) { + //Set first bit to lock this source + _sourceGroups[i].sourceStatuses[j] |= 1; + } else { + //Unset first bit to unlock this source + _sourceGroups[i].sourceStatuses[j] &= ~1; + } + found = YES; + } + } + } + } +} + +-(int) _getSourceIndexForSourceGroup:(int)sourceGroupId +{ + //Ensure source group id is valid to prevent memory corruption + if (sourceGroupId < 0 || sourceGroupId >= _sourceGroupTotal) { + CDLOG(@"Denshion::CDSoundEngine invalid source group id %i",sourceGroupId); + return CD_NO_SOURCE; + } + + int sourceIndex = -1;//Using -1 to indicate no source found + BOOL complete = NO; + ALint sourceState = 0; + sourceGroup *thisSourceGroup = &_sourceGroups[sourceGroupId]; + thisSourceGroup->currentIndex = thisSourceGroup->startIndex; + while (!complete) { + //Iterate over sources looking for one that is not locked, first bit indicates if source is locked + if ((thisSourceGroup->sourceStatuses[thisSourceGroup->currentIndex] & 1) == 0) { + //This source is not locked + sourceIndex = thisSourceGroup->sourceStatuses[thisSourceGroup->currentIndex] >> 1;//shift back to get the index + if (thisSourceGroup->nonInterruptible) { + //Check if this source is playing, if so it can't be interrupted + alGetSourcei(_sources[sourceIndex].sourceId, AL_SOURCE_STATE, &sourceState); + if (sourceState != AL_PLAYING) { + //complete = YES; + //Set start index so next search starts at the next position + thisSourceGroup->startIndex = thisSourceGroup->currentIndex + 1; + break; + } else { + sourceIndex = -1;//The source index was no good because the source was playing + } + } else { + //complete = YES; + //Set start index so next search starts at the next position + thisSourceGroup->startIndex = thisSourceGroup->currentIndex + 1; + break; + } + } + thisSourceGroup->currentIndex++; + if (thisSourceGroup->currentIndex >= thisSourceGroup->totalSources) { + //Reset to the beginning + thisSourceGroup->currentIndex = 0; + } + if (thisSourceGroup->currentIndex == thisSourceGroup->startIndex) { + //We have looped around and got back to the start + complete = YES; + } + } + + //Reset start index to beginning if beyond bounds + if (thisSourceGroup->startIndex >= thisSourceGroup->totalSources) { + thisSourceGroup->startIndex = 0; + } + + if (sourceIndex >= 0) { + return sourceIndex; + } else { + return CD_NO_SOURCE; + } + +} + +/** + * Play a sound. + * @param soundId the id of the sound to play (buffer id). + * @param SourceGroupId the source group that will be used to play the sound. + * @param pitch pitch multiplier. e.g 1.0 is unaltered, 0.5 is 1 octave lower. + * @param pan stereo position. -1 is fully left, 0 is centre and 1 is fully right. + * @param gain gain multiplier. e.g. 1.0 is unaltered, 0.5 is half the gain + * @param loop should the sound be looped or one shot. + * @return the id of the source being used to play the sound or CD_MUTE if the sound engine is muted or non functioning + * or CD_NO_SOURCE if a problem occurs setting up the source + * + */ +- (ALuint)playSound:(int) soundId sourceGroupId:(int)sourceGroupId pitch:(float) pitch pan:(float) pan gain:(float) gain loop:(BOOL) loop { + +#ifdef CD_DEBUG + //Sanity check parameters - only in DEBUG + NSAssert(soundId >= 0, @"soundId can not be negative"); + NSAssert(soundId < bufferTotal, @"soundId exceeds limit"); + NSAssert(sourceGroupId >= 0, @"sourceGroupId can not be negative"); + NSAssert(sourceGroupId < _sourceGroupTotal, @"sourceGroupId exceeds limit"); + NSAssert(pitch > 0, @"pitch must be greater than zero"); + NSAssert(pan >= -1 && pan <= 1, @"pan must be between -1 and 1"); + NSAssert(gain >= 0, @"gain can not be negative"); +#endif + //If mute or initialisation has failed or buffer is not loaded then do nothing + if (!enabled_ || !functioning_ || _buffers[soundId].bufferState != CD_BS_LOADED || _sourceGroups[sourceGroupId].enabled) { +#ifdef CD_DEBUG + if (!functioning_) { + CDLOGINFO(@"Denshion::CDSoundEngine - sound playback aborted because sound engine is not functioning"); + } else if (_buffers[soundId].bufferState != CD_BS_LOADED) { + CDLOGINFO(@"Denshion::CDSoundEngine - sound playback aborted because buffer %i is not loaded", soundId); + } +#endif + return CD_MUTE; + } + + int sourceIndex = [self _getSourceIndexForSourceGroup:sourceGroupId];//This method ensures sourceIndex is valid + + if (sourceIndex != CD_NO_SOURCE) { + ALint state; + ALuint source = _sources[sourceIndex].sourceId; + ALuint buffer = _buffers[soundId].bufferId; + alGetError();//Clear the error code + alGetSourcei(source, AL_SOURCE_STATE, &state); + if (state == AL_PLAYING) { + alSourceStop(source); + } + alSourcei(source, AL_BUFFER, buffer);//Attach to sound + alSourcef(source, AL_PITCH, pitch);//Set pitch + alSourcei(source, AL_LOOPING, loop);//Set looping + alSourcef(source, AL_GAIN, gain);//Set gain/volume + float sourcePosAL[] = {pan, 0.0f, 0.0f};//Set position - just using left and right panning + alSourcefv(source, AL_POSITION, sourcePosAL); + alGetError();//Clear the error code + alSourcePlay(source); + if((lastErrorCode_ = alGetError()) == AL_NO_ERROR) { + //Everything was okay + _sources[sourceIndex].attachedBufferId = buffer; + return source; + } else { + if (alcGetCurrentContext() == NULL) { + CDLOGINFO(@"Denshion::CDSoundEngine - posting bad OpenAL context message"); + [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_BadAlContext object:nil]; + } + return CD_NO_SOURCE; + } + } else { + return CD_NO_SOURCE; + } +} + +-(BOOL) _soundSourceAttachToBuffer:(CDSoundSource*) soundSource soundId:(int) soundId { + //Attach the source to the buffer + ALint state; + ALuint source = soundSource->_sourceId; + ALuint buffer = _buffers[soundId].bufferId; + alGetSourcei(source, AL_SOURCE_STATE, &state); + if (state == AL_PLAYING) { + alSourceStop(source); + } + alGetError();//Clear the error code + alSourcei(source, AL_BUFFER, buffer);//Attach to sound data + if((lastErrorCode_ = alGetError()) == AL_NO_ERROR) { + _sources[soundSource->_sourceIndex].attachedBufferId = buffer; + //_sourceBufferAttachments[soundSource->_sourceIndex] = buffer;//Keep track of which + soundSource->_soundId = soundId; + return YES; + } else { + return NO; + } +} + +/** + * Get a sound source for the specified sound in the specified source group + */ +-(CDSoundSource *) soundSourceForSound:(int) soundId sourceGroupId:(int) sourceGroupId +{ + if (!functioning_) { + return nil; + } + //Check if a source is available + int sourceIndex = [self _getSourceIndexForSourceGroup:sourceGroupId]; + if (sourceIndex != CD_NO_SOURCE) { + CDSoundSource *result = [[CDSoundSource alloc] init:_sources[sourceIndex].sourceId sourceIndex:sourceIndex soundEngine:self]; + [self _lockSource:sourceIndex lock:YES]; + //Try to attach to the buffer + if ([self _soundSourceAttachToBuffer:result soundId:soundId]) { + //Set to a known state + result.pitch = 1.0f; + result.pan = 0.0f; + result.gain = 1.0f; + result.looping = NO; + return [result autorelease]; + } else { + //Release the sound source we just created, this will also unlock the source + [result release]; + return nil; + } + } else { + //No available source within that source group + return nil; + } +} + +-(void) _soundSourcePreRelease:(CDSoundSource *) soundSource { + CDLOGINFO(@"Denshion::CDSoundEngine _soundSourcePreRelease %i",soundSource->_sourceIndex); + //Unlock the sound source's source + [self _lockSource:soundSource->_sourceIndex lock:NO]; +} + +/** + * Stop all sounds playing within a source group + */ +- (void) stopSourceGroup:(int) sourceGroupId { + + if (!functioning_ || sourceGroupId >= _sourceGroupTotal || sourceGroupId < 0) { + return; + } + int sourceCount = _sourceGroups[sourceGroupId].totalSources; + for (int i=0; i < sourceCount; i++) { + int sourceIndex = _sourceGroups[sourceGroupId].sourceStatuses[i] >> 1; + alSourceStop(_sources[sourceIndex].sourceId); + } + alGetError();//Clear error in case we stopped any sounds that couldn't be stopped +} + +/** + * Stop a sound playing. + * @param sourceId an OpenAL source identifier i.e. the return value of playSound + */ +- (void)stopSound:(ALuint) sourceId { + if (!functioning_) { + return; + } + alSourceStop(sourceId); + alGetError();//Clear error in case we stopped any sounds that couldn't be stopped +} + +- (void) stopAllSounds { + for (int i=0; i < sourceTotal_; i++) { + alSourceStop(_sources[i].sourceId); + } + alGetError();//Clear error in case we stopped any sounds that couldn't be stopped +} + +/** + * Set a source group as non interruptible. Default is that source groups are interruptible. + * Non interruptible means that if a request to play a sound is made for a source group and there are + * no free sources available then the play request will be ignored and CD_NO_SOURCE will be returned. + */ +- (void) setSourceGroupNonInterruptible:(int) sourceGroupId isNonInterruptible:(BOOL) isNonInterruptible { + //Ensure source group id is valid to prevent memory corruption + if (sourceGroupId < 0 || sourceGroupId >= _sourceGroupTotal) { + CDLOG(@"Denshion::CDSoundEngine setSourceGroupNonInterruptible invalid source group id %i",sourceGroupId); + return; + } + + if (isNonInterruptible) { + _sourceGroups[sourceGroupId].nonInterruptible = true; + } else { + _sourceGroups[sourceGroupId].nonInterruptible = false; + } +} + +/** + * Set the mute property for a source group. If mute is turned on any sounds in that source group + * will be stopped and further sounds in that source group will play. However, turning mute off + * will not restart any sounds that were playing when mute was turned on. Also the mute setting + * for the sound engine must be taken into account. If the sound engine is mute no sounds will play + * no matter what the source group mute setting is. + */ +- (void) setSourceGroupEnabled:(int) sourceGroupId enabled:(BOOL) enabled { + //Ensure source group id is valid to prevent memory corruption + if (sourceGroupId < 0 || sourceGroupId >= _sourceGroupTotal) { + CDLOG(@"Denshion::CDSoundEngine setSourceGroupEnabled invalid source group id %i",sourceGroupId); + return; + } + + if (enabled) { + _sourceGroups[sourceGroupId].enabled = true; + [self stopSourceGroup:sourceGroupId]; + } else { + _sourceGroups[sourceGroupId].enabled = false; + } +} + +/** + * Return the mute property for the source group identified by sourceGroupId + */ +- (BOOL) sourceGroupEnabled:(int) sourceGroupId { + return _sourceGroups[sourceGroupId].enabled; +} + +-(ALCcontext *) openALContext { + return context; +} + +- (void) _dumpSourceGroupsInfo { +#ifdef CD_DEBUG + CDLOGINFO(@"-------------- source Group Info --------------"); + for (int i=0; i < _sourceGroupTotal; i++) { + CDLOGINFO(@"Group: %i start:%i total:%i",i,_sourceGroups[i].startIndex, _sourceGroups[i].totalSources); + CDLOGINFO(@"----- mute:%i nonInterruptible:%i",_sourceGroups[i].enabled, _sourceGroups[i].nonInterruptible); + CDLOGINFO(@"----- Source statuses ----"); + for (int j=0; j < _sourceGroups[i].totalSources; j++) { + CDLOGINFO(@"Source status:%i index=%i locked=%i",j,_sourceGroups[i].sourceStatuses[j] >> 1, _sourceGroups[i].sourceStatuses[j] & 1); + } + } +#endif +} + +@end + +/////////////////////////////////////////////////////////////////////////////////////// +@implementation CDSoundSource + +@synthesize lastError; + +//Macro for handling the al error code +#define CDSOUNDSOURCE_UPDATE_LAST_ERROR (lastError = alGetError()) +#define CDSOUNDSOURCE_ERROR_HANDLER ( CDSOUNDSOURCE_UPDATE_LAST_ERROR == AL_NO_ERROR) + +-(id)init:(ALuint) theSourceId sourceIndex:(int) index soundEngine:(CDSoundEngine*) engine { + if ((self = [super init])) { + _sourceId = theSourceId; + _engine = engine; + _sourceIndex = index; + enabled_ = YES; + mute_ = NO; + _preMuteGain = self.gain; + } + return self; +} + +-(void) dealloc +{ + CDLOGINFO(@"Denshion::CDSoundSource deallocated %i",self->_sourceIndex); + + //Notify sound engine we are about to release + [_engine _soundSourcePreRelease:self]; + [super dealloc]; +} + +- (void) setPitch:(float) newPitchValue { + alSourcef(_sourceId, AL_PITCH, newPitchValue); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; +} + +- (void) setGain:(float) newGainValue { + if (!mute_) { + alSourcef(_sourceId, AL_GAIN, newGainValue); + } else { + _preMuteGain = newGainValue; + } + CDSOUNDSOURCE_UPDATE_LAST_ERROR; +} + +- (void) setPan:(float) newPanValue { + float sourcePosAL[] = {newPanValue, 0.0f, 0.0f};//Set position - just using left and right panning + alSourcefv(_sourceId, AL_POSITION, sourcePosAL); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; + +} + +- (void) setLooping:(BOOL) newLoopingValue { + alSourcei(_sourceId, AL_LOOPING, newLoopingValue); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; + +} + +- (BOOL) isPlaying { + ALint state; + alGetSourcei(_sourceId, AL_SOURCE_STATE, &state); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; + return (state == AL_PLAYING); +} + +- (float) pitch { + ALfloat pitchVal; + alGetSourcef(_sourceId, AL_PITCH, &pitchVal); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; + return pitchVal; +} + +- (float) pan { + ALfloat sourcePosAL[] = {0.0f,0.0f,0.0f}; + alGetSourcefv(_sourceId, AL_POSITION, sourcePosAL); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; + return sourcePosAL[0]; +} + +- (float) gain { + if (!mute_) { + ALfloat val; + alGetSourcef(_sourceId, AL_GAIN, &val); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; + return val; + } else { + return _preMuteGain; + } +} + +- (BOOL) looping { + ALfloat val; + alGetSourcef(_sourceId, AL_LOOPING, &val); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; + return val; +} + +-(BOOL) stop { + alSourceStop(_sourceId); + return CDSOUNDSOURCE_ERROR_HANDLER; +} + +-(BOOL) play { + if (enabled_) { + alSourcePlay(_sourceId); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; + if (lastError != AL_NO_ERROR) { + if (alcGetCurrentContext() == NULL) { + CDLOGINFO(@"Denshion::CDSoundSource - posting bad OpenAL context message"); + [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_BadAlContext object:nil]; + } + return NO; + } else { + return YES; + } + } else { + return NO; + } +} + +-(BOOL) pause { + alSourcePause(_sourceId); + return CDSOUNDSOURCE_ERROR_HANDLER; +} + +-(BOOL) rewind { + alSourceRewind(_sourceId); + return CDSOUNDSOURCE_ERROR_HANDLER; +} + +-(void) setSoundId:(int) soundId { + [_engine _soundSourceAttachToBuffer:self soundId:soundId]; +} + +-(int) soundId { + return _soundId; +} + +-(float) durationInSeconds { + return [_engine bufferDurationInSeconds:_soundId]; +} + +#pragma mark CDSoundSource AudioInterrupt protocol +- (BOOL) mute { + return mute_; +} + +/** + * Setting mute silences all sounds but playing sounds continue to advance playback + */ +- (void) setMute:(BOOL) newMuteValue { + + if (newMuteValue == mute_) { + return; + } + + if (newMuteValue) { + //Remember what the gain was + _preMuteGain = self.gain; + self.gain = 0.0f; + mute_ = newMuteValue;//Make sure this is done after setting the gain property as the setter behaves differently depending on mute value + } else { + //Restore gain to what it was before being muted + mute_ = newMuteValue; + self.gain = _preMuteGain; + } +} + +- (BOOL) enabled { + return enabled_; +} + +- (void) setEnabled:(BOOL)enabledValue +{ + if (enabled_ == enabledValue) { + return; + } + enabled_ = enabledValue; + if (enabled_ == NO) { + [self stop]; + } +} + +@end + +//////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark CDAudioInterruptTargetGroup + +@implementation CDAudioInterruptTargetGroup + +-(id) init { + if ((self = [super init])) { + children_ = [[NSMutableArray alloc] initWithCapacity:32]; + enabled_ = YES; + mute_ = NO; + } + return self; +} + +-(void) addAudioInterruptTarget:(NSObject*) interruptibleTarget { + //Synchronize child with group settings; + [interruptibleTarget setMute:mute_]; + [interruptibleTarget setEnabled:enabled_]; + [children_ addObject:interruptibleTarget]; +} + +-(void) removeAudioInterruptTarget:(NSObject*) interruptibleTarget { + [children_ removeObjectIdenticalTo:interruptibleTarget]; +} + +- (BOOL) mute { + return mute_; +} + +/** + * Setting mute silences all sounds but playing sounds continue to advance playback + */ +- (void) setMute:(BOOL) newMuteValue { + + if (newMuteValue == mute_) { + return; + } + + for (NSObject* target in children_) { + [target setMute:newMuteValue]; + } +} + +- (BOOL) enabled { + return enabled_; +} + +- (void) setEnabled:(BOOL)enabledValue +{ + if (enabledValue == enabled_) { + return; + } + + for (NSObject* target in children_) { + [target setEnabled:enabledValue]; + } +} + +@end + + + +//////////////////////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark CDAsynchBufferLoader + +@implementation CDAsynchBufferLoader + +-(id) init:(NSArray *)loadRequests soundEngine:(CDSoundEngine *) theSoundEngine { + if ((self = [super init])) { + _loadRequests = loadRequests; + [_loadRequests retain]; + _soundEngine = theSoundEngine; + [_soundEngine retain]; + } + return self; +} + +-(void) main { + CDLOGINFO(@"Denshion::CDAsynchBufferLoader - loading buffers"); + [super main]; + _soundEngine.asynchLoadProgress = 0.0f; + + if ([_loadRequests count] > 0) { + float increment = 1.0f / [_loadRequests count]; + //Iterate over load request and load + for (CDBufferLoadRequest *loadRequest in _loadRequests) { + [_soundEngine loadBuffer:loadRequest.soundId filePath:loadRequest.filePath]; + _soundEngine.asynchLoadProgress += increment; + } + } + + //Completed + _soundEngine.asynchLoadProgress = 1.0f; + [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_AsynchLoadComplete object:nil]; + +} + +-(void) dealloc { + [_loadRequests release]; + [_soundEngine release]; + [super dealloc]; +} + +@end + + +/////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark CDBufferLoadRequest + +@implementation CDBufferLoadRequest + +@synthesize filePath, soundId; + +-(id) init:(int) theSoundId filePath:(const NSString *) theFilePath { + if ((self = [super init])) { + soundId = theSoundId; + filePath = [theFilePath copy];//TODO: is retain necessary or does copy set retain count + [filePath retain]; + } + return self; +} + +-(void) dealloc { + [filePath release]; + [super dealloc]; +} + +@end + +/////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark CDFloatInterpolator + +@implementation CDFloatInterpolator +@synthesize start,end,interpolationType; + +-(float) interpolate:(float) t { + + if (t < 1.0f) { + switch (interpolationType) { + case kIT_Linear: + //Linear interpolation + return ((end - start) * t) + start; + + case kIT_SCurve: + //Cubic s curve t^2 * (3 - 2t) + return ((float)(t * t * (3.0 - (2.0 * t))) * (end - start)) + start; + + case kIT_Exponential: + //Formulas taken from EaseAction + if (end > start) { + //Fade in + float logDelta = (t==0) ? 0 : powf(2, 10 * (t/1 - 1)) - 1 * 0.001f; + return ((end - start) * logDelta) + start; + } else { + //Fade Out + float logDelta = (-powf(2, -10 * t/1) + 1); + return ((end - start) * logDelta) + start; + } + default: + return 0.0f; + } + } else { + return end; + } +} + +-(id) init:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal { + if ((self = [super init])) { + start = startVal; + end = endVal; + interpolationType = type; + } + return self; +} + +@end + +/////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark CDPropertyModifier + +@implementation CDPropertyModifier + +@synthesize stopTargetWhenComplete; + +-(id) init:(id) theTarget interpolationType:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal { + if ((self = [super init])) { + if (target) { + //Release the previous target if there is one + [target release]; + } + target = theTarget; +#if CD_DEBUG + //Check target is of the required type + if (![theTarget isMemberOfClass:[self _allowableType]] ) { + CDLOG(@"Denshion::CDPropertyModifier target is not of type %@",[self _allowableType]); + NSAssert([theTarget isKindOfClass:[CDSoundEngine class]], @"CDPropertyModifier target not of required type"); + } +#endif + [target retain]; + startValue = startVal; + endValue = endVal; + if (interpolator) { + //Release previous interpolator if there is one + [interpolator release]; + } + interpolator = [[CDFloatInterpolator alloc] init:type startVal:startVal endVal:endVal]; + stopTargetWhenComplete = NO; + } + return self; +} + +-(void) dealloc { + CDLOGINFO(@"Denshion::CDPropertyModifier deallocated %@",self); + [target release]; + [interpolator release]; + [super dealloc]; +} + +-(void) modify:(float) t { + if (t < 1.0) { + [self _setTargetProperty:[interpolator interpolate:t]]; + } else { + //At the end + [self _setTargetProperty:endValue]; + if (stopTargetWhenComplete) { + [self _stopTarget]; + } + } +} + +-(float) startValue { + return startValue; +} + +-(void) setStartValue:(float) startVal +{ + startValue = startVal; + interpolator.start = startVal; +} + +-(float) endValue { + return startValue; +} + +-(void) setEndValue:(float) endVal +{ + endValue = endVal; + interpolator.end = endVal; +} + +-(tCDInterpolationType) interpolationType { + return interpolator.interpolationType; +} + +-(void) setInterpolationType:(tCDInterpolationType) interpolationType { + interpolator.interpolationType = interpolationType; +} + +-(void) _setTargetProperty:(float) newVal { + +} + +-(float) _getTargetProperty { + return 0.0f; +} + +-(void) _stopTarget { + +} + +-(Class) _allowableType { + return [NSObject class]; +} +@end + +/////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark CDSoundSourceFader + +@implementation CDSoundSourceFader + +-(void) _setTargetProperty:(float) newVal { + ((CDSoundSource*)target).gain = newVal; +} + +-(float) _getTargetProperty { + return ((CDSoundSource*)target).gain; +} + +-(void) _stopTarget { + [((CDSoundSource*)target) stop]; +} + +-(Class) _allowableType { + return [CDSoundSource class]; +} + +@end + +/////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark CDSoundSourcePanner + +@implementation CDSoundSourcePanner + +-(void) _setTargetProperty:(float) newVal { + ((CDSoundSource*)target).pan = newVal; +} + +-(float) _getTargetProperty { + return ((CDSoundSource*)target).pan; +} + +-(void) _stopTarget { + [((CDSoundSource*)target) stop]; +} + +-(Class) _allowableType { + return [CDSoundSource class]; +} + +@end + +/////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark CDSoundSourcePitchBender + +@implementation CDSoundSourcePitchBender + +-(void) _setTargetProperty:(float) newVal { + ((CDSoundSource*)target).pitch = newVal; +} + +-(float) _getTargetProperty { + return ((CDSoundSource*)target).pitch; +} + +-(void) _stopTarget { + [((CDSoundSource*)target) stop]; +} + +-(Class) _allowableType { + return [CDSoundSource class]; +} + +@end + +/////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark CDSoundEngineFader + +@implementation CDSoundEngineFader + +-(void) _setTargetProperty:(float) newVal { + ((CDSoundEngine*)target).masterGain = newVal; +} + +-(float) _getTargetProperty { + return ((CDSoundEngine*)target).masterGain; +} + +-(void) _stopTarget { + [((CDSoundEngine*)target) stopAllSounds]; +} + +-(Class) _allowableType { + return [CDSoundEngine class]; +} + +@end + + diff --git a/libs/CocosDenshion/SimpleAudioEngine.h b/libs/CocosDenshion/SimpleAudioEngine.h new file mode 100644 index 0000000..35396c6 --- /dev/null +++ b/libs/CocosDenshion/SimpleAudioEngine.h @@ -0,0 +1,90 @@ +/* + Copyright (c) 2010 Steve Oldmeadow + + 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. + + $Id$ + */ + + +#import "CDAudioManager.h" + +/** + A wrapper to the CDAudioManager object. + This is recommended for basic audio requirements. If you just want to play some sound fx + and some background music and have no interest in learning the lower level workings then + this is the interface to use. + + Requirements: + - Firmware: OS 2.2 or greater + - Files: SimpleAudioEngine.*, CocosDenshion.* + - Frameworks: OpenAL, AudioToolbox, AVFoundation + @since v0.8 + */ +@interface SimpleAudioEngine : NSObject { + + BOOL mute_; + BOOL enabled_; +} + +/** Background music volume. Range is 0.0f to 1.0f. This will only have an effect if willPlayBackgroundMusic returns YES */ +@property (readwrite) float backgroundMusicVolume; +/** Effects volume. Range is 0.0f to 1.0f */ +@property (readwrite) float effectsVolume; +/** If NO it indicates background music will not be played either because no background music is loaded or the audio session does not permit it.*/ +@property (readonly) BOOL willPlayBackgroundMusic; + +/** returns the shared instance of the SimpleAudioEngine object */ ++ (SimpleAudioEngine*) sharedEngine; + +/** Preloads a music file so it will be ready to play as background music */ +-(void) preloadBackgroundMusic:(NSString*) filePath; + +/** plays background music in a loop*/ +-(void) playBackgroundMusic:(NSString*) filePath; +/** plays background music, if loop is true the music will repeat otherwise it will be played once */ +-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop; +/** stops playing background music */ +-(void) stopBackgroundMusic; +/** pauses the background music */ +-(void) pauseBackgroundMusic; +/** resume background music that has been paused */ +-(void) resumeBackgroundMusic; +/** rewind the background music */ +-(void) rewindBackgroundMusic; +/** returns whether or not the background music is playing */ +-(BOOL) isBackgroundMusicPlaying; + +/** plays an audio effect with a file path*/ +-(ALuint) playEffect:(NSString*) filePath; +/** stop a sound that is playing, note you must pass in the soundId that is returned when you started playing the sound with playEffect */ +-(void) stopEffect:(ALuint) soundId; +/** plays an audio effect with a file path, pitch, pan and gain */ +-(ALuint) playEffect:(NSString*) filePath pitch:(Float32) pitch pan:(Float32) pan gain:(Float32) gain; +/** preloads an audio effect */ +-(void) preloadEffect:(NSString*) filePath; +/** unloads an audio effect from memory */ +-(void) unloadEffect:(NSString*) filePath; +/** Gets a CDSoundSource object set up to play the specified file. */ +-(CDSoundSource *) soundSourceForFile:(NSString*) filePath; + +/** Shuts down the shared audio engine instance so that it can be reinitialised */ ++(void) end; + +@end diff --git a/libs/CocosDenshion/SimpleAudioEngine.m b/libs/CocosDenshion/SimpleAudioEngine.m new file mode 100644 index 0000000..cdff26c --- /dev/null +++ b/libs/CocosDenshion/SimpleAudioEngine.m @@ -0,0 +1,220 @@ +/* + Copyright (c) 2010 Steve Oldmeadow + + 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. + + $Id$ + */ + +#import "SimpleAudioEngine.h" + +@implementation SimpleAudioEngine + +static SimpleAudioEngine *sharedEngine = nil; +static CDSoundEngine* soundEngine = nil; +static CDAudioManager *am = nil; +static CDBufferManager *bufferManager = nil; + +// Init ++ (SimpleAudioEngine *) sharedEngine +{ + @synchronized(self) { + if (!sharedEngine) + sharedEngine = [[SimpleAudioEngine alloc] init]; + } + return sharedEngine; +} + ++ (id) alloc +{ + @synchronized(self) { + NSAssert(sharedEngine == nil, @"Attempted to allocate a second instance of a singleton."); + return [super alloc]; + } + return nil; +} + +-(id) init +{ + if((self=[super init])) { + am = [CDAudioManager sharedManager]; + soundEngine = am.soundEngine; + bufferManager = [[CDBufferManager alloc] initWithEngine:soundEngine]; + mute_ = NO; + enabled_ = YES; + } + return self; +} + +// Memory +- (void) dealloc +{ + am = nil; + soundEngine = nil; + bufferManager = nil; + [super dealloc]; +} + ++(void) end +{ + am = nil; + [CDAudioManager end]; + [bufferManager release]; + [sharedEngine release]; + sharedEngine = nil; +} + +#pragma mark SimpleAudioEngine - background music + +-(void) preloadBackgroundMusic:(NSString*) filePath { + [am preloadBackgroundMusic:filePath]; +} + +-(void) playBackgroundMusic:(NSString*) filePath +{ + [am playBackgroundMusic:filePath loop:TRUE]; +} + +-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop +{ + [am playBackgroundMusic:filePath loop:loop]; +} + +-(void) stopBackgroundMusic +{ + [am stopBackgroundMusic]; +} + +-(void) pauseBackgroundMusic { + [am pauseBackgroundMusic]; +} + +-(void) resumeBackgroundMusic { + [am resumeBackgroundMusic]; +} + +-(void) rewindBackgroundMusic { + [am rewindBackgroundMusic]; +} + +-(BOOL) isBackgroundMusicPlaying { + return [am isBackgroundMusicPlaying]; +} + +-(BOOL) willPlayBackgroundMusic { + return [am willPlayBackgroundMusic]; +} + +#pragma mark SimpleAudioEngine - sound effects + +-(ALuint) playEffect:(NSString*) filePath +{ + return [self playEffect:filePath pitch:1.0f pan:0.0f gain:1.0f]; +} + +-(ALuint) playEffect:(NSString*) filePath pitch:(Float32) pitch pan:(Float32) pan gain:(Float32) gain +{ + int soundId = [bufferManager bufferForFile:filePath create:YES]; + if (soundId != kCDNoBuffer) { + return [soundEngine playSound:soundId sourceGroupId:0 pitch:pitch pan:pan gain:gain loop:false]; + } else { + return CD_MUTE; + } +} + +-(void) stopEffect:(ALuint) soundId { + [soundEngine stopSound:soundId]; +} + +-(void) preloadEffect:(NSString*) filePath +{ + int soundId = [bufferManager bufferForFile:filePath create:YES]; + if (soundId == kCDNoBuffer) { + CDLOG(@"Denshion::SimpleAudioEngine sound failed to preload %@",filePath); + } +} + +-(void) unloadEffect:(NSString*) filePath +{ + CDLOGINFO(@"Denshion::SimpleAudioEngine unloadedEffect %@",filePath); + [bufferManager releaseBufferForFile:filePath]; +} + +#pragma mark Audio Interrupt Protocol +-(BOOL) mute +{ + return mute_; +} + +-(void) setMute:(BOOL) muteValue +{ + if (mute_ != muteValue) { + mute_ = muteValue; + am.mute = mute_; + } +} + +-(BOOL) enabled +{ + return enabled_; +} + +-(void) setEnabled:(BOOL) enabledValue +{ + if (enabled_ != enabledValue) { + enabled_ = enabledValue; + am.enabled = enabled_; + } +} + + +#pragma mark SimpleAudioEngine - BackgroundMusicVolume +-(float) backgroundMusicVolume +{ + return am.backgroundMusic.volume; +} + +-(void) setBackgroundMusicVolume:(float) volume +{ + am.backgroundMusic.volume = volume; +} + +#pragma mark SimpleAudioEngine - EffectsVolume +-(float) effectsVolume +{ + return am.soundEngine.masterGain; +} + +-(void) setEffectsVolume:(float) volume +{ + am.soundEngine.masterGain = volume; +} + +-(CDSoundSource *) soundSourceForFile:(NSString*) filePath { + int soundId = [bufferManager bufferForFile:filePath create:YES]; + if (soundId != kCDNoBuffer) { + CDSoundSource *result = [soundEngine soundSourceForSound:soundId sourceGroupId:0]; + CDLOGINFO(@"Denshion::SimpleAudioEngine sound source created for %@",filePath); + return result; + } else { + return nil; + } +} + +@end diff --git a/libs/FontLabel/FontLabel.h b/libs/FontLabel/FontLabel.h new file mode 100644 index 0000000..6de9c2c --- /dev/null +++ b/libs/FontLabel/FontLabel.h @@ -0,0 +1,44 @@ +// +// FontLabel.h +// FontLabel +// +// Created by Kevin Ballard on 5/8/09. +// Copyright © 2009 Zynga Game Networks +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +@class ZFont; +@class ZAttributedString; + +@interface FontLabel : UILabel { + void *reserved; // works around a bug in UILabel + ZFont *zFont; + ZAttributedString *zAttributedText; +} +@property (nonatomic, setter=setCGFont:) CGFontRef cgFont __AVAILABILITY_INTERNAL_DEPRECATED; +@property (nonatomic, assign) CGFloat pointSize __AVAILABILITY_INTERNAL_DEPRECATED; +@property (nonatomic, retain, setter=setZFont:) ZFont *zFont; +// if attributedText is nil, fall back on using the inherited UILabel properties +// if attributedText is non-nil, the font/text/textColor +// in addition, adjustsFontSizeToFitWidth does not work with attributed text +@property (nonatomic, copy) ZAttributedString *zAttributedText; +// -initWithFrame:fontName:pointSize: uses FontManager to look up the font name +- (id)initWithFrame:(CGRect)frame fontName:(NSString *)fontName pointSize:(CGFloat)pointSize; +- (id)initWithFrame:(CGRect)frame zFont:(ZFont *)font; +- (id)initWithFrame:(CGRect)frame font:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED; +@end diff --git a/libs/FontLabel/FontLabel.m b/libs/FontLabel/FontLabel.m new file mode 100644 index 0000000..58975b1 --- /dev/null +++ b/libs/FontLabel/FontLabel.m @@ -0,0 +1,195 @@ +// +// FontLabel.m +// FontLabel +// +// Created by Kevin Ballard on 5/8/09. +// Copyright © 2009 Zynga Game Networks +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "FontLabel.h" +#import "FontManager.h" +#import "FontLabelStringDrawing.h" +#import "ZFont.h" + +@interface ZFont (ZFontPrivate) +@property (nonatomic, readonly) CGFloat ratio; +@end + +@implementation FontLabel +@synthesize zFont; +@synthesize zAttributedText; + +- (id)initWithFrame:(CGRect)frame fontName:(NSString *)fontName pointSize:(CGFloat)pointSize { + return [self initWithFrame:frame zFont:[[FontManager sharedManager] zFontWithName:fontName pointSize:pointSize]]; +} + +- (id)initWithFrame:(CGRect)frame zFont:(ZFont *)font { + if ((self = [super initWithFrame:frame])) { + zFont = [font retain]; + } + return self; +} + +- (id)initWithFrame:(CGRect)frame font:(CGFontRef)font pointSize:(CGFloat)pointSize { + return [self initWithFrame:frame zFont:[ZFont fontWithCGFont:font size:pointSize]]; +} + +- (CGFontRef)cgFont { + return self.zFont.cgFont; +} + +- (void)setCGFont:(CGFontRef)font { + if (self.zFont.cgFont != font) { + self.zFont = [ZFont fontWithCGFont:font size:self.zFont.pointSize]; + } +} + +- (CGFloat)pointSize { + return self.zFont.pointSize; +} + +- (void)setPointSize:(CGFloat)pointSize { + if (self.zFont.pointSize != pointSize) { + self.zFont = [ZFont fontWithCGFont:self.zFont.cgFont size:pointSize]; + } +} + +- (void)setZAttributedText:(ZAttributedString *)attStr { + if (zAttributedText != attStr) { + [zAttributedText release]; + zAttributedText = [attStr copy]; + [self setNeedsDisplay]; + } +} + +- (void)drawTextInRect:(CGRect)rect { + if (self.zFont == NULL && self.zAttributedText == nil) { + [super drawTextInRect:rect]; + return; + } + + if (self.zAttributedText == nil) { + // this method is documented as setting the text color for us, but that doesn't appear to be the case + if (self.highlighted) { + [(self.highlightedTextColor ?: [UIColor whiteColor]) setFill]; + } else { + [(self.textColor ?: [UIColor blackColor]) setFill]; + } + + ZFont *actualFont = self.zFont; + CGSize origSize = rect.size; + if (self.numberOfLines == 1) { + origSize.height = actualFont.leading; + CGPoint point = CGPointMake(rect.origin.x, + rect.origin.y + roundf(((rect.size.height - actualFont.leading) / 2.0f))); + CGSize size = [self.text sizeWithZFont:actualFont]; + if (self.adjustsFontSizeToFitWidth && self.minimumFontSize < actualFont.pointSize) { + if (size.width > origSize.width) { + CGFloat desiredRatio = (origSize.width * actualFont.ratio) / size.width; + CGFloat desiredPointSize = desiredRatio * actualFont.pointSize / actualFont.ratio; + actualFont = [actualFont fontWithSize:MAX(MAX(desiredPointSize, self.minimumFontSize), 1.0f)]; + size = [self.text sizeWithZFont:actualFont]; + } + if (!CGSizeEqualToSize(origSize, size)) { + switch (self.baselineAdjustment) { + case UIBaselineAdjustmentAlignCenters: + point.y += roundf((origSize.height - size.height) / 2.0f); + break; + case UIBaselineAdjustmentAlignBaselines: + point.y += (self.zFont.ascender - actualFont.ascender); + break; + case UIBaselineAdjustmentNone: + break; + } + } + } + size.width = MIN(size.width, origSize.width); + // adjust the point for alignment + switch (self.textAlignment) { + case UITextAlignmentLeft: + break; + case UITextAlignmentCenter: + point.x += (origSize.width - size.width) / 2.0f; + break; + case UITextAlignmentRight: + point.x += origSize.width - size.width; + break; + } + [self.text drawAtPoint:point forWidth:size.width withZFont:actualFont lineBreakMode:self.lineBreakMode]; + } else { + CGSize size = [self.text sizeWithZFont:actualFont constrainedToSize:origSize lineBreakMode:self.lineBreakMode numberOfLines:self.numberOfLines]; + CGPoint point = rect.origin; + point.y += roundf((rect.size.height - size.height) / 2.0f); + rect = (CGRect){point, CGSizeMake(rect.size.width, size.height)}; + [self.text drawInRect:rect withZFont:actualFont lineBreakMode:self.lineBreakMode alignment:self.textAlignment numberOfLines:self.numberOfLines]; + } + } else { + ZAttributedString *attStr = self.zAttributedText; + if (self.highlighted) { + // modify the string to change the base color + ZMutableAttributedString *mutStr = [[attStr mutableCopy] autorelease]; + NSRange activeRange = NSMakeRange(0, attStr.length); + while (activeRange.length > 0) { + NSRange effective; + UIColor *color = [attStr attribute:ZForegroundColorAttributeName atIndex:activeRange.location + longestEffectiveRange:&effective inRange:activeRange]; + if (color == nil) { + [mutStr addAttribute:ZForegroundColorAttributeName value:[UIColor whiteColor] range:effective]; + } + activeRange.location += effective.length, activeRange.length -= effective.length; + } + attStr = mutStr; + } + CGSize size = [attStr sizeConstrainedToSize:rect.size lineBreakMode:self.lineBreakMode numberOfLines:self.numberOfLines]; + CGPoint point = rect.origin; + point.y += roundf((rect.size.height - size.height) / 2.0f); + rect = (CGRect){point, CGSizeMake(rect.size.width, size.height)}; + [attStr drawInRect:rect withLineBreakMode:self.lineBreakMode alignment:self.textAlignment numberOfLines:self.numberOfLines]; + } +} + +- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines { + if (self.zFont == NULL && self.zAttributedText == nil) { + return [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines]; + } + + if (numberOfLines == 1) { + // if numberOfLines == 1 we need to use the version that converts spaces + CGSize size; + if (self.zAttributedText == nil) { + size = [self.text sizeWithZFont:self.zFont]; + } else { + size = [self.zAttributedText size]; + } + bounds.size.width = MIN(bounds.size.width, size.width); + bounds.size.height = MIN(bounds.size.height, size.height); + } else { + if (numberOfLines > 0) bounds.size.height = MIN(bounds.size.height, self.zFont.leading * numberOfLines); + if (self.zAttributedText == nil) { + bounds.size = [self.text sizeWithZFont:self.zFont constrainedToSize:bounds.size lineBreakMode:self.lineBreakMode]; + } else { + bounds.size = [self.zAttributedText sizeConstrainedToSize:bounds.size lineBreakMode:self.lineBreakMode]; + } + } + return bounds; +} + +- (void)dealloc { + [zFont release]; + [zAttributedText release]; + [super dealloc]; +} +@end diff --git a/libs/FontLabel/FontLabelStringDrawing.h b/libs/FontLabel/FontLabelStringDrawing.h new file mode 100644 index 0000000..821da22 --- /dev/null +++ b/libs/FontLabel/FontLabelStringDrawing.h @@ -0,0 +1,69 @@ +// +// FontLabelStringDrawing.h +// FontLabel +// +// Created by Kevin Ballard on 5/5/09. +// Copyright © 2009 Zynga Game Networks +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "ZAttributedString.h" + +@class ZFont; + +@interface NSString (FontLabelStringDrawing) +// CGFontRef-based methods +- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED; +- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size __AVAILABILITY_INTERNAL_DEPRECATED; +- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size + lineBreakMode:(UILineBreakMode)lineBreakMode __AVAILABILITY_INTERNAL_DEPRECATED; +- (CGSize)drawAtPoint:(CGPoint)point withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED; +- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED; +- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize + lineBreakMode:(UILineBreakMode)lineBreakMode __AVAILABILITY_INTERNAL_DEPRECATED; +- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize + lineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment __AVAILABILITY_INTERNAL_DEPRECATED; + +// ZFont-based methods +- (CGSize)sizeWithZFont:(ZFont *)font; +- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size; +- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode; +- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode + numberOfLines:(NSUInteger)numberOfLines; +- (CGSize)drawAtPoint:(CGPoint)point withZFont:(ZFont *)font; +- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode; +- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font; +- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode; +- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode + alignment:(UITextAlignment)alignment; +- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode + alignment:(UITextAlignment)alignment numberOfLines:(NSUInteger)numberOfLines; +@end + +@interface ZAttributedString (ZAttributedStringDrawing) +- (CGSize)size; +- (CGSize)sizeConstrainedToSize:(CGSize)size; +- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode; +- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode + numberOfLines:(NSUInteger)numberOfLines; +- (CGSize)drawAtPoint:(CGPoint)point; +- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode; +- (CGSize)drawInRect:(CGRect)rect; +- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode; +- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment; +- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment + numberOfLines:(NSUInteger)numberOfLines; +@end diff --git a/libs/FontLabel/FontLabelStringDrawing.m b/libs/FontLabel/FontLabelStringDrawing.m new file mode 100644 index 0000000..2907372 --- /dev/null +++ b/libs/FontLabel/FontLabelStringDrawing.m @@ -0,0 +1,892 @@ +// +// FontLabelStringDrawing.m +// FontLabel +// +// Created by Kevin Ballard on 5/5/09. +// Copyright © 2009 Zynga Game Networks +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "FontLabelStringDrawing.h" +#import "ZFont.h" +#import "ZAttributedStringPrivate.h" + +@interface ZFont (ZFontPrivate) +@property (nonatomic, readonly) CGFloat ratio; +@end + +#define kUnicodeHighSurrogateStart 0xD800 +#define kUnicodeHighSurrogateEnd 0xDBFF +#define kUnicodeHighSurrogateMask kUnicodeHighSurrogateStart +#define kUnicodeLowSurrogateStart 0xDC00 +#define kUnicodeLowSurrogateEnd 0xDFFF +#define kUnicodeLowSurrogateMask kUnicodeLowSurrogateStart +#define kUnicodeSurrogateTypeMask 0xFC00 +#define UnicharIsHighSurrogate(c) ((c & kUnicodeSurrogateTypeMask) == kUnicodeHighSurrogateMask) +#define UnicharIsLowSurrogate(c) ((c & kUnicodeSurrogateTypeMask) == kUnicodeLowSurrogateMask) +#define ConvertSurrogatePairToUTF32(high, low) ((UInt32)((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)) + +typedef enum { + kFontTableFormat4 = 4, + kFontTableFormat12 = 12, +} FontTableFormat; + +typedef struct fontTable { + NSUInteger retainCount; + CFDataRef cmapTable; + FontTableFormat format; + union { + struct { + UInt16 segCountX2; + UInt16 *endCodes; + UInt16 *startCodes; + UInt16 *idDeltas; + UInt16 *idRangeOffsets; + } format4; + struct { + UInt32 nGroups; + struct { + UInt32 startCharCode; + UInt32 endCharCode; + UInt32 startGlyphCode; + } *groups; + } format12; + } cmap; +} fontTable; + +static FontTableFormat supportedFormats[] = { kFontTableFormat4, kFontTableFormat12 }; +static size_t supportedFormatsCount = sizeof(supportedFormats) / sizeof(FontTableFormat); + +static fontTable *newFontTable(CFDataRef cmapTable, FontTableFormat format) { + fontTable *table = (struct fontTable *)malloc(sizeof(struct fontTable)); + table->retainCount = 1; + table->cmapTable = CFRetain(cmapTable); + table->format = format; + return table; +} + +static fontTable *retainFontTable(fontTable *table) { + if (table != NULL) { + table->retainCount++; + } + return table; +} + +static void releaseFontTable(fontTable *table) { + if (table != NULL) { + if (table->retainCount <= 1) { + CFRelease(table->cmapTable); + free(table); + } else { + table->retainCount--; + } + } +} + +static const void *fontTableRetainCallback(CFAllocatorRef allocator, const void *value) { + return retainFontTable((fontTable *)value); +} + +static void fontTableReleaseCallback(CFAllocatorRef allocator, const void *value) { + releaseFontTable((fontTable *)value); +} + +static const CFDictionaryValueCallBacks kFontTableDictionaryValueCallBacks = { + .version = 0, + .retain = &fontTableRetainCallback, + .release = &fontTableReleaseCallback, + .copyDescription = NULL, + .equal = NULL +}; + +// read the cmap table from the font +// we only know how to understand some of the table formats at the moment +static fontTable *readFontTableFromCGFont(CGFontRef font) { + CFDataRef cmapTable = CGFontCopyTableForTag(font, 'cmap'); + NSCAssert1(cmapTable != NULL, @"CGFontCopyTableForTag returned NULL for 'cmap' tag in font %@", + (font ? [(id)CFCopyDescription(font) autorelease] : @"(null)")); + const UInt8 * const bytes = CFDataGetBytePtr(cmapTable); + NSCAssert1(OSReadBigInt16(bytes, 0) == 0, @"cmap table for font %@ has bad version number", + (font ? [(id)CFCopyDescription(font) autorelease] : @"(null)")); + UInt16 numberOfSubtables = OSReadBigInt16(bytes, 2); + const UInt8 *unicodeSubtable = NULL; + //UInt16 unicodeSubtablePlatformID; + UInt16 unicodeSubtablePlatformSpecificID; + FontTableFormat unicodeSubtableFormat; + const UInt8 * const encodingSubtables = &bytes[4]; + for (UInt16 i = 0; i < numberOfSubtables; i++) { + const UInt8 * const encodingSubtable = &encodingSubtables[8 * i]; + UInt16 platformID = OSReadBigInt16(encodingSubtable, 0); + UInt16 platformSpecificID = OSReadBigInt16(encodingSubtable, 2); + // find the best subtable + // best is defined by a combination of encoding and format + // At the moment we only support format 4, so ignore all other format tables + // We prefer platformID == 0, but we will also accept Microsoft's unicode format + if (platformID == 0 || (platformID == 3 && platformSpecificID == 1)) { + BOOL preferred = NO; + if (unicodeSubtable == NULL) { + preferred = YES; + } else if (platformID == 0 && platformSpecificID > unicodeSubtablePlatformSpecificID) { + preferred = YES; + } + if (preferred) { + UInt32 offset = OSReadBigInt32(encodingSubtable, 4); + const UInt8 *subtable = &bytes[offset]; + UInt16 format = OSReadBigInt16(subtable, 0); + for (size_t i = 0; i < supportedFormatsCount; i++) { + if (format == supportedFormats[i]) { + if (format >= 8) { + // the version is a fixed-point + UInt16 formatFrac = OSReadBigInt16(subtable, 2); + if (formatFrac != 0) { + // all the current formats with a Fixed version are always *.0 + continue; + } + } + unicodeSubtable = subtable; + //unicodeSubtablePlatformID = platformID; + unicodeSubtablePlatformSpecificID = platformSpecificID; + unicodeSubtableFormat = format; + break; + } + } + } + } + } + fontTable *table = NULL; + if (unicodeSubtable != NULL) { + table = newFontTable(cmapTable, unicodeSubtableFormat); + switch (unicodeSubtableFormat) { + case kFontTableFormat4: + // subtable format 4 + //UInt16 length = OSReadBigInt16(unicodeSubtable, 2); + //UInt16 language = OSReadBigInt16(unicodeSubtable, 4); + table->cmap.format4.segCountX2 = OSReadBigInt16(unicodeSubtable, 6); + //UInt16 searchRange = OSReadBigInt16(unicodeSubtable, 8); + //UInt16 entrySelector = OSReadBigInt16(unicodeSubtable, 10); + //UInt16 rangeShift = OSReadBigInt16(unicodeSubtable, 12); + table->cmap.format4.endCodes = (UInt16*)&unicodeSubtable[14]; + table->cmap.format4.startCodes = (UInt16*)&((UInt8*)table->cmap.format4.endCodes)[table->cmap.format4.segCountX2+2]; + table->cmap.format4.idDeltas = (UInt16*)&((UInt8*)table->cmap.format4.startCodes)[table->cmap.format4.segCountX2]; + table->cmap.format4.idRangeOffsets = (UInt16*)&((UInt8*)table->cmap.format4.idDeltas)[table->cmap.format4.segCountX2]; + //UInt16 *glyphIndexArray = &idRangeOffsets[segCountX2]; + break; + case kFontTableFormat12: + table->cmap.format12.nGroups = OSReadBigInt32(unicodeSubtable, 12); + table->cmap.format12.groups = (void *)&unicodeSubtable[16]; + break; + default: + releaseFontTable(table); + table = NULL; + } + } + CFRelease(cmapTable); + return table; +} + +// outGlyphs must be at least size n +static void mapCharactersToGlyphsInFont(const fontTable *table, unichar characters[], size_t charLen, CGGlyph outGlyphs[], size_t *outGlyphLen) { + if (table != NULL) { + NSUInteger j = 0; + switch (table->format) { + case kFontTableFormat4: { + for (NSUInteger i = 0; i < charLen; i++, j++) { + unichar c = characters[i]; + UInt16 segOffset; + BOOL foundSegment = NO; + for (segOffset = 0; segOffset < table->cmap.format4.segCountX2; segOffset += 2) { + UInt16 endCode = OSReadBigInt16(table->cmap.format4.endCodes, segOffset); + if (endCode >= c) { + foundSegment = YES; + break; + } + } + if (!foundSegment) { + // no segment + // this is an invalid font + outGlyphs[j] = 0; + } else { + UInt16 startCode = OSReadBigInt16(table->cmap.format4.startCodes, segOffset); + if (!(startCode <= c)) { + // the code falls in a hole between segments + outGlyphs[j] = 0; + } else { + UInt16 idRangeOffset = OSReadBigInt16(table->cmap.format4.idRangeOffsets, segOffset); + if (idRangeOffset == 0) { + UInt16 idDelta = OSReadBigInt16(table->cmap.format4.idDeltas, segOffset); + outGlyphs[j] = (c + idDelta) % 65536; + } else { + // use the glyphIndexArray + UInt16 glyphOffset = idRangeOffset + 2 * (c - startCode); + outGlyphs[j] = OSReadBigInt16(&((UInt8*)table->cmap.format4.idRangeOffsets)[segOffset], glyphOffset); + } + } + } + } + break; + } + case kFontTableFormat12: { + UInt32 lastSegment = UINT32_MAX; + for (NSUInteger i = 0; i < charLen; i++, j++) { + unichar c = characters[i]; + UInt32 c32 = c; + if (UnicharIsHighSurrogate(c)) { + if (i+1 < charLen) { // do we have another character after this one? + unichar cc = characters[i+1]; + if (UnicharIsLowSurrogate(cc)) { + c32 = ConvertSurrogatePairToUTF32(c, cc); + i++; + } + } + } + // Start the heuristic search + // If this is an ASCII char, just do a linear search + // Otherwise do a hinted, modified binary search + // Start the first pivot at the last range found + // And when moving the pivot, limit the movement by increasing + // powers of two. This should help with locality + __typeof__(table->cmap.format12.groups[0]) *foundGroup = NULL; + if (c32 <= 0x7F) { + // ASCII + for (UInt32 idx = 0; idx < table->cmap.format12.nGroups; idx++) { + __typeof__(table->cmap.format12.groups[idx]) *group = &table->cmap.format12.groups[idx]; + if (c32 < OSSwapBigToHostInt32(group->startCharCode)) { + // we've fallen into a hole + break; + } else if (c32 <= OSSwapBigToHostInt32(group->endCharCode)) { + // this is the range + foundGroup = group; + break; + } + } + } else { + // heuristic search + UInt32 maxJump = (lastSegment == UINT32_MAX ? UINT32_MAX / 2 : 8); + UInt32 lowIdx = 0, highIdx = table->cmap.format12.nGroups; // highIdx is the first invalid idx + UInt32 pivot = (lastSegment == UINT32_MAX ? lowIdx + (highIdx - lowIdx) / 2 : lastSegment); + while (highIdx > lowIdx) { + __typeof__(table->cmap.format12.groups[pivot]) *group = &table->cmap.format12.groups[pivot]; + if (c32 < OSSwapBigToHostInt32(group->startCharCode)) { + highIdx = pivot; + } else if (c32 > OSSwapBigToHostInt32(group->endCharCode)) { + lowIdx = pivot + 1; + } else { + // we've hit the range + foundGroup = group; + break; + } + if (highIdx - lowIdx > maxJump * 2) { + if (highIdx == pivot) { + pivot -= maxJump; + } else { + pivot += maxJump; + } + maxJump *= 2; + } else { + pivot = lowIdx + (highIdx - lowIdx) / 2; + } + } + if (foundGroup != NULL) lastSegment = pivot; + } + if (foundGroup == NULL) { + outGlyphs[j] = 0; + } else { + outGlyphs[j] = (CGGlyph)(OSSwapBigToHostInt32(foundGroup->startGlyphCode) + + (c32 - OSSwapBigToHostInt32(foundGroup->startCharCode))); + } + } + break; + } + } + if (outGlyphLen != NULL) *outGlyphLen = j; + } else { + // we have no table, so just null out the glyphs + bzero(outGlyphs, charLen*sizeof(CGGlyph)); + if (outGlyphLen != NULL) *outGlyphLen = 0; + } +} + +static BOOL mapGlyphsToAdvancesInFont(ZFont *font, size_t n, CGGlyph glyphs[], CGFloat outAdvances[]) { + int advances[n]; + if (CGFontGetGlyphAdvances(font.cgFont, glyphs, n, advances)) { + CGFloat ratio = font.ratio; + + for (size_t i = 0; i < n; i++) { + outAdvances[i] = advances[i]*ratio; + } + return YES; + } else { + bzero(outAdvances, n*sizeof(CGFloat)); + } + return NO; +} + +static id getValueOrDefaultForRun(ZAttributeRun *run, NSString *key) { + id value = [run.attributes objectForKey:key]; + if (value == nil) { + static NSDictionary *defaultValues = nil; + if (defaultValues == nil) { + defaultValues = [[NSDictionary alloc] initWithObjectsAndKeys: + [ZFont fontWithUIFont:[UIFont systemFontOfSize:12]], ZFontAttributeName, + [UIColor blackColor], ZForegroundColorAttributeName, + [UIColor clearColor], ZBackgroundColorAttributeName, + [NSNumber numberWithInt:ZUnderlineStyleNone], ZUnderlineStyleAttributeName, + nil]; + } + value = [defaultValues objectForKey:key]; + } + return value; +} + +static void readRunInformation(NSArray *attributes, NSUInteger len, CFMutableDictionaryRef fontTableMap, + NSUInteger index, ZAttributeRun **currentRun, NSUInteger *nextRunStart, + ZFont **currentFont, fontTable **currentTable) { + *currentRun = [attributes objectAtIndex:index]; + *nextRunStart = ([attributes count] > index+1 ? [[attributes objectAtIndex:index+1] index] : len); + *currentFont = getValueOrDefaultForRun(*currentRun, ZFontAttributeName); + if (!CFDictionaryGetValueIfPresent(fontTableMap, (*currentFont).cgFont, (const void **)currentTable)) { + *currentTable = readFontTableFromCGFont((*currentFont).cgFont); + CFDictionarySetValue(fontTableMap, (*currentFont).cgFont, *currentTable); + releaseFontTable(*currentTable); + } +} + +static CGSize drawOrSizeTextConstrainedToSize(BOOL performDraw, NSString *string, NSArray *attributes, CGSize constrainedSize, NSUInteger maxLines, + UILineBreakMode lineBreakMode, UITextAlignment alignment, BOOL ignoreColor) { + NSUInteger len = [string length]; + NSUInteger idx = 0; + CGPoint drawPoint = CGPointZero; + CGSize retValue = CGSizeZero; + CGContextRef ctx = (performDraw ? UIGraphicsGetCurrentContext() : NULL); + + BOOL convertNewlines = (maxLines == 1); + + // Extract the characters from the string + // Convert newlines to spaces if necessary + unichar *characters = (unichar *)malloc(sizeof(unichar) * len); + if (convertNewlines) { + NSCharacterSet *charset = [NSCharacterSet newlineCharacterSet]; + NSRange range = NSMakeRange(0, len); + size_t cIdx = 0; + while (range.length > 0) { + NSRange newlineRange = [string rangeOfCharacterFromSet:charset options:0 range:range]; + if (newlineRange.location == NSNotFound) { + [string getCharacters:&characters[cIdx] range:range]; + cIdx += range.length; + break; + } else { + NSUInteger delta = newlineRange.location - range.location; + if (newlineRange.location > range.location) { + [string getCharacters:&characters[cIdx] range:NSMakeRange(range.location, delta)]; + } + cIdx += delta; + characters[cIdx] = (unichar)' '; + cIdx++; + delta += newlineRange.length; + range.location += delta, range.length -= delta; + if (newlineRange.length == 1 && range.length >= 1 && + [string characterAtIndex:newlineRange.location] == (unichar)'\r' && + [string characterAtIndex:range.location] == (unichar)'\n') { + // CRLF sequence, skip the LF + range.location += 1, range.length -= 1; + } + } + } + len = cIdx; + } else { + [string getCharacters:characters range:NSMakeRange(0, len)]; + } + + // Create storage for glyphs and advances + CGGlyph *glyphs; + CGFloat *advances; + { + NSUInteger maxRunLength = 0; + ZAttributeRun *a = [attributes objectAtIndex:0]; + for (NSUInteger i = 1; i < [attributes count]; i++) { + ZAttributeRun *b = [attributes objectAtIndex:i]; + maxRunLength = MAX(maxRunLength, b.index - a.index); + a = b; + } + maxRunLength = MAX(maxRunLength, len - a.index); + maxRunLength++; // for a potential ellipsis + glyphs = (CGGlyph *)malloc(sizeof(CGGlyph) * maxRunLength); + advances = (CGFloat *)malloc(sizeof(CGFloat) * maxRunLength); + } + + // Use this table to cache all fontTable objects + CFMutableDictionaryRef fontTableMap = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, + &kFontTableDictionaryValueCallBacks); + + // Fetch initial style values + NSUInteger currentRunIdx = 0; + ZAttributeRun *currentRun; + NSUInteger nextRunStart; + ZFont *currentFont; + fontTable *currentTable; + +#define READ_RUN() readRunInformation(attributes, len, fontTableMap, \ + currentRunIdx, ¤tRun, &nextRunStart, \ + ¤tFont, ¤tTable) + + READ_RUN(); + + // fetch the glyphs for the first run + size_t glyphCount; + NSUInteger glyphIdx; + +#define READ_GLYPHS() do { \ + mapCharactersToGlyphsInFont(currentTable, &characters[currentRun.index], (nextRunStart - currentRun.index), glyphs, &glyphCount); \ + mapGlyphsToAdvancesInFont(currentFont, (nextRunStart - currentRun.index), glyphs, advances); \ + glyphIdx = 0; \ + } while (0) + + READ_GLYPHS(); + + NSMutableCharacterSet *alphaCharset = [NSMutableCharacterSet alphanumericCharacterSet]; + [alphaCharset addCharactersInString:@"([{'\"\u2019\u02BC"]; + + // scan left-to-right looking for newlines or until we hit the width constraint + // When we hit a wrapping point, calculate truncation as follows: + // If we have room to draw at least one more character on the next line, no truncation + // Otherwise apply the truncation algorithm to the current line. + // After calculating any truncation, draw. + // Each time we hit the end of an attribute run, calculate the new font and make sure + // it fits (vertically) within the size constraint. If not, truncate this line. + // When we draw, iterate over the attribute runs for this line and draw each run separately + BOOL lastLine = NO; // used to indicate truncation and to stop the iterating + NSUInteger lineCount = 1; + while (idx < len && !lastLine) { + if (maxLines > 0 && lineCount == maxLines) { + lastLine = YES; + } + // scan left-to-right + struct { + NSUInteger index; + NSUInteger glyphIndex; + NSUInteger currentRunIdx; + } indexCache = { idx, glyphIdx, currentRunIdx }; + CGSize lineSize = CGSizeMake(0, currentFont.leading); + CGFloat lineAscender = currentFont.ascender; + struct { + NSUInteger index; + NSUInteger glyphIndex; + NSUInteger currentRunIdx; + CGSize lineSize; + } lastWrapCache = {0, 0, 0, CGSizeZero}; + BOOL inAlpha = NO; // used for calculating wrap points + + BOOL finishLine = NO; + for (;idx <= len && !finishLine;) { + NSUInteger skipCount = 0; + if (idx == len) { + finishLine = YES; + lastLine = YES; + } else { + if (idx >= nextRunStart) { + // cycle the font and table and grab the next set of glyphs + do { + currentRunIdx++; + READ_RUN(); + } while (idx >= nextRunStart); + READ_GLYPHS(); + // re-scan the characters to synchronize the glyph index + for (NSUInteger j = currentRun.index; j < idx; j++) { + if (UnicharIsHighSurrogate(characters[j]) && j+1 lineSize.height) { + lineSize.height = currentFont.leading; + if (retValue.height + currentFont.ascender > constrainedSize.height) { + lastLine = YES; + finishLine = YES; + } + } + lineAscender = MAX(lineAscender, currentFont.ascender); + } + unichar c = characters[idx]; + // Mark a wrap point before spaces and after any stretch of non-alpha characters + BOOL markWrap = NO; + if (c == (unichar)' ') { + markWrap = YES; + } else if ([alphaCharset characterIsMember:c]) { + if (!inAlpha) { + markWrap = YES; + inAlpha = YES; + } + } else { + inAlpha = NO; + } + if (markWrap) { + lastWrapCache = (__typeof__(lastWrapCache)){ + .index = idx, + .glyphIndex = glyphIdx, + .currentRunIdx = currentRunIdx, + .lineSize = lineSize + }; + } + // process the line + if (c == (unichar)'\n' || c == 0x0085) { // U+0085 is the NEXT_LINE unicode character + finishLine = YES; + skipCount = 1; + } else if (c == (unichar)'\r') { + finishLine = YES; + // check for CRLF + if (idx+1 < len && characters[idx+1] == (unichar)'\n') { + skipCount = 2; + } else { + skipCount = 1; + } + } else if (lineSize.width + advances[glyphIdx] > constrainedSize.width) { + finishLine = YES; + if (retValue.height + lineSize.height + currentFont.ascender > constrainedSize.height) { + lastLine = YES; + } + // walk backwards if wrapping is necessary + if (lastWrapCache.index > indexCache.index && lineBreakMode != UILineBreakModeCharacterWrap && + (!lastLine || lineBreakMode != UILineBreakModeClip)) { + // we're doing some sort of word wrapping + idx = lastWrapCache.index; + lineSize = lastWrapCache.lineSize; + if (!lastLine) { + // re-check if this is the last line + if (lastWrapCache.currentRunIdx != currentRunIdx) { + currentRunIdx = lastWrapCache.currentRunIdx; + READ_RUN(); + READ_GLYPHS(); + } + if (retValue.height + lineSize.height + currentFont.ascender > constrainedSize.height) { + lastLine = YES; + } + } + glyphIdx = lastWrapCache.glyphIndex; + // skip any spaces + for (NSUInteger j = idx; j < len && characters[j] == (unichar)' '; j++) { + skipCount++; + } + } + } + } + if (finishLine) { + // TODO: support head/middle truncation + if (lastLine && idx < len && lineBreakMode == UILineBreakModeTailTruncation) { + // truncate + unichar ellipsis = 0x2026; // ellipsis (…) + CGGlyph ellipsisGlyph; + mapCharactersToGlyphsInFont(currentTable, &ellipsis, 1, &ellipsisGlyph, NULL); + CGFloat ellipsisWidth; + mapGlyphsToAdvancesInFont(currentFont, 1, &ellipsisGlyph, &ellipsisWidth); + while ((idx - indexCache.index) > 1 && lineSize.width + ellipsisWidth > constrainedSize.width) { + // we have more than 1 character and we're too wide, so back up + idx--; + if (UnicharIsHighSurrogate(characters[idx]) && UnicharIsLowSurrogate(characters[idx+1])) { + idx--; + } + if (idx < currentRun.index) { + ZFont *oldFont = currentFont; + do { + currentRunIdx--; + READ_RUN(); + } while (idx < currentRun.index); + READ_GLYPHS(); + glyphIdx = glyphCount-1; + if (oldFont != currentFont) { + mapCharactersToGlyphsInFont(currentTable, &ellipsis, 1, &ellipsisGlyph, NULL); + mapGlyphsToAdvancesInFont(currentFont, 1, &ellipsisGlyph, &ellipsisWidth); + } + } else { + glyphIdx--; + } + lineSize.width -= advances[glyphIdx]; + } + // skip any spaces before truncating + while ((idx - indexCache.index) > 1 && characters[idx-1] == (unichar)' ') { + idx--; + if (idx < currentRun.index) { + currentRunIdx--; + READ_RUN(); + READ_GLYPHS(); + glyphIdx = glyphCount-1; + } else { + glyphIdx--; + } + lineSize.width -= advances[glyphIdx]; + } + lineSize.width += ellipsisWidth; + glyphs[glyphIdx] = ellipsisGlyph; + idx++; + glyphIdx++; + } + retValue.width = MAX(retValue.width, lineSize.width); + retValue.height += lineSize.height; + + // draw + if (performDraw) { + switch (alignment) { + case UITextAlignmentLeft: + drawPoint.x = 0; + break; + case UITextAlignmentCenter: + drawPoint.x = (constrainedSize.width - lineSize.width) / 2.0f; + break; + case UITextAlignmentRight: + drawPoint.x = constrainedSize.width - lineSize.width; + break; + } + NSUInteger stopGlyphIdx = glyphIdx; + NSUInteger lastRunIdx = currentRunIdx; + NSUInteger stopCharIdx = idx; + idx = indexCache.index; + if (currentRunIdx != indexCache.currentRunIdx) { + currentRunIdx = indexCache.currentRunIdx; + READ_RUN(); + READ_GLYPHS(); + } + glyphIdx = indexCache.glyphIndex; + for (NSUInteger drawIdx = currentRunIdx; drawIdx <= lastRunIdx; drawIdx++) { + if (drawIdx != currentRunIdx) { + currentRunIdx = drawIdx; + READ_RUN(); + READ_GLYPHS(); + } + NSUInteger numGlyphs; + if (drawIdx == lastRunIdx) { + numGlyphs = stopGlyphIdx - glyphIdx; + idx = stopCharIdx; + } else { + numGlyphs = glyphCount - glyphIdx; + idx = nextRunStart; + } + CGContextSetFont(ctx, currentFont.cgFont); + CGContextSetFontSize(ctx, currentFont.pointSize); + // calculate the fragment size + CGFloat fragmentWidth = 0; + for (NSUInteger g = 0; g < numGlyphs; g++) { + fragmentWidth += advances[glyphIdx + g]; + } + + if (!ignoreColor) { + UIColor *foregroundColor = getValueOrDefaultForRun(currentRun, ZForegroundColorAttributeName); + UIColor *backgroundColor = getValueOrDefaultForRun(currentRun, ZBackgroundColorAttributeName); + if (backgroundColor != nil && ![backgroundColor isEqual:[UIColor clearColor]]) { + [backgroundColor setFill]; + UIRectFillUsingBlendMode((CGRect){ drawPoint, { fragmentWidth, lineSize.height } }, kCGBlendModeNormal); + } + [foregroundColor setFill]; + } + + CGContextShowGlyphsAtPoint(ctx, drawPoint.x, drawPoint.y + lineAscender, &glyphs[glyphIdx], numGlyphs); + NSNumber *underlineStyle = getValueOrDefaultForRun(currentRun, ZUnderlineStyleAttributeName); + if ([underlineStyle integerValue] & ZUnderlineStyleMask) { + // we only support single for the time being + UIRectFill(CGRectMake(drawPoint.x, drawPoint.y + lineAscender, fragmentWidth, 1)); + } + drawPoint.x += fragmentWidth; + glyphIdx += numGlyphs; + } + drawPoint.y += lineSize.height; + } + idx += skipCount; + glyphIdx += skipCount; + lineCount++; + } else { + lineSize.width += advances[glyphIdx]; + glyphIdx++; + idx++; + if (idx < len && UnicharIsHighSurrogate(characters[idx-1]) && UnicharIsLowSurrogate(characters[idx])) { + // skip the second half of the surrogate pair + idx++; + } + } + } + } + CFRelease(fontTableMap); + free(glyphs); + free(advances); + free(characters); + +#undef READ_GLYPHS +#undef READ_RUN + + return retValue; +} + +static NSArray *attributeRunForFont(ZFont *font) { + return [NSArray arrayWithObject:[ZAttributeRun attributeRunWithIndex:0 + attributes:[NSDictionary dictionaryWithObject:font + forKey:ZFontAttributeName]]]; +} + +static CGSize drawTextInRect(CGRect rect, NSString *text, NSArray *attributes, UILineBreakMode lineBreakMode, + UITextAlignment alignment, NSUInteger numberOfLines, BOOL ignoreColor) { + CGContextRef ctx = UIGraphicsGetCurrentContext(); + + CGContextSaveGState(ctx); + + // flip it upside-down because our 0,0 is upper-left, whereas ttfs are for screens where 0,0 is lower-left + CGAffineTransform textTransform = CGAffineTransformMake(1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f); + CGContextSetTextMatrix(ctx, textTransform); + + CGContextTranslateCTM(ctx, rect.origin.x, rect.origin.y); + + CGContextSetTextDrawingMode(ctx, kCGTextFill); + CGSize size = drawOrSizeTextConstrainedToSize(YES, text, attributes, rect.size, numberOfLines, lineBreakMode, alignment, ignoreColor); + + CGContextRestoreGState(ctx); + + return size; +} + +@implementation NSString (FontLabelStringDrawing) +// CGFontRef-based methods +- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize { + return [self sizeWithZFont:[ZFont fontWithCGFont:font size:pointSize]]; +} + +- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size { + return [self sizeWithZFont:[ZFont fontWithCGFont:font size:pointSize] constrainedToSize:size]; +} + +- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size + lineBreakMode:(UILineBreakMode)lineBreakMode { + return [self sizeWithZFont:[ZFont fontWithCGFont:font size:pointSize] constrainedToSize:size lineBreakMode:lineBreakMode]; +} + +- (CGSize)drawAtPoint:(CGPoint)point withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize { + return [self drawAtPoint:point withZFont:[ZFont fontWithCGFont:font size:pointSize]]; +} + +- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize { + return [self drawInRect:rect withZFont:[ZFont fontWithCGFont:font size:pointSize]]; +} + +- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize lineBreakMode:(UILineBreakMode)lineBreakMode { + return [self drawInRect:rect withZFont:[ZFont fontWithCGFont:font size:pointSize] lineBreakMode:lineBreakMode]; +} + +- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize + lineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment { + return [self drawInRect:rect withZFont:[ZFont fontWithCGFont:font size:pointSize] lineBreakMode:lineBreakMode alignment:alignment]; +} + +// ZFont-based methods +- (CGSize)sizeWithZFont:(ZFont *)font { + CGSize size = drawOrSizeTextConstrainedToSize(NO, self, attributeRunForFont(font), CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), 1, + UILineBreakModeClip, UITextAlignmentLeft, YES); + return CGSizeMake(ceilf(size.width), ceilf(size.height)); +} + +- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size { + return [self sizeWithZFont:font constrainedToSize:size lineBreakMode:UILineBreakModeWordWrap]; +} + +/* + According to experimentation with UIStringDrawing, this can actually return a CGSize whose height is greater + than the one passed in. The two cases are as follows: + 1. If the given size parameter's height is smaller than a single line, the returned value will + be the height of one line. + 2. If the given size parameter's height falls between multiples of a line height, and the wrapped string + actually extends past the size.height, and the difference between size.height and the previous multiple + of a line height is >= the font's ascender, then the returned size's height is extended to the next line. + To put it simply, if the baseline point of a given line falls in the given size, the entire line will + be present in the output size. + */ +- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode { + size = drawOrSizeTextConstrainedToSize(NO, self, attributeRunForFont(font), size, 0, lineBreakMode, UITextAlignmentLeft, YES); + return CGSizeMake(ceilf(size.width), ceilf(size.height)); +} + +- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode + numberOfLines:(NSUInteger)numberOfLines { + size = drawOrSizeTextConstrainedToSize(NO, self, attributeRunForFont(font), size, numberOfLines, lineBreakMode, UITextAlignmentLeft, YES); + return CGSizeMake(ceilf(size.width), ceilf(size.height)); +} + +- (CGSize)drawAtPoint:(CGPoint)point withZFont:(ZFont *)font { + return [self drawAtPoint:point forWidth:CGFLOAT_MAX withZFont:font lineBreakMode:UILineBreakModeClip]; +} + +- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode { + return drawTextInRect((CGRect){ point, { width, CGFLOAT_MAX } }, self, attributeRunForFont(font), lineBreakMode, UITextAlignmentLeft, 1, YES); +} + +- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font { + return [self drawInRect:rect withZFont:font lineBreakMode:UILineBreakModeWordWrap]; +} + +- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode { + return [self drawInRect:rect withZFont:font lineBreakMode:lineBreakMode alignment:UITextAlignmentLeft]; +} + +- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode + alignment:(UITextAlignment)alignment { + return drawTextInRect(rect, self, attributeRunForFont(font), lineBreakMode, alignment, 0, YES); +} + +- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode + alignment:(UITextAlignment)alignment numberOfLines:(NSUInteger)numberOfLines { + return drawTextInRect(rect, self, attributeRunForFont(font), lineBreakMode, alignment, numberOfLines, YES); +} +@end + +@implementation ZAttributedString (ZAttributedStringDrawing) +- (CGSize)size { + CGSize size = drawOrSizeTextConstrainedToSize(NO, self.string, self.attributes, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), 1, + UILineBreakModeClip, UITextAlignmentLeft, NO); + return CGSizeMake(ceilf(size.width), ceilf(size.height)); +} + +- (CGSize)sizeConstrainedToSize:(CGSize)size { + return [self sizeConstrainedToSize:size lineBreakMode:UILineBreakModeWordWrap]; +} + +- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode { + size = drawOrSizeTextConstrainedToSize(NO, self.string, self.attributes, size, 0, lineBreakMode, UITextAlignmentLeft, NO); + return CGSizeMake(ceilf(size.width), ceilf(size.height)); +} + +- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode + numberOfLines:(NSUInteger)numberOfLines { + size = drawOrSizeTextConstrainedToSize(NO, self.string, self.attributes, size, numberOfLines, lineBreakMode, UITextAlignmentLeft, NO); + return CGSizeMake(ceilf(size.width), ceilf(size.height)); +} + +- (CGSize)drawAtPoint:(CGPoint)point { + return [self drawAtPoint:point forWidth:CGFLOAT_MAX lineBreakMode:UILineBreakModeClip]; +} + +- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode { + return drawTextInRect((CGRect){ point, { width, CGFLOAT_MAX } }, self.string, self.attributes, lineBreakMode, UITextAlignmentLeft, 1, NO); +} + +- (CGSize)drawInRect:(CGRect)rect { + return [self drawInRect:rect withLineBreakMode:UILineBreakModeWordWrap]; +} + +- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode { + return [self drawInRect:rect withLineBreakMode:lineBreakMode alignment:UITextAlignmentLeft]; +} + +- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment { + return drawTextInRect(rect, self.string, self.attributes, lineBreakMode, alignment, 0, NO); +} + +- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment + numberOfLines:(NSUInteger)numberOfLines { + return drawTextInRect(rect, self.string, self.attributes, lineBreakMode, alignment, numberOfLines, NO); +} +@end diff --git a/libs/FontLabel/FontManager.h b/libs/FontLabel/FontManager.h new file mode 100644 index 0000000..1592b8a --- /dev/null +++ b/libs/FontLabel/FontManager.h @@ -0,0 +1,85 @@ +// +// FontManager.h +// FontLabel +// +// Created by Kevin Ballard on 5/5/09. +// Copyright © 2009 Zynga Game Networks +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +@class ZFont; + +@interface FontManager : NSObject { + CFMutableDictionaryRef fonts; + NSMutableDictionary *urls; +} ++ (FontManager *)sharedManager; +/*! + @method + @abstract Loads a TTF font from the main bundle + @param filename The name of the font file to load (with or without extension). + @return YES if the font was loaded, NO if an error occurred + @discussion If the font has already been loaded, this method does nothing and returns YES. + This method first attempts to load the font by appending .ttf to the filename. + If that file does not exist, it tries the filename exactly as given. +*/ +- (BOOL)loadFont:(NSString *)filename; +/*! + @method + @abstract Loads a font from the given file URL + @param url A file URL that points to a font file + @return YES if the font was loaded, NO if an error occurred + @discussion If the font has already been loaded, this method does nothing and returns YES. +*/ +- (BOOL)loadFontURL:(NSURL *)url; +/*! + @method + @abstract Returns the loaded font with the given filename + @param filename The name of the font file that was given to -loadFont: + @return A CGFontRef, or NULL if the specified font cannot be found + @discussion If the font has not been loaded yet, -loadFont: will be + called with the given name first. +*/ +- (CGFontRef)fontWithName:(NSString *)filename __AVAILABILITY_INTERNAL_DEPRECATED; +/*! + @method + @abstract Returns a ZFont object corresponding to the loaded font with the given filename and point size + @param filename The name of the font file that was given to -loadFont: + @param pointSize The point size of the font + @return A ZFont, or NULL if the specified font cannot be found + @discussion If the font has not been loaded yet, -loadFont: will be + called with the given name first. +*/ +- (ZFont *)zFontWithName:(NSString *)filename pointSize:(CGFloat)pointSize; +/*! + @method + @abstract Returns a ZFont object corresponding to the loaded font with the given file URL and point size + @param url A file URL that points to a font file + @param pointSize The point size of the font + @return A ZFont, or NULL if the specified font cannot be loaded + @discussion If the font has not been loaded yet, -loadFontURL: will be called with the given URL first. +*/ +- (ZFont *)zFontWithURL:(NSURL *)url pointSize:(CGFloat)pointSize; +/*! + @method + @abstract Returns a CFArrayRef of all loaded CGFont objects + @return A CFArrayRef of all loaded CGFont objects + @description You are responsible for releasing the CFArrayRef +*/ +- (CFArrayRef)copyAllFonts; +@end diff --git a/libs/FontLabel/FontManager.m b/libs/FontLabel/FontManager.m new file mode 100644 index 0000000..12eac2d --- /dev/null +++ b/libs/FontLabel/FontManager.m @@ -0,0 +1,123 @@ +// +// FontManager.m +// FontLabel +// +// Created by Kevin Ballard on 5/5/09. +// Copyright © 2009 Zynga Game Networks +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "FontManager.h" +#import "ZFont.h" + +static FontManager *sharedFontManager = nil; + +@implementation FontManager ++ (FontManager *)sharedManager { + @synchronized(self) { + if (sharedFontManager == nil) { + sharedFontManager = [[self alloc] init]; + } + } + return sharedFontManager; +} + +- (id)init { + if ((self = [super init])) { + fonts = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + urls = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (BOOL)loadFont:(NSString *)filename { + NSString *fontPath = [[NSBundle mainBundle] pathForResource:filename ofType:@"ttf"]; + if (fontPath == nil) { + fontPath = [[NSBundle mainBundle] pathForResource:filename ofType:nil]; + } + if (fontPath == nil) return NO; + + NSURL *url = [NSURL fileURLWithPath:fontPath]; + if ([self loadFontURL:url]) { + [urls setObject:url forKey:filename]; + return YES; + } + return NO; +} + +- (BOOL)loadFontURL:(NSURL *)url { + CGDataProviderRef fontDataProvider = CGDataProviderCreateWithURL((CFURLRef)url); + if (fontDataProvider == NULL) return NO; + CGFontRef newFont = CGFontCreateWithDataProvider(fontDataProvider); + CGDataProviderRelease(fontDataProvider); + if (newFont == NULL) return NO; + + CFDictionarySetValue(fonts, url, newFont); + CGFontRelease(newFont); + return YES; +} + +- (CGFontRef)fontWithName:(NSString *)filename { + CGFontRef font = NULL; + NSURL *url = [urls objectForKey:filename]; + if (url == nil && [self loadFont:filename]) { + url = [urls objectForKey:filename]; + } + if (url != nil) { + font = (CGFontRef)CFDictionaryGetValue(fonts, url); + } + return font; +} + +- (ZFont *)zFontWithName:(NSString *)filename pointSize:(CGFloat)pointSize { + NSURL *url = [urls objectForKey:filename]; + if (url == nil && [self loadFont:filename]) { + url = [urls objectForKey:filename]; + } + if (url != nil) { + CGFontRef cgFont = (CGFontRef)CFDictionaryGetValue(fonts, url); + if (cgFont != NULL) { + return [ZFont fontWithCGFont:cgFont size:pointSize]; + } + } + return nil; +} + +- (ZFont *)zFontWithURL:(NSURL *)url pointSize:(CGFloat)pointSize { + CGFontRef cgFont = (CGFontRef)CFDictionaryGetValue(fonts, url); + if (cgFont == NULL && [self loadFontURL:url]) { + cgFont = (CGFontRef)CFDictionaryGetValue(fonts, url); + } + if (cgFont != NULL) { + return [ZFont fontWithCGFont:cgFont size:pointSize]; + } + return nil; +} + +- (CFArrayRef)copyAllFonts { + CFIndex count = CFDictionaryGetCount(fonts); + CGFontRef *values = (CGFontRef *)malloc(sizeof(CGFontRef) * count); + CFDictionaryGetKeysAndValues(fonts, NULL, (const void **)values); + CFArrayRef array = CFArrayCreate(NULL, (const void **)values, count, &kCFTypeArrayCallBacks); + free(values); + return array; +} + +- (void)dealloc { + CFRelease(fonts); + [urls release]; + [super dealloc]; +} +@end diff --git a/libs/FontLabel/ZAttributedString.h b/libs/FontLabel/ZAttributedString.h new file mode 100644 index 0000000..e194c81 --- /dev/null +++ b/libs/FontLabel/ZAttributedString.h @@ -0,0 +1,77 @@ +// +// ZAttributedString.h +// FontLabel +// +// Created by Kevin Ballard on 9/22/09. +// Copyright 2009 Zynga Game Networks. All rights reserved. +// + +#import + +#if NS_BLOCKS_AVAILABLE +#define Z_BLOCKS 1 +#else +// set this to 1 if you are using PLBlocks +#define Z_BLOCKS 0 +#endif + +#if Z_BLOCKS +enum { + ZAttributedStringEnumerationReverse = (1UL << 1), + ZAttributedStringEnumerationLongestEffectiveRangeNotRequired = (1UL << 20) +}; +typedef NSUInteger ZAttributedStringEnumerationOptions; +#endif + +@interface ZAttributedString : NSObject { + NSMutableString *_buffer; + NSMutableArray *_attributes; +} +@property (nonatomic, readonly) NSUInteger length; +@property (nonatomic, readonly) NSString *string; +- (id)initWithAttributedString:(ZAttributedString *)attr; +- (id)initWithString:(NSString *)str; +- (id)initWithString:(NSString *)str attributes:(NSDictionary *)attributes; +- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange; +- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit; +- (ZAttributedString *)attributedSubstringFromRange:(NSRange)aRange; +- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange; +- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit; +#if Z_BLOCKS +- (void)enumerateAttribute:(NSString *)attrName inRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts + usingBlock:(void (^)(id value, NSRange range, BOOL *stop))block; +- (void)enumerateAttributesInRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts + usingBlock:(void (^)(NSDictionary *attrs, NSRange range, BOOL *stop))block; +#endif +- (BOOL)isEqualToAttributedString:(ZAttributedString *)otherString; +@end + +@interface ZMutableAttributedString : ZAttributedString { +} +- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)range; +- (void)addAttributes:(NSDictionary *)attributes range:(NSRange)range; +- (void)appendAttributedString:(ZAttributedString *)str; +- (void)deleteCharactersInRange:(NSRange)range; +- (void)insertAttributedString:(ZAttributedString *)str atIndex:(NSUInteger)idx; +- (void)removeAttribute:(NSString *)name range:(NSRange)range; +- (void)replaceCharactersInRange:(NSRange)range withAttributedString:(ZAttributedString *)str; +- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str; +- (void)setAttributedString:(ZAttributedString *)str; +- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range; +@end + +extern NSString * const ZFontAttributeName; +extern NSString * const ZForegroundColorAttributeName; +extern NSString * const ZBackgroundColorAttributeName; +extern NSString * const ZUnderlineStyleAttributeName; + +enum { + ZUnderlineStyleNone = 0x00, + ZUnderlineStyleSingle = 0x01 +}; +#define ZUnderlineStyleMask 0x00FF + +enum { + ZUnderlinePatternSolid = 0x0000 +}; +#define ZUnderlinePatternMask 0xFF00 diff --git a/libs/FontLabel/ZAttributedString.m b/libs/FontLabel/ZAttributedString.m new file mode 100644 index 0000000..79f0323 --- /dev/null +++ b/libs/FontLabel/ZAttributedString.m @@ -0,0 +1,596 @@ +// +// ZAttributedString.m +// FontLabel +// +// Created by Kevin Ballard on 9/22/09. +// Copyright 2009 Zynga Game Networks. All rights reserved. +// + +#import "ZAttributedString.h" +#import "ZAttributedStringPrivate.h" + +@interface ZAttributedString () +- (NSUInteger)indexOfEffectiveAttributeRunForIndex:(NSUInteger)index; +- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange uniquingOnName:(NSString *)attributeName; +- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange + inRange:(NSRange)rangeLimit uniquingOnName:(NSString *)attributeName; +@end + +@interface ZAttributedString () +@property (nonatomic, readonly) NSArray *attributes; +@end + +@implementation ZAttributedString +@synthesize string = _buffer; +@synthesize attributes = _attributes; + +- (id)initWithAttributedString:(ZAttributedString *)attr { + NSParameterAssert(attr != nil); + if ((self = [super init])) { + _buffer = [attr->_buffer mutableCopy]; + _attributes = [[NSMutableArray alloc] initWithArray:attr->_attributes copyItems:YES]; + } + return self; +} + +- (id)initWithString:(NSString *)str { + return [self initWithString:str attributes:nil]; +} + +- (id)initWithString:(NSString *)str attributes:(NSDictionary *)attributes { + if ((self = [super init])) { + _buffer = [str mutableCopy]; + _attributes = [[NSMutableArray alloc] initWithObjects:[ZAttributeRun attributeRunWithIndex:0 attributes:attributes], nil]; + } + return self; +} + +- (id)init { + return [self initWithString:@"" attributes:nil]; +} + +- (id)initWithCoder:(NSCoder *)decoder { + if ((self = [super init])) { + _buffer = [[decoder decodeObjectForKey:@"buffer"] mutableCopy]; + _attributes = [[decoder decodeObjectForKey:@"attributes"] mutableCopy]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_buffer forKey:@"buffer"]; + [aCoder encodeObject:_attributes forKey:@"attributes"]; +} + +- (id)copyWithZone:(NSZone *)zone { + return [self retain]; +} + +- (id)mutableCopyWithZone:(NSZone *)zone { + return [(ZMutableAttributedString *)[ZMutableAttributedString allocWithZone:zone] initWithAttributedString:self]; +} + +- (NSUInteger)length { + return [_buffer length]; +} + +- (NSString *)description { + NSMutableArray *components = [NSMutableArray arrayWithCapacity:[_attributes count]*2]; + NSRange range = NSMakeRange(0, 0); + for (NSUInteger i = 0; i <= [_attributes count]; i++) { + range.location = NSMaxRange(range); + ZAttributeRun *run; + if (i < [_attributes count]) { + run = [_attributes objectAtIndex:i]; + range.length = run.index - range.location; + } else { + run = nil; + range.length = [_buffer length] - range.location; + } + if (range.length > 0) { + [components addObject:[NSString stringWithFormat:@"\"%@\"", [_buffer substringWithRange:range]]]; + } + if (run != nil) { + NSMutableArray *attrDesc = [NSMutableArray arrayWithCapacity:[run.attributes count]]; + for (id key in run.attributes) { + [attrDesc addObject:[NSString stringWithFormat:@"%@: %@", key, [run.attributes objectForKey:key]]]; + } + [components addObject:[NSString stringWithFormat:@"{%@}", [attrDesc componentsJoinedByString:@", "]]]; + } + } + return [NSString stringWithFormat:@"%@", [components componentsJoinedByString:@" "]]; +} + +- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange { + NSParameterAssert(attributeName != nil); + return [[self attributesAtIndex:index effectiveRange:aRange uniquingOnName:attributeName] objectForKey:attributeName]; +} + +- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit { + NSParameterAssert(attributeName != nil); + return [[self attributesAtIndex:index longestEffectiveRange:aRange inRange:rangeLimit uniquingOnName:attributeName] objectForKey:attributeName]; +} + +- (ZAttributedString *)attributedSubstringFromRange:(NSRange)aRange { + if (NSMaxRange(aRange) > [_buffer length]) { + @throw [NSException exceptionWithName:NSRangeException reason:@"range was outisde of the attributed string" userInfo:nil]; + } + ZMutableAttributedString *newStr = [self mutableCopy]; + if (aRange.location > 0) { + [newStr deleteCharactersInRange:NSMakeRange(0, aRange.location)]; + } + if (NSMaxRange(aRange) < [_buffer length]) { + [newStr deleteCharactersInRange:NSMakeRange(aRange.length, [_buffer length] - NSMaxRange(aRange))]; + } + return [newStr autorelease]; +} + +- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange { + return [NSDictionary dictionaryWithDictionary:[self attributesAtIndex:index effectiveRange:aRange uniquingOnName:nil]]; +} + +- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit { + return [NSDictionary dictionaryWithDictionary:[self attributesAtIndex:index longestEffectiveRange:aRange inRange:rangeLimit uniquingOnName:nil]]; +} + +#if Z_BLOCKS +// Warning: this code has not been tested. The only guarantee is that it compiles. +- (void)enumerateAttribute:(NSString *)attrName inRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts + usingBlock:(void (^)(id, NSRange, BOOL*))block { + if (opts & ZAttributedStringEnumerationLongestEffectiveRangeNotRequired) { + [self enumerateAttributesInRange:enumerationRange options:opts usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) { + id value = [attrs objectForKey:attrName]; + if (value != nil) { + block(value, range, stop); + } + }]; + } else { + __block id oldValue = nil; + __block NSRange effectiveRange = NSMakeRange(0, 0); + [self enumerateAttributesInRange:enumerationRange options:opts usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) { + id value = [attrs objectForKey:attrName]; + if (oldValue == nil) { + oldValue = value; + effectiveRange = range; + } else if (value != nil && [oldValue isEqual:value]) { + // combine the attributes + effectiveRange = NSUnionRange(effectiveRange, range); + } else { + BOOL innerStop = NO; + block(oldValue, effectiveRange, &innerStop); + if (innerStop) { + *stop = YES; + oldValue = nil; + } else { + oldValue = value; + } + } + }]; + if (oldValue != nil) { + BOOL innerStop = NO; // necessary for the block, but unused + block(oldValue, effectiveRange, &innerStop); + } + } +} + +- (void)enumerateAttributesInRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts + usingBlock:(void (^)(NSDictionary*, NSRange, BOOL*))block { + // copy the attributes so we can mutate the string if necessary during enumeration + // also clip the array during copy to only the subarray of attributes that cover the requested range + NSArray *attrs; + if (NSEqualRanges(enumerationRange, NSMakeRange(0, 0))) { + attrs = [NSArray arrayWithArray:_attributes]; + } else { + // in this binary search, last is the first run after the range + NSUInteger first = 0, last = [_attributes count]; + while (last > first+1) { + NSUInteger pivot = (last + first) / 2; + ZAttributeRun *run = [_attributes objectAtIndex:pivot]; + if (run.index < enumerationRange.location) { + first = pivot; + } else if (run.index >= NSMaxRange(enumerationRange)) { + last = pivot; + } + } + attrs = [_attributes subarrayWithRange:NSMakeRange(first, last-first)]; + } + if (opts & ZAttributedStringEnumerationReverse) { + NSUInteger end = [_buffer length]; + for (ZAttributeRun *run in [attrs reverseObjectEnumerator]) { + BOOL stop = NO; + NSUInteger start = run.index; + // clip to enumerationRange + start = MAX(start, enumerationRange.location); + end = MIN(end, NSMaxRange(enumerationRange)); + block(run.attributes, NSMakeRange(start, end - start), &stop); + if (stop) break; + end = run.index; + } + } else { + NSUInteger start = 0; + ZAttributeRun *run = [attrs objectAtIndex:0]; + NSInteger offset = 0; + NSInteger oldLength = [_buffer length]; + for (NSUInteger i = 1;;i++) { + NSUInteger end; + if (i >= [attrs count]) { + end = oldLength; + } else { + end = [[attrs objectAtIndex:i] index]; + } + BOOL stop = NO; + NSUInteger clippedStart = MAX(start, enumerationRange.location); + NSUInteger clippedEnd = MIN(end, NSMaxRange(enumerationRange)); + block(run.attributes, NSMakeRange(clippedStart + offset, clippedEnd - start), &stop); + if (stop || i >= [attrs count]) break; + start = end; + NSUInteger newLength = [_buffer length]; + offset += (newLength - oldLength); + oldLength = newLength; + } + } +} +#endif + +- (BOOL)isEqualToAttributedString:(ZAttributedString *)otherString { + return ([_buffer isEqualToString:otherString->_buffer] && [_attributes isEqualToArray:otherString->_attributes]); +} + +- (BOOL)isEqual:(id)object { + return [object isKindOfClass:[ZAttributedString class]] && [self isEqualToAttributedString:(ZAttributedString *)object]; +} + +#pragma mark - + +- (NSUInteger)indexOfEffectiveAttributeRunForIndex:(NSUInteger)index { + NSUInteger first = 0, last = [_attributes count]; + while (last > first + 1) { + NSUInteger pivot = (last + first) / 2; + ZAttributeRun *run = [_attributes objectAtIndex:pivot]; + if (run.index > index) { + last = pivot; + } else if (run.index < index) { + first = pivot; + } else { + first = pivot; + break; + } + } + return first; +} + +- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange uniquingOnName:(NSString *)attributeName { + if (index >= [_buffer length]) { + @throw [NSException exceptionWithName:NSRangeException reason:@"index beyond range of attributed string" userInfo:nil]; + } + NSUInteger runIndex = [self indexOfEffectiveAttributeRunForIndex:index]; + ZAttributeRun *run = [_attributes objectAtIndex:runIndex]; + if (aRange != NULL) { + aRange->location = run.index; + runIndex++; + if (runIndex < [_attributes count]) { + aRange->length = [[_attributes objectAtIndex:runIndex] index] - aRange->location; + } else { + aRange->length = [_buffer length] - aRange->location; + } + } + return run.attributes; +} +- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange + inRange:(NSRange)rangeLimit uniquingOnName:(NSString *)attributeName { + if (index >= [_buffer length]) { + @throw [NSException exceptionWithName:NSRangeException reason:@"index beyond range of attributed string" userInfo:nil]; + } else if (NSMaxRange(rangeLimit) > [_buffer length]) { + @throw [NSException exceptionWithName:NSRangeException reason:@"rangeLimit beyond range of attributed string" userInfo:nil]; + } + NSUInteger runIndex = [self indexOfEffectiveAttributeRunForIndex:index]; + ZAttributeRun *run = [_attributes objectAtIndex:runIndex]; + if (aRange != NULL) { + if (attributeName != nil) { + id value = [run.attributes objectForKey:attributeName]; + NSUInteger endRunIndex = runIndex+1; + runIndex--; + // search backwards + while (1) { + if (run.index <= rangeLimit.location) { + break; + } + ZAttributeRun *prevRun = [_attributes objectAtIndex:runIndex]; + id prevValue = [prevRun.attributes objectForKey:attributeName]; + if (prevValue == value || (value != nil && [prevValue isEqual:value])) { + runIndex--; + run = prevRun; + } else { + break; + } + } + // search forwards + ZAttributeRun *endRun = nil; + while (endRunIndex < [_attributes count]) { + ZAttributeRun *nextRun = [_attributes objectAtIndex:endRunIndex]; + if (nextRun.index >= NSMaxRange(rangeLimit)) { + endRun = nextRun; + break; + } + id nextValue = [nextRun.attributes objectForKey:attributeName]; + if (nextValue == value || (value != nil && [nextValue isEqual:value])) { + endRunIndex++; + } else { + endRun = nextRun; + break; + } + } + aRange->location = MAX(run.index, rangeLimit.location); + aRange->length = MIN((endRun ? endRun.index : [_buffer length]), NSMaxRange(rangeLimit)) - aRange->location; + } else { + // with no attribute name, we don't need to do any real searching, + // as we already guarantee each run has unique attributes. + // just make sure to clip the range to the rangeLimit + aRange->location = MAX(run.index, rangeLimit.location); + ZAttributeRun *endRun = (runIndex+1 < [_attributes count] ? [_attributes objectAtIndex:runIndex+1] : nil); + aRange->length = MIN((endRun ? endRun.index : [_buffer length]), NSMaxRange(rangeLimit)) - aRange->location; + } + } + return run.attributes; +} + +- (void)dealloc { + [_buffer release]; + [_attributes release]; + [super dealloc]; +} +@end + +@interface ZMutableAttributedString () +- (void)cleanupAttributesInRange:(NSRange)range; +- (NSRange)rangeOfAttributeRunsForRange:(NSRange)range; +- (void)offsetRunsInRange:(NSRange )range byOffset:(NSInteger)offset; +@end + +@implementation ZMutableAttributedString +- (id)copyWithZone:(NSZone *)zone { + return [(ZAttributedString *)[ZAttributedString allocWithZone:zone] initWithAttributedString:self]; +} + +- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)range { + range = [self rangeOfAttributeRunsForRange:range]; + for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) { + [run.attributes setObject:value forKey:name]; + } + [self cleanupAttributesInRange:range]; +} + +- (void)addAttributes:(NSDictionary *)attributes range:(NSRange)range { + range = [self rangeOfAttributeRunsForRange:range]; + for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) { + [run.attributes addEntriesFromDictionary:attributes]; + } + [self cleanupAttributesInRange:range]; +} + +- (void)appendAttributedString:(ZAttributedString *)str { + [self insertAttributedString:str atIndex:[_buffer length]]; +} + +- (void)deleteCharactersInRange:(NSRange)range { + NSRange runRange = [self rangeOfAttributeRunsForRange:range]; + [_buffer replaceCharactersInRange:range withString:@""]; + [_attributes removeObjectsInRange:runRange]; + for (NSUInteger i = runRange.location; i < [_attributes count]; i++) { + ZAttributeRun *run = [_attributes objectAtIndex:i]; + ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:(run.index - range.length) attributes:run.attributes]; + [_attributes replaceObjectAtIndex:i withObject:newRun]; + [newRun release]; + } + [self cleanupAttributesInRange:NSMakeRange(runRange.location, 0)]; +} + +- (void)insertAttributedString:(ZAttributedString *)str atIndex:(NSUInteger)idx { + [self replaceCharactersInRange:NSMakeRange(idx, 0) withAttributedString:str]; +} + +- (void)removeAttribute:(NSString *)name range:(NSRange)range { + range = [self rangeOfAttributeRunsForRange:range]; + for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) { + [run.attributes removeObjectForKey:name]; + } + [self cleanupAttributesInRange:range]; +} + +- (void)replaceCharactersInRange:(NSRange)range withAttributedString:(ZAttributedString *)str { + NSRange replaceRange = [self rangeOfAttributeRunsForRange:range]; + NSInteger offset = [str->_buffer length] - range.length; + [_buffer replaceCharactersInRange:range withString:str->_buffer]; + [_attributes replaceObjectsInRange:replaceRange withObjectsFromArray:str->_attributes]; + NSRange newRange = NSMakeRange(replaceRange.location, [str->_attributes count]); + [self offsetRunsInRange:newRange byOffset:range.location]; + [self offsetRunsInRange:NSMakeRange(NSMaxRange(newRange), [_attributes count] - NSMaxRange(newRange)) byOffset:offset]; + [self cleanupAttributesInRange:NSMakeRange(newRange.location, 0)]; + [self cleanupAttributesInRange:NSMakeRange(NSMaxRange(newRange), 0)]; +} + +- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str { + [self replaceCharactersInRange:range withAttributedString:[[[ZAttributedString alloc] initWithString:str] autorelease]]; +} + +- (void)setAttributedString:(ZAttributedString *)str { + [_buffer release], _buffer = [str->_buffer mutableCopy]; + [_attributes release], _attributes = [str->_attributes mutableCopy]; +} + +- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range { + range = [self rangeOfAttributeRunsForRange:range]; + for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) { + [run.attributes setDictionary:attributes]; + } + [self cleanupAttributesInRange:range]; +} + +#pragma mark - + +// splits the existing runs to provide one or more new runs for the given range +- (NSRange)rangeOfAttributeRunsForRange:(NSRange)range { + NSParameterAssert(NSMaxRange(range) <= [_buffer length]); + + // find (or create) the first run + NSUInteger first = 0; + ZAttributeRun *lastRun = nil; + for (;;first++) { + if (first >= [_attributes count]) { + // we didn't find a run + first = [_attributes count]; + ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:range.location attributes:lastRun.attributes]; + [_attributes addObject:newRun]; + [newRun release]; + break; + } + ZAttributeRun *run = [_attributes objectAtIndex:first]; + if (run.index == range.location) { + break; + } else if (run.index > range.location) { + ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:range.location attributes:lastRun.attributes]; + [_attributes insertObject:newRun atIndex:first]; + [newRun release]; + break; + } + lastRun = run; + } + + if (((ZAttributeRun *)[_attributes lastObject]).index < NSMaxRange(range)) { + NSRange subrange = NSMakeRange(first, [_attributes count] - first); + if (NSMaxRange(range) < [_buffer length]) { + ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:NSMaxRange(range) attributes:[[_attributes lastObject] attributes]]; + [_attributes addObject:newRun]; + [newRun release]; + } + return subrange; + } else { + // find the last run within and the first run after the range + NSUInteger lastIn = first, firstAfter = [_attributes count]-1; + while (firstAfter > lastIn + 1) { + NSUInteger idx = (firstAfter + lastIn) / 2; + ZAttributeRun *run = [_attributes objectAtIndex:idx]; + if (run.index < range.location) { + lastIn = idx; + } else if (run.index > range.location) { + firstAfter = idx; + } else { + // this is definitively the first run after the range + firstAfter = idx; + break; + } + } + if ([[_attributes objectAtIndex:firstAfter] index] > NSMaxRange(range)) { + // the first after is too far after, insert another run! + ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:NSMaxRange(range) + attributes:[[_attributes objectAtIndex:firstAfter-1] attributes]]; + [_attributes insertObject:newRun atIndex:firstAfter]; + [newRun release]; + } + return NSMakeRange(lastIn, firstAfter - lastIn); + } +} + +- (void)cleanupAttributesInRange:(NSRange)range { + // expand the range to include one surrounding attribute on each side + if (range.location > 0) { + range.location -= 1; + range.length += 1; + } + if (NSMaxRange(range) < [_attributes count]) { + range.length += 1; + } else { + // make sure the range is capped to the attributes count + range.length = [_attributes count] - range.location; + } + if (range.length == 0) return; + ZAttributeRun *lastRun = [_attributes objectAtIndex:range.location]; + for (NSUInteger i = range.location+1; i < NSMaxRange(range);) { + ZAttributeRun *run = [_attributes objectAtIndex:i]; + if ([lastRun.attributes isEqualToDictionary:run.attributes]) { + [_attributes removeObjectAtIndex:i]; + range.length -= 1; + } else { + lastRun = run; + i++; + } + } +} + +- (void)offsetRunsInRange:(NSRange)range byOffset:(NSInteger)offset { + for (NSUInteger i = range.location; i < NSMaxRange(range); i++) { + ZAttributeRun *run = [_attributes objectAtIndex:i]; + ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:run.index + offset attributes:run.attributes]; + [_attributes replaceObjectAtIndex:i withObject:newRun]; + [newRun release]; + } +} +@end + +@implementation ZAttributeRun +@synthesize index = _index; +@synthesize attributes = _attributes; + ++ (id)attributeRunWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs { + return [[[self alloc] initWithIndex:idx attributes:attrs] autorelease]; +} + +- (id)initWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs { + NSParameterAssert(idx >= 0); + if ((self = [super init])) { + _index = idx; + if (attrs == nil) { + _attributes = [[NSMutableDictionary alloc] init]; + } else { + _attributes = [attrs mutableCopy]; + } + } + return self; +} + +- (id)initWithCoder:(NSCoder *)decoder { + if ((self = [super init])) { + _index = [[decoder decodeObjectForKey:@"index"] unsignedIntegerValue]; + _attributes = [[decoder decodeObjectForKey:@"attributes"] mutableCopy]; + } + return self; +} + +- (id)init { + return [self initWithIndex:0 attributes:[NSDictionary dictionary]]; +} + +- (id)copyWithZone:(NSZone *)zone { + return [[ZAttributeRun allocWithZone:zone] initWithIndex:_index attributes:_attributes]; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:_index] forKey:@"index"]; + [aCoder encodeObject:_attributes forKey:@"attributes"]; +} + +- (NSString *)description { + NSMutableArray *components = [NSMutableArray arrayWithCapacity:[_attributes count]]; + for (id key in _attributes) { + [components addObject:[NSString stringWithFormat:@"%@=%@", key, [_attributes objectForKey:key]]]; + } + return [NSString stringWithFormat:@"<%@: %p index=%lu attributes={%@}>", + NSStringFromClass([self class]), self, (unsigned long)_index, [components componentsJoinedByString:@" "]]; +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[ZAttributeRun class]]) return NO; + ZAttributeRun *other = (ZAttributeRun *)object; + return _index == other->_index && [_attributes isEqualToDictionary:other->_attributes]; +} + +- (void)dealloc { + [_attributes release]; + [super dealloc]; +} +@end + +NSString * const ZFontAttributeName = @"ZFontAttributeName"; +NSString * const ZForegroundColorAttributeName = @"ZForegroundColorAttributeName"; +NSString * const ZBackgroundColorAttributeName = @"ZBackgroundColorAttributeName"; +NSString * const ZUnderlineStyleAttributeName = @"ZUnderlineStyleAttributeName"; diff --git a/libs/FontLabel/ZAttributedStringPrivate.h b/libs/FontLabel/ZAttributedStringPrivate.h new file mode 100644 index 0000000..1021d7b --- /dev/null +++ b/libs/FontLabel/ZAttributedStringPrivate.h @@ -0,0 +1,24 @@ +// +// ZAttributedStringPrivate.h +// FontLabel +// +// Created by Kevin Ballard on 9/23/09. +// Copyright 2009 Zynga Game Networks. All rights reserved. +// + +#import +#import "ZAttributedString.h" + +@interface ZAttributeRun : NSObject { + NSUInteger _index; + NSMutableDictionary *_attributes; +} +@property (nonatomic, readonly) NSUInteger index; +@property (nonatomic, readonly) NSMutableDictionary *attributes; ++ (id)attributeRunWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs; +- (id)initWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs; +@end + +@interface ZAttributedString (ZAttributedStringPrivate) +@property (nonatomic, readonly) NSArray *attributes; +@end diff --git a/libs/FontLabel/ZFont.h b/libs/FontLabel/ZFont.h new file mode 100644 index 0000000..05ae823 --- /dev/null +++ b/libs/FontLabel/ZFont.h @@ -0,0 +1,47 @@ +// +// ZFont.h +// FontLabel +// +// Created by Kevin Ballard on 7/2/09. +// Copyright © 2009 Zynga Game Networks +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +@interface ZFont : NSObject { + CGFontRef _cgFont; + CGFloat _pointSize; + CGFloat _ratio; + NSString *_familyName; + NSString *_fontName; + NSString *_postScriptName; +} +@property (nonatomic, readonly) CGFontRef cgFont; +@property (nonatomic, readonly) CGFloat pointSize; +@property (nonatomic, readonly) CGFloat ascender; +@property (nonatomic, readonly) CGFloat descender; +@property (nonatomic, readonly) CGFloat leading; +@property (nonatomic, readonly) CGFloat xHeight; +@property (nonatomic, readonly) CGFloat capHeight; +@property (nonatomic, readonly) NSString *familyName; +@property (nonatomic, readonly) NSString *fontName; +@property (nonatomic, readonly) NSString *postScriptName; ++ (ZFont *)fontWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize; ++ (ZFont *)fontWithUIFont:(UIFont *)uiFont; +- (id)initWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize; +- (ZFont *)fontWithSize:(CGFloat)fontSize; +@end diff --git a/libs/FontLabel/ZFont.m b/libs/FontLabel/ZFont.m new file mode 100644 index 0000000..793b13a --- /dev/null +++ b/libs/FontLabel/ZFont.m @@ -0,0 +1,170 @@ +// +// ZFont.m +// FontLabel +// +// Created by Kevin Ballard on 7/2/09. +// Copyright © 2009 Zynga Game Networks +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ZFont.h" + +@interface ZFont () +@property (nonatomic, readonly) CGFloat ratio; +- (NSString *)copyNameTableEntryForID:(UInt16)nameID; +@end + +@implementation ZFont +@synthesize cgFont=_cgFont, pointSize=_pointSize, ratio=_ratio; + ++ (ZFont *)fontWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize { + return [[[self alloc] initWithCGFont:cgFont size:fontSize] autorelease]; +} + ++ (ZFont *)fontWithUIFont:(UIFont *)uiFont { + NSParameterAssert(uiFont != nil); + CGFontRef cgFont = CGFontCreateWithFontName((CFStringRef)uiFont.fontName); + ZFont *zFont = [[self alloc] initWithCGFont:cgFont size:uiFont.pointSize]; + CGFontRelease(cgFont); + return [zFont autorelease]; +} + +- (id)initWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize { + if ((self = [super init])) { + _cgFont = CGFontRetain(cgFont); + _pointSize = fontSize; + _ratio = fontSize/CGFontGetUnitsPerEm(cgFont); + } + return self; +} + +- (id)init { + NSAssert(NO, @"-init is not valid for ZFont"); + return nil; +} + +- (CGFloat)ascender { + return ceilf(self.ratio * CGFontGetAscent(self.cgFont)); +} + +- (CGFloat)descender { + return floorf(self.ratio * CGFontGetDescent(self.cgFont)); +} + +- (CGFloat)leading { + return (self.ascender - self.descender); +} + +- (CGFloat)capHeight { + return ceilf(self.ratio * CGFontGetCapHeight(self.cgFont)); +} + +- (CGFloat)xHeight { + return ceilf(self.ratio * CGFontGetXHeight(self.cgFont)); +} + +- (NSString *)familyName { + if (_familyName == nil) { + _familyName = [self copyNameTableEntryForID:1]; + } + return _familyName; +} + +- (NSString *)fontName { + if (_fontName == nil) { + _fontName = [self copyNameTableEntryForID:4]; + } + return _fontName; +} + +- (NSString *)postScriptName { + if (_postScriptName == nil) { + _postScriptName = [self copyNameTableEntryForID:6]; + } + return _postScriptName; +} + +- (ZFont *)fontWithSize:(CGFloat)fontSize { + if (fontSize == self.pointSize) return self; + NSParameterAssert(fontSize > 0.0); + return [[[ZFont alloc] initWithCGFont:self.cgFont size:fontSize] autorelease]; +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[ZFont class]]) return NO; + ZFont *font = (ZFont *)object; + return (font.cgFont == self.cgFont && font.pointSize == self.pointSize); +} + +- (NSString *)copyNameTableEntryForID:(UInt16)aNameID { + CFDataRef nameTable = CGFontCopyTableForTag(self.cgFont, 'name'); + NSAssert1(nameTable != NULL, @"CGFontCopyTableForTag returned NULL for 'name' tag in font %@", + [(id)CFCopyDescription(self.cgFont) autorelease]); + const UInt8 * const bytes = CFDataGetBytePtr(nameTable); + NSAssert1(OSReadBigInt16(bytes, 0) == 0, @"name table for font %@ has bad version number", + [(id)CFCopyDescription(self.cgFont) autorelease]); + const UInt16 count = OSReadBigInt16(bytes, 2); + const UInt16 stringOffset = OSReadBigInt16(bytes, 4); + const UInt8 * const nameRecords = &bytes[6]; + UInt16 nameLength = 0; + UInt16 nameOffset = 0; + NSStringEncoding encoding = 0; + for (UInt16 idx = 0; idx < count; idx++) { + const uintptr_t recordOffset = 12 * idx; + const UInt16 nameID = OSReadBigInt16(nameRecords, recordOffset + 6); + if (nameID != aNameID) continue; + const UInt16 platformID = OSReadBigInt16(nameRecords, recordOffset + 0); + const UInt16 platformSpecificID = OSReadBigInt16(nameRecords, recordOffset + 2); + encoding = 0; + // for now, we only support a subset of encodings + switch (platformID) { + case 0: // Unicode + encoding = NSUTF16StringEncoding; + break; + case 1: // Macintosh + switch (platformSpecificID) { + case 0: + encoding = NSMacOSRomanStringEncoding; + break; + } + case 3: // Microsoft + switch (platformSpecificID) { + case 1: + encoding = NSUTF16StringEncoding; + break; + } + } + if (encoding == 0) continue; + nameLength = OSReadBigInt16(nameRecords, recordOffset + 8); + nameOffset = OSReadBigInt16(nameRecords, recordOffset + 10); + break; + } + NSString *result = nil; + if (nameOffset > 0) { + const UInt8 *nameBytes = &bytes[stringOffset + nameOffset]; + result = [[NSString alloc] initWithBytes:nameBytes length:nameLength encoding:encoding]; + } + CFRelease(nameTable); + return result; +} + +- (void)dealloc { + CGFontRelease(_cgFont); + [_familyName release]; + [_fontName release]; + [_postScriptName release]; + [super dealloc]; +} +@end diff --git a/libs/TouchJSON/CDataScanner.h b/libs/TouchJSON/CDataScanner.h new file mode 100644 index 0000000..41f68e8 --- /dev/null +++ b/libs/TouchJSON/CDataScanner.h @@ -0,0 +1,71 @@ +// +// CDataScanner.h +// TouchCode +// +// Created by Jonathan Wight on 04/16/08. +// Copyright 2008 toxicsoftware.com. All rights reserved. +// +// 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 + +// NSScanner + +@interface CDataScanner : NSObject { + NSData *data; + + u_int8_t *start; + u_int8_t *end; + u_int8_t *current; + NSUInteger length; +} + +@property (readwrite, nonatomic, retain) NSData *data; +@property (readwrite, nonatomic, assign) NSUInteger scanLocation; +@property (readonly, nonatomic, assign) NSUInteger bytesRemaining; +@property (readonly, nonatomic, assign) BOOL isAtEnd; + +- (id)initWithData:(NSData *)inData; + +- (unichar)currentCharacter; +- (unichar)scanCharacter; +- (BOOL)scanCharacter:(unichar)inCharacter; + +- (BOOL)scanUTF8String:(const char *)inString intoString:(NSString **)outValue; +- (BOOL)scanString:(NSString *)inString intoString:(NSString **)outValue; +- (BOOL)scanCharactersFromSet:(NSCharacterSet *)inSet intoString:(NSString **)outValue; // inSet must only contain 7-bit ASCII characters + +- (BOOL)scanUpToString:(NSString *)string intoString:(NSString **)outValue; +- (BOOL)scanUpToCharactersFromSet:(NSCharacterSet *)set intoString:(NSString **)outValue; // inSet must only contain 7-bit ASCII characters + +- (BOOL)scanNumber:(NSNumber **)outValue; +- (BOOL)scanDecimalNumber:(NSDecimalNumber **)outValue; + +- (BOOL)scanDataOfLength:(NSUInteger)inLength intoData:(NSData **)outData; + +- (void)skipWhitespace; + +- (NSString *)remainingString; +- (NSData *)remainingData; + +@end diff --git a/libs/TouchJSON/CDataScanner.m b/libs/TouchJSON/CDataScanner.m new file mode 100644 index 0000000..b3cee6f --- /dev/null +++ b/libs/TouchJSON/CDataScanner.m @@ -0,0 +1,340 @@ +// +// CDataScanner.m +// TouchCode +// +// Created by Jonathan Wight on 04/16/08. +// Copyright 2008 toxicsoftware.com. All rights reserved. +// +// 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 "CDataScanner.h" + +#import "CDataScanner_Extensions.h" + +@interface CDataScanner () +@end + +#pragma mark - + +inline static unichar CharacterAtPointer(void *start, void *end) + { + #pragma unused(end) + + const u_int8_t theByte = *(u_int8_t *)start; + if (theByte & 0x80) + { + // TODO -- UNICODE!!!! (well in theory nothing todo here) + } + const unichar theCharacter = theByte; + return(theCharacter); + } + + static NSCharacterSet *sDoubleCharacters = NULL; + + @implementation CDataScanner + +- (id)init + { + if ((self = [super init]) != NULL) + { + } + return(self); + } + +- (id)initWithData:(NSData *)inData; + { + if ((self = [self init]) != NULL) + { + [self setData:inData]; + } + return(self); + } + + + (void)initialize + { + if (sDoubleCharacters == NULL) + { + sDoubleCharacters = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789eE-+."] retain]; + } + } + +- (void)dealloc + { + [data release]; + data = NULL; + // + [super dealloc]; + } + +- (NSUInteger)scanLocation + { + return(current - start); + } + +- (NSUInteger)bytesRemaining + { + return(end - current); + } + +- (NSData *)data + { + return(data); + } + +- (void)setData:(NSData *)inData + { + if (data != inData) + { + [data release]; + data = [inData retain]; + } + + if (data) + { + start = (u_int8_t *)data.bytes; + end = start + data.length; + current = start; + length = data.length; + } + else + { + start = NULL; + end = NULL; + current = NULL; + length = 0; + } + } + +- (void)setScanLocation:(NSUInteger)inScanLocation + { + current = start + inScanLocation; + } + +- (BOOL)isAtEnd + { + return(self.scanLocation >= length); + } + +- (unichar)currentCharacter + { + return(CharacterAtPointer(current, end)); + } + +#pragma mark - + +- (unichar)scanCharacter + { + const unichar theCharacter = CharacterAtPointer(current++, end); + return(theCharacter); + } + +- (BOOL)scanCharacter:(unichar)inCharacter + { + unichar theCharacter = CharacterAtPointer(current, end); + if (theCharacter == inCharacter) + { + ++current; + return(YES); + } + else + return(NO); + } + +- (BOOL)scanUTF8String:(const char *)inString intoString:(NSString **)outValue + { + const size_t theLength = strlen(inString); + if ((size_t)(end - current) < theLength) + return(NO); + if (strncmp((char *)current, inString, theLength) == 0) + { + current += theLength; + if (outValue) + *outValue = [NSString stringWithUTF8String:inString]; + return(YES); + } + return(NO); + } + +- (BOOL)scanString:(NSString *)inString intoString:(NSString **)outValue + { + if ((size_t)(end - current) < inString.length) + return(NO); + if (strncmp((char *)current, [inString UTF8String], inString.length) == 0) + { + current += inString.length; + if (outValue) + *outValue = inString; + return(YES); + } + return(NO); + } + +- (BOOL)scanCharactersFromSet:(NSCharacterSet *)inSet intoString:(NSString **)outValue + { + u_int8_t *P; + for (P = current; P < end && [inSet characterIsMember:*P] == YES; ++P) + ; + + if (P == current) + { + return(NO); + } + + if (outValue) + { + *outValue = [[[NSString alloc] initWithBytes:current length:P - current encoding:NSUTF8StringEncoding] autorelease]; + } + + current = P; + + return(YES); + } + +- (BOOL)scanUpToString:(NSString *)inString intoString:(NSString **)outValue + { + const char *theToken = [inString UTF8String]; + const char *theResult = strnstr((char *)current, theToken, end - current); + if (theResult == NULL) + { + return(NO); + } + + if (outValue) + { + *outValue = [[[NSString alloc] initWithBytes:current length:theResult - (char *)current encoding:NSUTF8StringEncoding] autorelease]; + } + + current = (u_int8_t *)theResult; + + return(YES); + } + +- (BOOL)scanUpToCharactersFromSet:(NSCharacterSet *)inSet intoString:(NSString **)outValue + { + u_int8_t *P; + for (P = current; P < end && [inSet characterIsMember:*P] == NO; ++P) + ; + + if (P == current) + { + return(NO); + } + + if (outValue) + { + *outValue = [[[NSString alloc] initWithBytes:current length:P - current encoding:NSUTF8StringEncoding] autorelease]; + } + + current = P; + + return(YES); + } + +- (BOOL)scanNumber:(NSNumber **)outValue + { + NSString *theString = NULL; + if ([self scanCharactersFromSet:sDoubleCharacters intoString:&theString]) + { + if ([theString rangeOfString:@"."].location != NSNotFound) + { + if (outValue) + { + *outValue = [NSDecimalNumber decimalNumberWithString:theString]; + } + return(YES); + } + else if ([theString rangeOfString:@"-"].location != NSNotFound) + { + if (outValue != NULL) + { + *outValue = [NSNumber numberWithLongLong:[theString longLongValue]]; + } + return(YES); + } + else + { + if (outValue != NULL) + { + *outValue = [NSNumber numberWithUnsignedLongLong:strtoull([theString UTF8String], NULL, 0)]; + } + return(YES); + } + + } + return(NO); + } + +- (BOOL)scanDecimalNumber:(NSDecimalNumber **)outValue; + { + NSString *theString = NULL; + if ([self scanCharactersFromSet:sDoubleCharacters intoString:&theString]) + { + if (outValue) + { + *outValue = [NSDecimalNumber decimalNumberWithString:theString]; + } + return(YES); + } + return(NO); + } + +- (BOOL)scanDataOfLength:(NSUInteger)inLength intoData:(NSData **)outData; + { + if (self.bytesRemaining < inLength) + { + return(NO); + } + + if (outData) + { + *outData = [NSData dataWithBytes:current length:inLength]; + } + + current += inLength; + return(YES); + } + + +- (void)skipWhitespace + { + u_int8_t *P; + for (P = current; P < end && (isspace(*P)); ++P) + ; + + current = P; + } + +- (NSString *)remainingString + { + NSData *theRemainingData = [NSData dataWithBytes:current length:end - current]; + NSString *theString = [[[NSString alloc] initWithData:theRemainingData encoding:NSUTF8StringEncoding] autorelease]; + return(theString); + } + +- (NSData *)remainingData; + { + NSData *theRemainingData = [NSData dataWithBytes:current length:end - current]; + return(theRemainingData); + } + + @end diff --git a/libs/TouchJSON/Extensions/CDataScanner_Extensions.h b/libs/TouchJSON/Extensions/CDataScanner_Extensions.h new file mode 100644 index 0000000..cde1dbb --- /dev/null +++ b/libs/TouchJSON/Extensions/CDataScanner_Extensions.h @@ -0,0 +1,40 @@ +// +// CDataScanner_Extensions.h +// TouchCode +// +// Created by Jonathan Wight on 12/08/2005. +// Copyright 2005 toxicsoftware.com. All rights reserved. +// +// 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 "CDataScanner.h" + +@interface CDataScanner (CDataScanner_Extensions) + +- (BOOL)scanCStyleComment:(NSString **)outComment; +- (BOOL)scanCPlusPlusStyleComment:(NSString **)outComment; + +- (NSUInteger)lineOfScanLocation; +- (NSDictionary *)userInfoForScanLocation; + +@end diff --git a/libs/TouchJSON/Extensions/CDataScanner_Extensions.m b/libs/TouchJSON/Extensions/CDataScanner_Extensions.m new file mode 100644 index 0000000..90dbbda --- /dev/null +++ b/libs/TouchJSON/Extensions/CDataScanner_Extensions.m @@ -0,0 +1,135 @@ +// +// CDataScanner_Extensions.m +// TouchCode +// +// Created by Jonathan Wight on 12/08/2005. +// Copyright 2005 toxicsoftware.com. All rights reserved. +// +// 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 "CDataScanner_Extensions.h" + +#define LF 0x000a // Line Feed +#define FF 0x000c // Form Feed +#define CR 0x000d // Carriage Return +#define NEL 0x0085 // Next Line +#define LS 0x2028 // Line Separator +#define PS 0x2029 // Paragraph Separator + +@implementation CDataScanner (CDataScanner_Extensions) + +- (BOOL)scanCStyleComment:(NSString **)outComment +{ +if ([self scanString:@"/*" intoString:NULL] == YES) + { + NSString *theComment = NULL; + if ([self scanUpToString:@"*/" intoString:&theComment] == NO) + [NSException raise:NSGenericException format:@"Started to scan a C style comment but it wasn't terminated."]; + + if ([theComment rangeOfString:@"/*"].location != NSNotFound) + [NSException raise:NSGenericException format:@"C style comments should not be nested."]; + + if ([self scanString:@"*/" intoString:NULL] == NO) + [NSException raise:NSGenericException format:@"C style comment did not end correctly."]; + + if (outComment != NULL) + *outComment = theComment; + + return(YES); + } +else + { + return(NO); + } +} + +- (BOOL)scanCPlusPlusStyleComment:(NSString **)outComment + { + if ([self scanString:@"//" intoString:NULL] == YES) + { + unichar theCharacters[] = { LF, FF, CR, NEL, LS, PS, }; + NSCharacterSet *theLineBreaksCharacterSet = [NSCharacterSet characterSetWithCharactersInString:[NSString stringWithCharacters:theCharacters length:sizeof(theCharacters) / sizeof(*theCharacters)]]; + + NSString *theComment = NULL; + [self scanUpToCharactersFromSet:theLineBreaksCharacterSet intoString:&theComment]; + [self scanCharactersFromSet:theLineBreaksCharacterSet intoString:NULL]; + + if (outComment != NULL) + *outComment = theComment; + + return(YES); + } + else + { + return(NO); + } + } + +- (NSUInteger)lineOfScanLocation + { + NSUInteger theLine = 0; + for (const u_int8_t *C = start; C < current; ++C) + { + // TODO: JIW What about MS-DOS line endings you bastard! (Also other unicode line endings) + if (*C == '\n' || *C == '\r') + { + ++theLine; + } + } + return(theLine); + } + +- (NSDictionary *)userInfoForScanLocation + { + NSUInteger theLine = 0; + const u_int8_t *theLineStart = start; + for (const u_int8_t *C = start; C < current; ++C) + { + if (*C == '\n' || *C == '\r') + { + theLineStart = C - 1; + ++theLine; + } + } + + NSUInteger theCharacter = current - theLineStart; + + NSRange theStartRange = NSIntersectionRange((NSRange){ .location = MAX((NSInteger)self.scanLocation - 20, 0), .length = 20 + (NSInteger)self.scanLocation - 20 }, (NSRange){ .location = 0, .length = self.data.length }); + NSRange theEndRange = NSIntersectionRange((NSRange){ .location = self.scanLocation, .length = 20 }, (NSRange){ .location = 0, .length = self.data.length }); + + + NSString *theSnippet = [NSString stringWithFormat:@"%@!HERE>!%@", + [[[NSString alloc] initWithData:[self.data subdataWithRange:theStartRange] encoding:NSUTF8StringEncoding] autorelease], + [[[NSString alloc] initWithData:[self.data subdataWithRange:theEndRange] encoding:NSUTF8StringEncoding] autorelease] + ]; + + NSDictionary *theUserInfo = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithUnsignedInteger:theLine], @"line", + [NSNumber numberWithUnsignedInteger:theCharacter], @"character", + [NSNumber numberWithUnsignedInteger:self.scanLocation], @"location", + theSnippet, @"snippet", + NULL]; + return(theUserInfo); + } + +@end diff --git a/libs/TouchJSON/Extensions/NSDictionary_JSONExtensions.h b/libs/TouchJSON/Extensions/NSDictionary_JSONExtensions.h new file mode 100644 index 0000000..6e611d0 --- /dev/null +++ b/libs/TouchJSON/Extensions/NSDictionary_JSONExtensions.h @@ -0,0 +1,37 @@ +// +// NSDictionary_JSONExtensions.h +// TouchCode +// +// Created by Jonathan Wight on 04/17/08. +// Copyright 2008 toxicsoftware.com. All rights reserved. +// +// 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 + +@interface NSDictionary (NSDictionary_JSONExtensions) + ++ (id)dictionaryWithJSONData:(NSData *)inData error:(NSError **)outError; ++ (id)dictionaryWithJSONString:(NSString *)inJSON error:(NSError **)outError; + +@end diff --git a/libs/TouchJSON/Extensions/NSDictionary_JSONExtensions.m b/libs/TouchJSON/Extensions/NSDictionary_JSONExtensions.m new file mode 100644 index 0000000..c0bb43c --- /dev/null +++ b/libs/TouchJSON/Extensions/NSDictionary_JSONExtensions.m @@ -0,0 +1,47 @@ +// +// NSDictionary_JSONExtensions.m +// TouchCode +// +// Created by Jonathan Wight on 04/17/08. +// Copyright 2008 toxicsoftware.com. All rights reserved. +// +// 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 "NSDictionary_JSONExtensions.h" + +#import "CJSONDeserializer.h" + +@implementation NSDictionary (NSDictionary_JSONExtensions) + ++ (id)dictionaryWithJSONData:(NSData *)inData error:(NSError **)outError + { + return([[CJSONDeserializer deserializer] deserialize:inData error:outError]); + } + ++ (id)dictionaryWithJSONString:(NSString *)inJSON error:(NSError **)outError; + { + NSData *theData = [inJSON dataUsingEncoding:NSUTF8StringEncoding]; + return([self dictionaryWithJSONData:theData error:outError]); + } + +@end diff --git a/libs/TouchJSON/JSON/CJSONDeserializer.h b/libs/TouchJSON/JSON/CJSONDeserializer.h new file mode 100644 index 0000000..0c3ed02 --- /dev/null +++ b/libs/TouchJSON/JSON/CJSONDeserializer.h @@ -0,0 +1,63 @@ +// +// CJSONDeserializer.h +// TouchCode +// +// Created by Jonathan Wight on 12/15/2005. +// Copyright 2005 toxicsoftware.com. All rights reserved. +// +// 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 + +#import "CJSONScanner.h" + +extern NSString *const kJSONDeserializerErrorDomain /* = @"CJSONDeserializerErrorDomain" */; + +enum { + kJSONDeserializationOptions_MutableContainers = kJSONScannerOptions_MutableContainers, + kJSONDeserializationOptions_MutableLeaves = kJSONScannerOptions_MutableLeaves, +}; +typedef NSUInteger EJSONDeserializationOptions; + +@class CJSONScanner; + +@interface CJSONDeserializer : NSObject { + CJSONScanner *scanner; + EJSONDeserializationOptions options; +} + +@property (readwrite, nonatomic, retain) CJSONScanner *scanner; +/// Object to return instead when a null encountered in the JSON. Defaults to NSNull. Setting to null causes the scanner to skip null values. +@property (readwrite, nonatomic, retain) id nullObject; +/// JSON must be encoded in Unicode (UTF-8, UTF-16 or UTF-32). Use this if you expect to get the JSON in another encoding. +@property (readwrite, nonatomic, assign) NSStringEncoding allowedEncoding; +@property (readwrite, nonatomic, assign) EJSONDeserializationOptions options; + ++ (id)deserializer; + +- (id)deserialize:(NSData *)inData error:(NSError **)outError; + +- (id)deserializeAsDictionary:(NSData *)inData error:(NSError **)outError; +- (id)deserializeAsArray:(NSData *)inData error:(NSError **)outError; + +@end diff --git a/libs/TouchJSON/JSON/CJSONDeserializer.m b/libs/TouchJSON/JSON/CJSONDeserializer.m new file mode 100644 index 0000000..27a2d03 --- /dev/null +++ b/libs/TouchJSON/JSON/CJSONDeserializer.m @@ -0,0 +1,161 @@ +// +// CJSONDeserializer.m +// TouchCode +// +// Created by Jonathan Wight on 12/15/2005. +// Copyright 2005 toxicsoftware.com. All rights reserved. +// +// 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 "CJSONDeserializer.h" + +#import "CJSONScanner.h" +#import "CDataScanner.h" + +NSString *const kJSONDeserializerErrorDomain = @"CJSONDeserializerErrorDomain"; + +@interface CJSONDeserializer () +@end + +@implementation CJSONDeserializer + +@synthesize scanner; +@synthesize options; + ++ (id)deserializer + { + return([[[self alloc] init] autorelease]); + } + +- (id)init + { + if ((self = [super init]) != NULL) + { + } + return(self); + } + +- (void)dealloc + { + [scanner release]; + scanner = NULL; + // + [super dealloc]; + } + +#pragma mark - + +- (CJSONScanner *)scanner + { + if (scanner == NULL) + { + scanner = [[CJSONScanner alloc] init]; + } + return(scanner); + } + +- (id)nullObject + { + return(self.scanner.nullObject); + } + +- (void)setNullObject:(id)inNullObject + { + self.scanner.nullObject = inNullObject; + } + +#pragma mark - + +- (NSStringEncoding)allowedEncoding + { + return(self.scanner.allowedEncoding); + } + +- (void)setAllowedEncoding:(NSStringEncoding)inAllowedEncoding + { + self.scanner.allowedEncoding = inAllowedEncoding; + } + +#pragma mark - + +- (id)deserialize:(NSData *)inData error:(NSError **)outError + { + if (inData == NULL || [inData length] == 0) + { + if (outError) + *outError = [NSError errorWithDomain:kJSONDeserializerErrorDomain code:kJSONScannerErrorCode_NothingToScan userInfo:NULL]; + + return(NULL); + } + if ([self.scanner setData:inData error:outError] == NO) + { + return(NULL); + } + id theObject = NULL; + if ([self.scanner scanJSONObject:&theObject error:outError] == YES) + return(theObject); + else + return(NULL); + } + +- (id)deserializeAsDictionary:(NSData *)inData error:(NSError **)outError + { + if (inData == NULL || [inData length] == 0) + { + if (outError) + *outError = [NSError errorWithDomain:kJSONDeserializerErrorDomain code:kJSONScannerErrorCode_NothingToScan userInfo:NULL]; + + return(NULL); + } + if ([self.scanner setData:inData error:outError] == NO) + { + return(NULL); + } + NSDictionary *theDictionary = NULL; + if ([self.scanner scanJSONDictionary:&theDictionary error:outError] == YES) + return(theDictionary); + else + return(NULL); + } + +- (id)deserializeAsArray:(NSData *)inData error:(NSError **)outError + { + if (inData == NULL || [inData length] == 0) + { + if (outError) + *outError = [NSError errorWithDomain:kJSONDeserializerErrorDomain code:kJSONScannerErrorCode_NothingToScan userInfo:NULL]; + + return(NULL); + } + if ([self.scanner setData:inData error:outError] == NO) + { + return(NULL); + } + NSArray *theArray = NULL; + if ([self.scanner scanJSONArray:&theArray error:outError] == YES) + return(theArray); + else + return(NULL); + } + +@end diff --git a/libs/TouchJSON/JSON/CJSONScanner.h b/libs/TouchJSON/JSON/CJSONScanner.h new file mode 100644 index 0000000..d410893 --- /dev/null +++ b/libs/TouchJSON/JSON/CJSONScanner.h @@ -0,0 +1,95 @@ +// +// CJSONScanner.h +// TouchCode +// +// Created by Jonathan Wight on 12/07/2005. +// Copyright 2005 toxicsoftware.com. All rights reserved. +// +// 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 "CDataScanner.h" + +enum { + kJSONScannerOptions_MutableContainers = 0x1, + kJSONScannerOptions_MutableLeaves = 0x2, +}; +typedef NSUInteger EJSONScannerOptions; + +/// CDataScanner subclass that understands JSON syntax natively. You should generally use CJSONDeserializer instead of this class. (TODO - this could have been a category?) +@interface CJSONScanner : CDataScanner { + BOOL strictEscapeCodes; + id nullObject; + NSStringEncoding allowedEncoding; + EJSONScannerOptions options; +} + +@property (readwrite, nonatomic, assign) BOOL strictEscapeCodes; +@property (readwrite, nonatomic, retain) id nullObject; +@property (readwrite, nonatomic, assign) NSStringEncoding allowedEncoding; +@property (readwrite, nonatomic, assign) EJSONScannerOptions options; + +- (BOOL)setData:(NSData *)inData error:(NSError **)outError; + +- (BOOL)scanJSONObject:(id *)outObject error:(NSError **)outError; +- (BOOL)scanJSONDictionary:(NSDictionary **)outDictionary error:(NSError **)outError; +- (BOOL)scanJSONArray:(NSArray **)outArray error:(NSError **)outError; +- (BOOL)scanJSONStringConstant:(NSString **)outStringConstant error:(NSError **)outError; +- (BOOL)scanJSONNumberConstant:(NSNumber **)outNumberConstant error:(NSError **)outError; + +@end + +extern NSString *const kJSONScannerErrorDomain /* = @"kJSONScannerErrorDomain" */; + +typedef enum { + + // Fundamental scanning errors + kJSONScannerErrorCode_NothingToScan = -11, + kJSONScannerErrorCode_CouldNotDecodeData = -12, + kJSONScannerErrorCode_CouldNotSerializeData = -13, + kJSONScannerErrorCode_CouldNotSerializeObject = -14, + kJSONScannerErrorCode_CouldNotScanObject = -15, + + // Dictionary scanning + kJSONScannerErrorCode_DictionaryStartCharacterMissing = -101, + kJSONScannerErrorCode_DictionaryKeyScanFailed = -102, + kJSONScannerErrorCode_DictionaryKeyNotTerminated = -103, + kJSONScannerErrorCode_DictionaryValueScanFailed = -104, + kJSONScannerErrorCode_DictionaryKeyValuePairNoDelimiter = -105, + kJSONScannerErrorCode_DictionaryNotTerminated = -106, + + // Array scanning + kJSONScannerErrorCode_ArrayStartCharacterMissing = -201, + kJSONScannerErrorCode_ArrayValueScanFailed = -202, + kJSONScannerErrorCode_ArrayValueIsNull = -203, + kJSONScannerErrorCode_ArrayNotTerminated = -204, + + // String scanning + kJSONScannerErrorCode_StringNotStartedWithBackslash = -301, + kJSONScannerErrorCode_StringUnicodeNotDecoded = -302, + kJSONScannerErrorCode_StringUnknownEscapeCode = -303, + kJSONScannerErrorCode_StringNotTerminated = -304, + + // Number scanning + kJSONScannerErrorCode_NumberNotScannable = -401 + +} EJSONScannerErrorCode; diff --git a/libs/TouchJSON/JSON/CJSONScanner.m b/libs/TouchJSON/JSON/CJSONScanner.m new file mode 100644 index 0000000..c5ffeb4 --- /dev/null +++ b/libs/TouchJSON/JSON/CJSONScanner.m @@ -0,0 +1,676 @@ +// +// CJSONScanner.m +// TouchCode +// +// Created by Jonathan Wight on 12/07/2005. +// Copyright 2005 toxicsoftware.com. All rights reserved. +// +// 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 "CJSONScanner.h" + +#import "CDataScanner_Extensions.h" + +#if !defined(TREAT_COMMENTS_AS_WHITESPACE) +#define TREAT_COMMENTS_AS_WHITESPACE 0 +#endif // !defined(TREAT_COMMENTS_AS_WHITESPACE) + +NSString *const kJSONScannerErrorDomain = @"kJSONScannerErrorDomain"; + +inline static int HexToInt(char inCharacter) + { + int theValues[] = { 0x0 /* 48 '0' */, 0x1 /* 49 '1' */, 0x2 /* 50 '2' */, 0x3 /* 51 '3' */, 0x4 /* 52 '4' */, 0x5 /* 53 '5' */, 0x6 /* 54 '6' */, 0x7 /* 55 '7' */, 0x8 /* 56 '8' */, 0x9 /* 57 '9' */, -1 /* 58 ':' */, -1 /* 59 ';' */, -1 /* 60 '<' */, -1 /* 61 '=' */, -1 /* 62 '>' */, -1 /* 63 '?' */, -1 /* 64 '@' */, 0xa /* 65 'A' */, 0xb /* 66 'B' */, 0xc /* 67 'C' */, 0xd /* 68 'D' */, 0xe /* 69 'E' */, 0xf /* 70 'F' */, -1 /* 71 'G' */, -1 /* 72 'H' */, -1 /* 73 'I' */, -1 /* 74 'J' */, -1 /* 75 'K' */, -1 /* 76 'L' */, -1 /* 77 'M' */, -1 /* 78 'N' */, -1 /* 79 'O' */, -1 /* 80 'P' */, -1 /* 81 'Q' */, -1 /* 82 'R' */, -1 /* 83 'S' */, -1 /* 84 'T' */, -1 /* 85 'U' */, -1 /* 86 'V' */, -1 /* 87 'W' */, -1 /* 88 'X' */, -1 /* 89 'Y' */, -1 /* 90 'Z' */, -1 /* 91 '[' */, -1 /* 92 '\' */, -1 /* 93 ']' */, -1 /* 94 '^' */, -1 /* 95 '_' */, -1 /* 96 '`' */, 0xa /* 97 'a' */, 0xb /* 98 'b' */, 0xc /* 99 'c' */, 0xd /* 100 'd' */, 0xe /* 101 'e' */, 0xf /* 102 'f' */, }; + if (inCharacter >= '0' && inCharacter <= 'f') + return(theValues[inCharacter - '0']); + else + return(-1); + } + +@interface CJSONScanner () +- (BOOL)scanNotQuoteCharactersIntoString:(NSString **)outValue; +@end + +#pragma mark - + +@implementation CJSONScanner + +@synthesize strictEscapeCodes; +@synthesize nullObject; +@synthesize allowedEncoding; +@synthesize options; + +- (id)init + { + if ((self = [super init]) != NULL) + { + strictEscapeCodes = NO; + nullObject = [[NSNull null] retain]; + } + return(self); + } + +- (void)dealloc + { + [nullObject release]; + nullObject = NULL; + // + [super dealloc]; + } + +#pragma mark - + +- (BOOL)setData:(NSData *)inData error:(NSError **)outError; + { + NSData *theData = inData; + if (theData && theData.length >= 4) + { + // This code is lame, but it works. Because the first character of any JSON string will always be a (ascii) control character we can work out the Unicode encoding by the bit pattern. See section 3 of http://www.ietf.org/rfc/rfc4627.txt + const char *theChars = theData.bytes; + NSStringEncoding theEncoding = NSUTF8StringEncoding; + if (theChars[0] != 0 && theChars[1] == 0) + { + if (theChars[2] != 0 && theChars[3] == 0) + theEncoding = NSUTF16LittleEndianStringEncoding; + else if (theChars[2] == 0 && theChars[3] == 0) + theEncoding = NSUTF32LittleEndianStringEncoding; + } + else if (theChars[0] == 0 && theChars[2] == 0 && theChars[3] != 0) + { + if (theChars[1] == 0) + theEncoding = NSUTF32BigEndianStringEncoding; + else if (theChars[1] != 0) + theEncoding = NSUTF16BigEndianStringEncoding; + } + + NSString *theString = [[NSString alloc] initWithData:theData encoding:theEncoding]; + if (theString == NULL && self.allowedEncoding != 0) + { + theString = [[NSString alloc] initWithData:theData encoding:self.allowedEncoding]; + } + theData = [theString dataUsingEncoding:NSUTF8StringEncoding]; + [theString release]; + } + + if (theData) + { + [super setData:theData]; + return(YES); + } + else + { + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan data. Data wasn't encoded properly?", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_CouldNotDecodeData userInfo:theUserInfo]; + } + return(NO); + } + } + +- (void)setData:(NSData *)inData + { + [self setData:inData error:NULL]; + } + +#pragma mark - + +- (BOOL)scanJSONObject:(id *)outObject error:(NSError **)outError + { + BOOL theResult = YES; + + [self skipWhitespace]; + + id theObject = NULL; + + const unichar C = [self currentCharacter]; + switch (C) + { + case 't': + if ([self scanUTF8String:"true" intoString:NULL]) + { + theObject = [NSNumber numberWithBool:YES]; + } + break; + case 'f': + if ([self scanUTF8String:"false" intoString:NULL]) + { + theObject = [NSNumber numberWithBool:NO]; + } + break; + case 'n': + if ([self scanUTF8String:"null" intoString:NULL]) + { + theObject = self.nullObject; + } + break; + case '\"': + case '\'': + theResult = [self scanJSONStringConstant:&theObject error:outError]; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + theResult = [self scanJSONNumberConstant:&theObject error:outError]; + break; + case '{': + theResult = [self scanJSONDictionary:&theObject error:outError]; + break; + case '[': + theResult = [self scanJSONArray:&theObject error:outError]; + break; + default: + theResult = NO; + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan object. Character not a valid JSON character.", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_CouldNotScanObject userInfo:theUserInfo]; + } + break; + } + + if (outObject != NULL) + *outObject = theObject; + + return(theResult); + } + +- (BOOL)scanJSONDictionary:(NSDictionary **)outDictionary error:(NSError **)outError + { + NSUInteger theScanLocation = [self scanLocation]; + + [self skipWhitespace]; + + if ([self scanCharacter:'{'] == NO) + { + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan dictionary. Dictionary that does not start with '{' character.", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_DictionaryStartCharacterMissing userInfo:theUserInfo]; + } + return(NO); + } + + NSMutableDictionary *theDictionary = [[NSMutableDictionary alloc] init]; + + while ([self currentCharacter] != '}') + { + [self skipWhitespace]; + + if ([self currentCharacter] == '}') + break; + + NSString *theKey = NULL; + if ([self scanJSONStringConstant:&theKey error:outError] == NO) + { + [self setScanLocation:theScanLocation]; + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan dictionary. Failed to scan a key.", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_DictionaryKeyScanFailed userInfo:theUserInfo]; + } + [theDictionary release]; + return(NO); + } + + [self skipWhitespace]; + + if ([self scanCharacter:':'] == NO) + { + [self setScanLocation:theScanLocation]; + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan dictionary. Key was not terminated with a ':' character.", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_DictionaryKeyNotTerminated userInfo:theUserInfo]; + } + [theDictionary release]; + return(NO); + } + + id theValue = NULL; + if ([self scanJSONObject:&theValue error:outError] == NO) + { + [self setScanLocation:theScanLocation]; + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan dictionary. Failed to scan a value.", NSLocalizedDescriptionKey, + NULL]; + + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_DictionaryValueScanFailed userInfo:theUserInfo]; + } + [theDictionary release]; + return(NO); + } + + if (theValue == NULL && self.nullObject == NULL) + { + // If the value is a null and nullObject is also null then we're skipping this key/value pair. + } + else + { + [theDictionary setValue:theValue forKey:theKey]; + } + + [self skipWhitespace]; + if ([self scanCharacter:','] == NO) + { + if ([self currentCharacter] != '}') + { + [self setScanLocation:theScanLocation]; + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan dictionary. Key value pairs not delimited with a ',' character.", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_DictionaryKeyValuePairNoDelimiter userInfo:theUserInfo]; + } + [theDictionary release]; + return(NO); + } + break; + } + else + { + [self skipWhitespace]; + if ([self currentCharacter] == '}') + break; + } + } + + if ([self scanCharacter:'}'] == NO) + { + [self setScanLocation:theScanLocation]; + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan dictionary. Dictionary not terminated by a '}' character.", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_DictionaryNotTerminated userInfo:theUserInfo]; + } + [theDictionary release]; + return(NO); + } + + if (outDictionary != NULL) + { + if (self.options & kJSONScannerOptions_MutableContainers) + { + *outDictionary = [theDictionary autorelease]; + } + else + { + *outDictionary = [[theDictionary copy] autorelease]; + [theDictionary release]; + } + } + else + { + [theDictionary release]; + } + + return(YES); + } + +- (BOOL)scanJSONArray:(NSArray **)outArray error:(NSError **)outError + { + NSUInteger theScanLocation = [self scanLocation]; + + [self skipWhitespace]; + + if ([self scanCharacter:'['] == NO) + { + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan array. Array not started by a '[' character.", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_ArrayStartCharacterMissing userInfo:theUserInfo]; + } + return(NO); + } + + NSMutableArray *theArray = [[NSMutableArray alloc] init]; + + [self skipWhitespace]; + while ([self currentCharacter] != ']') + { + NSString *theValue = NULL; + if ([self scanJSONObject:&theValue error:outError] == NO) + { + [self setScanLocation:theScanLocation]; + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan array. Could not scan a value.", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_ArrayValueScanFailed userInfo:theUserInfo]; + } + [theArray release]; + return(NO); + } + + if (theValue == NULL) + { + if (self.nullObject != NULL) + { + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan array. Value is NULL.", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_ArrayValueIsNull userInfo:theUserInfo]; + } + [theArray release]; + return(NO); + } + } + else + { + [theArray addObject:theValue]; + } + + [self skipWhitespace]; + if ([self scanCharacter:','] == NO) + { + [self skipWhitespace]; + if ([self currentCharacter] != ']') + { + [self setScanLocation:theScanLocation]; + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan array. Array not terminated by a ']' character.", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_ArrayNotTerminated userInfo:theUserInfo]; + } + [theArray release]; + return(NO); + } + + break; + } + [self skipWhitespace]; + } + + [self skipWhitespace]; + + if ([self scanCharacter:']'] == NO) + { + [self setScanLocation:theScanLocation]; + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan array. Array not terminated by a ']' character.", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_ArrayNotTerminated userInfo:theUserInfo]; + } + [theArray release]; + return(NO); + } + + if (outArray != NULL) + { + if (self.options & kJSONScannerOptions_MutableContainers) + { + *outArray = [theArray autorelease]; + } + else + { + *outArray = [[theArray copy] autorelease]; + [theArray release]; + } + } + else + { + [theArray release]; + } + return(YES); + } + +- (BOOL)scanJSONStringConstant:(NSString **)outStringConstant error:(NSError **)outError + { + NSUInteger theScanLocation = [self scanLocation]; + + [self skipWhitespace]; + + NSMutableString *theString = [[NSMutableString alloc] init]; + + if ([self scanCharacter:'"'] == NO) + { + [self setScanLocation:theScanLocation]; + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan string constant. String not started by a '\"' character.", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_StringNotStartedWithBackslash userInfo:theUserInfo]; + } + [theString release]; + return(NO); + } + + while ([self scanCharacter:'"'] == NO) + { + NSString *theStringChunk = NULL; + if ([self scanNotQuoteCharactersIntoString:&theStringChunk]) + { + CFStringAppend((CFMutableStringRef)theString, (CFStringRef)theStringChunk); + } + else if ([self scanCharacter:'\\'] == YES) + { + unichar theCharacter = [self scanCharacter]; + switch (theCharacter) + { + case '"': + case '\\': + case '/': + break; + case 'b': + theCharacter = '\b'; + break; + case 'f': + theCharacter = '\f'; + break; + case 'n': + theCharacter = '\n'; + break; + case 'r': + theCharacter = '\r'; + break; + case 't': + theCharacter = '\t'; + break; + case 'u': + { + theCharacter = 0; + + int theShift; + for (theShift = 12; theShift >= 0; theShift -= 4) + { + const int theDigit = HexToInt([self scanCharacter]); + if (theDigit == -1) + { + [self setScanLocation:theScanLocation]; + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan string constant. Unicode character could not be decoded.", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_StringUnicodeNotDecoded userInfo:theUserInfo]; + } + [theString release]; + return(NO); + } + theCharacter |= (theDigit << theShift); + } + } + break; + default: + { + if (strictEscapeCodes == YES) + { + [self setScanLocation:theScanLocation]; + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan string constant. Unknown escape code.", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_StringUnknownEscapeCode userInfo:theUserInfo]; + } + [theString release]; + return(NO); + } + } + break; + } + CFStringAppendCharacters((CFMutableStringRef)theString, &theCharacter, 1); + } + else + { + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan string constant. No terminating double quote character.", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_StringNotTerminated userInfo:theUserInfo]; + } + [theString release]; + return(NO); + } + } + + if (outStringConstant != NULL) + { + if (self.options & kJSONScannerOptions_MutableLeaves) + { + *outStringConstant = [theString autorelease]; + } + else + { + *outStringConstant = [[theString copy] autorelease]; + [theString release]; + } + } + else + { + [theString release]; + } + + return(YES); + } + +- (BOOL)scanJSONNumberConstant:(NSNumber **)outNumberConstant error:(NSError **)outError + { + NSNumber *theNumber = NULL; + + [self skipWhitespace]; + + if ([self scanNumber:&theNumber] == YES) + { + if (outNumberConstant != NULL) + *outNumberConstant = theNumber; + return(YES); + } + else + { + if (outError) + { + NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Could not scan number constant.", NSLocalizedDescriptionKey, + NULL]; + [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation]; + *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_NumberNotScannable userInfo:theUserInfo]; + } + return(NO); + } + } + +#if TREAT_COMMENTS_AS_WHITESPACE +- (void)skipWhitespace + { + [super skipWhitespace]; + [self scanCStyleComment:NULL]; + [self scanCPlusPlusStyleComment:NULL]; + [super skipWhitespace]; + } +#endif // TREAT_COMMENTS_AS_WHITESPACE + +#pragma mark - + +- (BOOL)scanNotQuoteCharactersIntoString:(NSString **)outValue + { + u_int8_t *P; + for (P = current; P < end && *P != '\"' && *P != '\\'; ++P) + ; + + if (P == current) + { + return(NO); + } + + if (outValue) + { + *outValue = [[[NSString alloc] initWithBytes:current length:P - current encoding:NSUTF8StringEncoding] autorelease]; + } + + current = P; + + return(YES); + } + +@end diff --git a/libs/TouchJSON/JSON/CJSONSerializer.h b/libs/TouchJSON/JSON/CJSONSerializer.h new file mode 100644 index 0000000..748a85c --- /dev/null +++ b/libs/TouchJSON/JSON/CJSONSerializer.h @@ -0,0 +1,53 @@ +// +// CJSONSerializer.h +// TouchCode +// +// Created by Jonathan Wight on 12/07/2005. +// Copyright 2005 toxicsoftware.com. All rights reserved. +// +// 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 + +@interface CJSONSerializer : NSObject { +} + ++ (id)serializer; + +- (BOOL)isValidJSONObject:(id)inObject; + +/// Take any JSON compatible object (generally NSNull, NSNumber, NSString, NSArray and NSDictionary) and produce an NSData containing the serialized JSON. +- (NSData *)serializeObject:(id)inObject error:(NSError **)outError; + +- (NSData *)serializeNull:(NSNull *)inNull error:(NSError **)outError; +- (NSData *)serializeNumber:(NSNumber *)inNumber error:(NSError **)outError; +- (NSData *)serializeString:(NSString *)inString error:(NSError **)outError; +- (NSData *)serializeArray:(NSArray *)inArray error:(NSError **)outError; +- (NSData *)serializeDictionary:(NSDictionary *)inDictionary error:(NSError **)outError; + +@end + +typedef enum { + CJSONSerializerErrorCouldNotSerializeDataType = -1, + CJSONSerializerErrorCouldNotSerializeObject = -1 +} CJSONSerializerError; diff --git a/libs/TouchJSON/JSON/CJSONSerializer.m b/libs/TouchJSON/JSON/CJSONSerializer.m new file mode 100644 index 0000000..952b3c2 --- /dev/null +++ b/libs/TouchJSON/JSON/CJSONSerializer.m @@ -0,0 +1,342 @@ +// +// CJSONSerializer.m +// TouchCode +// +// Created by Jonathan Wight on 12/07/2005. +// Copyright 2005 toxicsoftware.com. All rights reserved. +// +// 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 "CJSONSerializer.h" + +#import "JSONRepresentation.h" + +static NSData *kNULL = NULL; +static NSData *kFalse = NULL; +static NSData *kTrue = NULL; + +@implementation CJSONSerializer + ++ (void)initialize + { + NSAutoreleasePool *thePool = [[NSAutoreleasePool alloc] init]; + + if (self == [CJSONSerializer class]) + { + if (kNULL == NULL) + kNULL = [[NSData alloc] initWithBytesNoCopy:(void *)"null" length:4 freeWhenDone:NO]; + if (kFalse == NULL) + kFalse = [[NSData alloc] initWithBytesNoCopy:(void *)"false" length:5 freeWhenDone:NO]; + if (kTrue == NULL) + kTrue = [[NSData alloc] initWithBytesNoCopy:(void *)"true" length:4 freeWhenDone:NO]; + + [thePool release]; + } + } + ++ (id)serializer + { + return([[[self alloc] init] autorelease]); + } + +- (BOOL)isValidJSONObject:(id)inObject + { + if ([inObject isKindOfClass:[NSNull class]]) + { + return(YES); + } + else if ([inObject isKindOfClass:[NSNumber class]]) + { + return(YES); + } + else if ([inObject isKindOfClass:[NSString class]]) + { + return(YES); + } + else if ([inObject isKindOfClass:[NSArray class]]) + { + return(YES); + } + else if ([inObject isKindOfClass:[NSDictionary class]]) + { + return(YES); + } + else if ([inObject isKindOfClass:[NSData class]]) + { + return(YES); + } + else if ([inObject respondsToSelector:@selector(JSONDataRepresentation)]) + { + return(YES); + } + else + { + return(NO); + } + } + +- (NSData *)serializeObject:(id)inObject error:(NSError **)outError + { + NSData *theResult = NULL; + + if ([inObject isKindOfClass:[NSNull class]]) + { + theResult = [self serializeNull:inObject error:outError]; + } + else if ([inObject isKindOfClass:[NSNumber class]]) + { + theResult = [self serializeNumber:inObject error:outError]; + } + else if ([inObject isKindOfClass:[NSString class]]) + { + theResult = [self serializeString:inObject error:outError]; + } + else if ([inObject isKindOfClass:[NSArray class]]) + { + theResult = [self serializeArray:inObject error:outError]; + } + else if ([inObject isKindOfClass:[NSDictionary class]]) + { + theResult = [self serializeDictionary:inObject error:outError]; + } + else if ([inObject isKindOfClass:[NSData class]]) + { + NSString *theString = [[[NSString alloc] initWithData:inObject encoding:NSUTF8StringEncoding] autorelease]; + theResult = [self serializeString:theString error:outError]; + } + else if ([inObject respondsToSelector:@selector(JSONDataRepresentation)]) + { + theResult = [inObject JSONDataRepresentation]; + } + else + { + if (outError) + { + NSDictionary *theUserInfo = [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithFormat:@"Cannot serialize data of type '%@'", NSStringFromClass([inObject class])], NSLocalizedDescriptionKey, + NULL]; + *outError = [NSError errorWithDomain:@"TODO_DOMAIN" code:CJSONSerializerErrorCouldNotSerializeDataType userInfo:theUserInfo]; + } + return(NULL); + } + if (theResult == NULL) + { + if (outError) + { + NSDictionary *theUserInfo = [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithFormat:@"Could not serialize object '%@'", inObject], NSLocalizedDescriptionKey, + NULL]; + *outError = [NSError errorWithDomain:@"TODO_DOMAIN" code:CJSONSerializerErrorCouldNotSerializeObject userInfo:theUserInfo]; + } + return(NULL); + } + return(theResult); + } + +- (NSData *)serializeNull:(NSNull *)inNull error:(NSError **)outError + { + #pragma unused (inNull, outError) + return(kNULL); + } + +- (NSData *)serializeNumber:(NSNumber *)inNumber error:(NSError **)outError + { + #pragma unused (outError) + NSData *theResult = NULL; + switch (CFNumberGetType((CFNumberRef)inNumber)) + { + case kCFNumberCharType: + { + int theValue = [inNumber intValue]; + if (theValue == 0) + theResult = kFalse; + else if (theValue == 1) + theResult = kTrue; + else + theResult = [[inNumber stringValue] dataUsingEncoding:NSASCIIStringEncoding]; + } + break; + case kCFNumberFloat32Type: + case kCFNumberFloat64Type: + case kCFNumberFloatType: + case kCFNumberDoubleType: + case kCFNumberSInt8Type: + case kCFNumberSInt16Type: + case kCFNumberSInt32Type: + case kCFNumberSInt64Type: + case kCFNumberShortType: + case kCFNumberIntType: + case kCFNumberLongType: + case kCFNumberLongLongType: + case kCFNumberCFIndexType: + default: + theResult = [[inNumber stringValue] dataUsingEncoding:NSASCIIStringEncoding]; + break; + } + return(theResult); + } + +- (NSData *)serializeString:(NSString *)inString error:(NSError **)outError + { + #pragma unused (outError) + + const char *theUTF8String = [inString UTF8String]; + + NSMutableData *theData = [NSMutableData dataWithLength:strlen(theUTF8String) * 2 + 2]; + + char *theOutputStart = [theData mutableBytes]; + char *OUT = theOutputStart; + + *OUT++ = '"'; + + for (const char *IN = theUTF8String; IN && *IN != '\0'; ++IN) + { + switch (*IN) + { + case '\\': + { + *OUT++ = '\\'; + *OUT++ = '\\'; + } + break; + case '\"': + { + *OUT++ = '\\'; + *OUT++ = '\"'; + } + break; + case '/': + { + *OUT++ = '\\'; + *OUT++ = '/'; + } + break; + case '\b': + { + *OUT++ = '\\'; + *OUT++ = 'b'; + } + break; + case '\f': + { + *OUT++ = '\\'; + *OUT++ = 'f'; + } + break; + case '\n': + { + *OUT++ = '\\'; + *OUT++ = 'n'; + } + break; + case '\r': + { + *OUT++ = '\\'; + *OUT++ = 'r'; + } + break; + case '\t': + { + *OUT++ = '\\'; + *OUT++ = 't'; + } + break; + default: + { + *OUT++ = *IN; + } + break; + } + } + + *OUT++ = '"'; + + theData.length = OUT - theOutputStart; + return(theData); + } + +- (NSData *)serializeArray:(NSArray *)inArray error:(NSError **)outError + { + NSMutableData *theData = [NSMutableData data]; + + [theData appendBytes:"[" length:1]; + + NSEnumerator *theEnumerator = [inArray objectEnumerator]; + id theValue = NULL; + NSUInteger i = 0; + while ((theValue = [theEnumerator nextObject]) != NULL) + { + NSData *theValueData = [self serializeObject:theValue error:outError]; + if (theValueData == NULL) + { + return(NULL); + } + [theData appendData:theValueData]; + if (++i < [inArray count]) + [theData appendBytes:"," length:1]; + } + + [theData appendBytes:"]" length:1]; + + return(theData); + } + +- (NSData *)serializeDictionary:(NSDictionary *)inDictionary error:(NSError **)outError + { + NSMutableData *theData = [NSMutableData data]; + + [theData appendBytes:"{" length:1]; + + NSArray *theKeys = [inDictionary allKeys]; + NSEnumerator *theEnumerator = [theKeys objectEnumerator]; + NSString *theKey = NULL; + while ((theKey = [theEnumerator nextObject]) != NULL) + { + id theValue = [inDictionary objectForKey:theKey]; + + NSData *theKeyData = [self serializeString:theKey error:outError]; + if (theKeyData == NULL) + { + return(NULL); + } + NSData *theValueData = [self serializeObject:theValue error:outError]; + if (theValueData == NULL) + { + return(NULL); + } + + + [theData appendData:theKeyData]; + [theData appendBytes:":" length:1]; + [theData appendData:theValueData]; + + if (theKey != [theKeys lastObject]) + [theData appendData:[@"," dataUsingEncoding:NSASCIIStringEncoding]]; + } + + [theData appendBytes:"}" length:1]; + + return(theData); + } + +@end diff --git a/libs/TouchJSON/JSON/JSONRepresentation.h b/libs/TouchJSON/JSON/JSONRepresentation.h new file mode 100644 index 0000000..a83d76f --- /dev/null +++ b/libs/TouchJSON/JSON/JSONRepresentation.h @@ -0,0 +1,18 @@ +// +// JSONRepresentation.h +// TouchJSON +// +// Created by Jonathan Wight on 10/15/10. +// Copyright 2010 toxicsoftware.com. All rights reserved. +// + +#import + +@protocol JSONRepresentation + +@optional +- (id)initWithJSONDataRepresentation:(NSData *)inJSONData; + +- (NSData *)JSONDataRepresentation; + +@end diff --git a/libs/cocos2d/CCAction.h b/libs/cocos2d/CCAction.h new file mode 100644 index 0000000..51bad8e --- /dev/null +++ b/libs/cocos2d/CCAction.h @@ -0,0 +1,195 @@ +/* + * 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. + * + */ + + +#include +#import + +#import "ccTypes.h" + +enum { + //! Default tag + kCCActionTagInvalid = -1, +}; + +/** Base class for CCAction objects. + */ +@interface CCAction : NSObject +{ + id originalTarget_; + id target_; + NSInteger tag_; +} + +/** The "target". The action will modify the target properties. + The target will be set with the 'startWithTarget' method. + When the 'stop' method is called, target will be set to nil. + The target is 'assigned', it is not 'retained'. + */ +@property (nonatomic,readonly,assign) id target; + +/** The original target, since target can be nil. + Is the target that were used to run the action. Unless you are doing something complex, like CCActionManager, you should NOT call this method. + @since v0.8.2 +*/ +@property (nonatomic,readonly,assign) id originalTarget; + + +/** The action tag. An identifier of the action */ +@property (nonatomic,readwrite,assign) NSInteger tag; + +/** Allocates and initializes the action */ ++(id) action; + +/** Initializes the action */ +-(id) init; + +-(id) copyWithZone: (NSZone*) zone; + +//! return YES if the action has finished +-(BOOL) isDone; +//! called before the action start. It will also set the target. +-(void) startWithTarget:(id)target; +//! called after the action has finished. It will set the 'target' to nil. +//! IMPORTANT: You should never call "[action stop]" manually. Instead, use: "[target stopAction:action];" +-(void) stop; +//! called every frame with it's delta time. DON'T override unless you know what you are doing. +-(void) step: (ccTime) dt; +//! called once per frame. time a value between 0 and 1 +//! For example: +//! * 0 means that the action just started +//! * 0.5 means that the action is in the middle +//! * 1 means that the action is over +-(void) update: (ccTime) time; + +@end + +/** Base class actions that do have a finite time duration. + Possible actions: + - An action with a duration of 0 seconds + - An action with a duration of 35.5 seconds + Infitite time actions are valid + */ +@interface CCFiniteTimeAction : CCAction +{ + //! duration in seconds + ccTime duration_; +} +//! duration in seconds of the action +@property (nonatomic,readwrite) ccTime duration; + +/** returns a reversed action */ +- (CCFiniteTimeAction*) reverse; +@end + + +@class CCActionInterval; +/** Repeats an action for ever. + To repeat the an action for a limited number of times use the Repeat action. + @warning This action can't be Sequenceable because it is not an IntervalAction + */ +@interface CCRepeatForever : CCAction +{ + CCActionInterval *innerAction_; +} +/** Inner action */ +@property (nonatomic, readwrite, retain) CCActionInterval *innerAction; + +/** creates the action */ ++(id) actionWithAction: (CCActionInterval*) action; +/** initializes the action */ +-(id) initWithAction: (CCActionInterval*) action; +@end + +/** Changes the speed of an action, making it take longer (speed>1) + or less (speed<1) time. + Useful to simulate 'slow motion' or 'fast forward' effect. + @warning This action can't be Sequenceable because it is not an CCIntervalAction + */ +@interface CCSpeed : CCAction +{ + CCActionInterval *innerAction_; + float speed_; +} +/** alter the speed of the inner function in runtime */ +@property (nonatomic,readwrite) float speed; +/** Inner action of CCSpeed */ +@property (nonatomic, readwrite, retain) CCActionInterval *innerAction; + +/** creates the action */ ++(id) actionWithAction: (CCActionInterval*) action speed:(float)rate; +/** initializes the action */ +-(id) initWithAction: (CCActionInterval*) action speed:(float)rate; +@end + +@class CCNode; +/** CCFollow is an action that "follows" a node. + + Eg: + [layer runAction: [CCFollow actionWithTarget:hero]]; + + Instead of using CCCamera as a "follower", use this action instead. + @since v0.99.2 + */ +@interface CCFollow : CCAction +{ + /* node to follow */ + CCNode *followedNode_; + + /* whether camera should be limited to certain area */ + BOOL boundarySet; + + /* if screensize is bigger than the boundary - update not needed */ + BOOL boundaryFullyCovered; + + /* fast access to the screen dimensions */ + CGPoint halfScreenSize; + CGPoint fullScreenSize; + + /* world boundaries */ + float leftBoundary; + float rightBoundary; + float topBoundary; + float bottomBoundary; +} + +/** alter behavior - turn on/off boundary */ +@property (nonatomic,readwrite) BOOL boundarySet; + +/** creates the action with no boundary set */ ++(id) actionWithTarget:(CCNode *)followedNode; + +/** creates the action with a set boundary */ ++(id) actionWithTarget:(CCNode *)followedNode worldBoundary:(CGRect)rect; + +/** initializes the action */ +-(id) initWithTarget:(CCNode *)followedNode; + +/** initializes the action with a set boundary */ +-(id) initWithTarget:(CCNode *)followedNode worldBoundary:(CGRect)rect; + +@end + diff --git a/libs/cocos2d/CCAction.m b/libs/cocos2d/CCAction.m new file mode 100644 index 0000000..0187dbd --- /dev/null +++ b/libs/cocos2d/CCAction.m @@ -0,0 +1,362 @@ +/* + * 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. + * + */ + + + +#import +#import "CCDirector.h" +#import "ccMacros.h" +#import "CCAction.h" +#import "CCActionInterval.h" +#import "Support/CGPointExtension.h" + +// +// Action Base Class +// +#pragma mark - +#pragma mark Action +@implementation CCAction + +@synthesize tag = tag_, target = target_, originalTarget = originalTarget_; + ++(id) action +{ + return [[[self alloc] init] autorelease]; +} + +-(id) init +{ + if( (self=[super init]) ) { + originalTarget_ = target_ = nil; + tag_ = kCCActionTagInvalid; + } + return self; +} + +-(void) dealloc +{ + CCLOGINFO(@"cocos2d: deallocing %@", self); + [super dealloc]; +} + +-(NSString*) description +{ + return [NSString stringWithFormat:@"<%@ = %08X | Tag = %i>", [self class], self, tag_]; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] init]; + copy.tag = tag_; + return copy; +} + +-(void) startWithTarget:(id)aTarget +{ + originalTarget_ = target_ = aTarget; +} + +-(void) stop +{ + target_ = nil; +} + +-(BOOL) isDone +{ + return YES; +} + +-(void) step: (ccTime) dt +{ + NSLog(@"[Action step]. override me"); +} + +-(void) update: (ccTime) time +{ + NSLog(@"[Action update]. override me"); +} +@end + +// +// FiniteTimeAction +// +#pragma mark - +#pragma mark FiniteTimeAction +@implementation CCFiniteTimeAction +@synthesize duration = duration_; + +- (CCFiniteTimeAction*) reverse +{ + CCLOG(@"cocos2d: FiniteTimeAction#reverse: Implement me"); + return nil; +} +@end + + +// +// RepeatForever +// +#pragma mark - +#pragma mark RepeatForever +@implementation CCRepeatForever +@synthesize innerAction=innerAction_; ++(id) actionWithAction: (CCActionInterval*) action +{ + return [[[self alloc] initWithAction: action] autorelease]; +} + +-(id) initWithAction: (CCActionInterval*) action +{ + if( (self=[super init]) ) + self.innerAction = action; + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] initWithAction:[[innerAction_ copy] autorelease] ]; + return copy; +} + +-(void) dealloc +{ + [innerAction_ release]; + [super dealloc]; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + [innerAction_ startWithTarget:target_]; +} + +-(void) step:(ccTime) dt +{ + [innerAction_ step: dt]; + if( [innerAction_ isDone] ) { + ccTime diff = dt + innerAction_.duration - innerAction_.elapsed; + [innerAction_ startWithTarget:target_]; + + // to prevent jerk. issue #390 + [innerAction_ step: diff]; + } +} + + +-(BOOL) isDone +{ + return NO; +} + +- (CCActionInterval *) reverse +{ + return [CCRepeatForever actionWithAction:[innerAction_ reverse]]; +} +@end + +// +// Speed +// +#pragma mark - +#pragma mark Speed +@implementation CCSpeed +@synthesize speed=speed_; +@synthesize innerAction=innerAction_; + ++(id) actionWithAction: (CCActionInterval*) action speed:(float)r +{ + return [[[self alloc] initWithAction: action speed:r] autorelease]; +} + +-(id) initWithAction: (CCActionInterval*) action speed:(float)r +{ + if( (self=[super init]) ) { + self.innerAction = action; + speed_ = r; + } + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] initWithAction:[[innerAction_ copy] autorelease] speed:speed_]; + return copy; +} + +-(void) dealloc +{ + [innerAction_ release]; + [super dealloc]; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + [innerAction_ startWithTarget:target_]; +} + +-(void) stop +{ + [innerAction_ stop]; + [super stop]; +} + +-(void) step:(ccTime) dt +{ + [innerAction_ step: dt * speed_]; +} + +-(BOOL) isDone +{ + return [innerAction_ isDone]; +} + +- (CCActionInterval *) reverse +{ + return [CCSpeed actionWithAction:[innerAction_ reverse] speed:speed_]; +} +@end + +// +// Follow +// +#pragma mark - +#pragma mark Follow +@implementation CCFollow + +@synthesize boundarySet; + ++(id) actionWithTarget:(CCNode *) fNode +{ + return [[[self alloc] initWithTarget:fNode] autorelease]; +} + ++(id) actionWithTarget:(CCNode *) fNode worldBoundary:(CGRect)rect +{ + return [[[self alloc] initWithTarget:fNode worldBoundary:rect] autorelease]; +} + +-(id) initWithTarget:(CCNode *)fNode +{ + if( (self=[super init]) ) { + + followedNode_ = [fNode retain]; + boundarySet = FALSE; + boundaryFullyCovered = FALSE; + + CGSize s = [[CCDirector sharedDirector] winSize]; + fullScreenSize = CGPointMake(s.width, s.height); + halfScreenSize = ccpMult(fullScreenSize, .5f); + } + + return self; +} + +-(id) initWithTarget:(CCNode *)fNode worldBoundary:(CGRect)rect +{ + if( (self=[super init]) ) { + + followedNode_ = [fNode retain]; + boundarySet = TRUE; + boundaryFullyCovered = FALSE; + + CGSize winSize = [[CCDirector sharedDirector] winSize]; + fullScreenSize = CGPointMake(winSize.width, winSize.height); + halfScreenSize = ccpMult(fullScreenSize, .5f); + + leftBoundary = -((rect.origin.x+rect.size.width) - fullScreenSize.x); + rightBoundary = -rect.origin.x ; + topBoundary = -rect.origin.y; + bottomBoundary = -((rect.origin.y+rect.size.height) - fullScreenSize.y); + + if(rightBoundary < leftBoundary) + { + // screen width is larger than world's boundary width + //set both in the middle of the world + rightBoundary = leftBoundary = (leftBoundary + rightBoundary) / 2; + } + if(topBoundary < bottomBoundary) + { + // screen width is larger than world's boundary width + //set both in the middle of the world + topBoundary = bottomBoundary = (topBoundary + bottomBoundary) / 2; + } + + if( (topBoundary == bottomBoundary) && (leftBoundary == rightBoundary) ) + boundaryFullyCovered = TRUE; + } + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] init]; + copy.tag = tag_; + return copy; +} + +-(void) step:(ccTime) dt +{ +#define CLAMP(x,y,z) MIN(MAX(x,y),z) + + if(boundarySet) + { + // whole map fits inside a single screen, no need to modify the position - unless map boundaries are increased + if(boundaryFullyCovered) + return; + + CGPoint tempPos = ccpSub( halfScreenSize, followedNode_.position); + [target_ setPosition:ccp(CLAMP(tempPos.x,leftBoundary,rightBoundary), CLAMP(tempPos.y,bottomBoundary,topBoundary))]; + } + else + [target_ setPosition:ccpSub( halfScreenSize, followedNode_.position )]; + +#undef CLAMP +} + + +-(BOOL) isDone +{ + return !followedNode_.isRunning; +} + +-(void) stop +{ + target_ = nil; + [super stop]; +} + +-(void) dealloc +{ + [followedNode_ release]; + [super dealloc]; +} + +@end + + diff --git a/libs/cocos2d/CCActionCamera.h b/libs/cocos2d/CCActionCamera.h new file mode 100644 index 0000000..1ea83a7 --- /dev/null +++ b/libs/cocos2d/CCActionCamera.h @@ -0,0 +1,73 @@ +/* + * 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. + * + */ + +#import "CCActionInterval.h" + +@class CCCamera; + +/** Base class for CCCamera actions + */ +@interface CCActionCamera : CCActionInterval +{ + float centerXOrig_; + float centerYOrig_; + float centerZOrig_; + + float eyeXOrig_; + float eyeYOrig_; + float eyeZOrig_; + + float upXOrig_; + float upYOrig_; + float upZOrig_; +} +@end + +/** CCOrbitCamera action + Orbits the camera around the center of the screen using spherical coordinates + */ +@interface CCOrbitCamera : CCActionCamera +{ + float radius_; + float deltaRadius_; + float angleZ_; + float deltaAngleZ_; + float angleX_; + float deltaAngleX_; + + float radZ_; + float radDeltaZ_; + float radX_; + float radDeltaX_; + +} +/** creates a CCOrbitCamera action with radius, delta-radius, z, deltaZ, x, deltaX */ ++(id) actionWithDuration:(float) t radius:(float)r deltaRadius:(float) dr angleZ:(float)z deltaAngleZ:(float)dz angleX:(float)x deltaAngleX:(float)dx; +/** initializes a CCOrbitCamera action with radius, delta-radius, z, deltaZ, x, deltaX */ +-(id) initWithDuration:(float) t radius:(float)r deltaRadius:(float) dr angleZ:(float)z deltaAngleZ:(float)dz angleX:(float)x deltaAngleX:(float)dx; +/** positions the camera according to spherical coordinates */ +-(void) sphericalRadius:(float*) r zenith:(float*) zenith azimuth:(float*) azimuth; +@end diff --git a/libs/cocos2d/CCActionCamera.m b/libs/cocos2d/CCActionCamera.m new file mode 100644 index 0000000..4dafc4e --- /dev/null +++ b/libs/cocos2d/CCActionCamera.m @@ -0,0 +1,147 @@ +/* + * 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. + * + */ + + + +#import "CCActionCamera.h" +#import "CCNode.h" +#import "CCCamera.h" +#import "ccMacros.h" + +// +// CameraAction +// +@implementation CCActionCamera +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + CCCamera *camera = [target_ camera]; + [camera centerX:¢erXOrig_ centerY:¢erYOrig_ centerZ:¢erZOrig_]; + [camera eyeX:&eyeXOrig_ eyeY:&eyeYOrig_ eyeZ:&eyeZOrig_]; + [camera upX:&upXOrig_ upY:&upYOrig_ upZ: &upZOrig_]; +} + +-(id) reverse +{ + return [CCReverseTime actionWithAction:self]; +} +@end + +@implementation CCOrbitCamera ++(id) actionWithDuration:(float)t radius:(float)r deltaRadius:(float) dr angleZ:(float)z deltaAngleZ:(float)dz angleX:(float)x deltaAngleX:(float)dx +{ + return [[[self alloc] initWithDuration:t radius:r deltaRadius:dr angleZ:z deltaAngleZ:dz angleX:x deltaAngleX:dx] autorelease]; +} + +-(id) copyWithZone: (NSZone*) zone +{ + return [[[self class] allocWithZone: zone] initWithDuration:duration_ radius:radius_ deltaRadius:deltaRadius_ angleZ:angleZ_ deltaAngleZ:deltaAngleZ_ angleX:angleX_ deltaAngleX:deltaAngleX_]; +} + + +-(id) initWithDuration:(float)t radius:(float)r deltaRadius:(float) dr angleZ:(float)z deltaAngleZ:(float)dz angleX:(float)x deltaAngleX:(float)dx +{ + if((self=[super initWithDuration:t]) ) { + + radius_ = r; + deltaRadius_ = dr; + angleZ_ = z; + deltaAngleZ_ = dz; + angleX_ = x; + deltaAngleX_ = dx; + + radDeltaZ_ = (CGFloat)CC_DEGREES_TO_RADIANS(dz); + radDeltaX_ = (CGFloat)CC_DEGREES_TO_RADIANS(dx); + } + + return self; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + float r, zenith, azimuth; + + [self sphericalRadius: &r zenith:&zenith azimuth:&azimuth]; + +#if 0 // isnan() is not supported on the simulator, and isnan() always returns false. + if( isnan(radius_) ) + radius_ = r; + + if( isnan( angleZ_) ) + angleZ_ = (CGFloat)CC_RADIANS_TO_DEGREES(zenith); + + if( isnan( angleX_ ) ) + angleX_ = (CGFloat)CC_RADIANS_TO_DEGREES(azimuth); +#endif + + radZ_ = (CGFloat)CC_DEGREES_TO_RADIANS(angleZ_); + radX_ = (CGFloat)CC_DEGREES_TO_RADIANS(angleX_); +} + +-(void) update: (ccTime) dt +{ + float r = (radius_ + deltaRadius_ * dt) *[CCCamera getZEye]; + float za = radZ_ + radDeltaZ_ * dt; + float xa = radX_ + radDeltaX_ * dt; + + float i = sinf(za) * cosf(xa) * r + centerXOrig_; + float j = sinf(za) * sinf(xa) * r + centerYOrig_; + float k = cosf(za) * r + centerZOrig_; + + [[target_ camera] setEyeX:i eyeY:j eyeZ:k]; +} + +-(void) sphericalRadius:(float*) newRadius zenith:(float*) zenith azimuth:(float*) azimuth +{ + float ex, ey, ez, cx, cy, cz, x, y, z; + float r; // radius + float s; + + CCCamera *camera = [target_ camera]; + [camera eyeX:&ex eyeY:&ey eyeZ:&ez]; + [camera centerX:&cx centerY:&cy centerZ:&cz]; + + x = ex-cx; + y = ey-cy; + z = ez-cz; + + r = sqrtf( x*x + y*y + z*z); + s = sqrtf( x*x + y*y); + if(s==0.0f) + s = FLT_EPSILON; + if(r==0.0f) + r = FLT_EPSILON; + + *zenith = acosf( z/r); + if( x < 0 ) + *azimuth = (float)M_PI - asinf(y/s); + else + *azimuth = asinf(y/s); + + *newRadius = r / [CCCamera getZEye]; +} +@end diff --git a/libs/cocos2d/CCActionEase.h b/libs/cocos2d/CCActionEase.h new file mode 100644 index 0000000..fced701 --- /dev/null +++ b/libs/cocos2d/CCActionEase.h @@ -0,0 +1,159 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2008-2009 Jason Booth + * + * 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 "CCActionInterval.h" + +/** Base class for Easing actions + */ +@interface CCActionEase : CCActionInterval +{ + CCActionInterval * other; +} +/** creates the action */ ++(id) actionWithAction: (CCActionInterval*) action; +/** initializes the action */ +-(id) initWithAction: (CCActionInterval*) action; +@end + +/** Base class for Easing actions with rate parameters + */ +@interface CCEaseRateAction : CCActionEase +{ + float rate; +} +/** rate value for the actions */ +@property (nonatomic,readwrite,assign) float rate; +/** Creates the action with the inner action and the rate parameter */ ++(id) actionWithAction: (CCActionInterval*) action rate:(float)rate; +/** Initializes the action with the inner action and the rate parameter */ +-(id) initWithAction: (CCActionInterval*) action rate:(float)rate; +@end + +/** CCEaseIn action with a rate + */ +@interface CCEaseIn : CCEaseRateAction {} @end + +/** CCEaseOut action with a rate + */ +@interface CCEaseOut : CCEaseRateAction {} @end + +/** CCEaseInOut action with a rate + */ +@interface CCEaseInOut : CCEaseRateAction {} @end + +/** CCEase Exponential In + */ +@interface CCEaseExponentialIn : CCActionEase {} @end +/** Ease Exponential Out + */ +@interface CCEaseExponentialOut : CCActionEase {} @end +/** Ease Exponential InOut + */ +@interface CCEaseExponentialInOut : CCActionEase {} @end +/** Ease Sine In + */ +@interface CCEaseSineIn : CCActionEase {} @end +/** Ease Sine Out + */ +@interface CCEaseSineOut : CCActionEase {} @end +/** Ease Sine InOut + */ +@interface CCEaseSineInOut : CCActionEase {} @end + +/** Ease Elastic abstract class + @since v0.8.2 + */ +@interface CCEaseElastic : CCActionEase +{ + float period_; +} + +/** period of the wave in radians. default is 0.3 */ +@property (nonatomic,readwrite) float period; + +/** Creates the action with the inner action and the period in radians (default is 0.3) */ ++(id) actionWithAction: (CCActionInterval*) action period:(float)period; +/** Initializes the action with the inner action and the period in radians (default is 0.3) */ +-(id) initWithAction: (CCActionInterval*) action period:(float)period; +@end + +/** Ease Elastic In action. + @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action. + @since v0.8.2 + */ +@interface CCEaseElasticIn : CCEaseElastic {} @end +/** Ease Elastic Out action. + @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action. + @since v0.8.2 + */ +@interface CCEaseElasticOut : CCEaseElastic {} @end +/** Ease Elastic InOut action. + @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action. + @since v0.8.2 + */ +@interface CCEaseElasticInOut : CCEaseElastic {} @end + +/** CCEaseBounce abstract class. + @since v0.8.2 +*/ +@interface CCEaseBounce : CCActionEase {} @end + +/** CCEaseBounceIn action. + @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action. + @since v0.8.2 +*/ +@interface CCEaseBounceIn : CCEaseBounce {} @end + +/** EaseBounceOut action. + @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action. + @since v0.8.2 + */ +@interface CCEaseBounceOut : CCEaseBounce {} @end + +/** CCEaseBounceInOut action. + @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action. + @since v0.8.2 + */ +@interface CCEaseBounceInOut : CCEaseBounce {} @end + +/** CCEaseBackIn action. + @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action. + @since v0.8.2 + */ +@interface CCEaseBackIn : CCActionEase {} @end + +/** CCEaseBackOut action. + @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action. + @since v0.8.2 + */ +@interface CCEaseBackOut : CCActionEase {} @end + +/** CCEaseBackInOut action. + @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action. + @since v0.8.2 + */ +@interface CCEaseBackInOut : CCActionEase {} @end + diff --git a/libs/cocos2d/CCActionEase.m b/libs/cocos2d/CCActionEase.m new file mode 100644 index 0000000..f28be11 --- /dev/null +++ b/libs/cocos2d/CCActionEase.m @@ -0,0 +1,534 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2008-2009 Jason Booth + * + * 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. + * + */ + + +/* + * Elastic, Back and Bounce actions based on code from: + * http://github.com/NikhilK/silverlightfx/ + * + * by http://github.com/NikhilK + */ + +#import "CCActionEase.h" + +#ifndef M_PI_X_2 +#define M_PI_X_2 (float)M_PI * 2.0f +#endif + +#pragma mark EaseAction + +// +// EaseAction +// +@implementation CCActionEase + ++(id) actionWithAction: (CCActionInterval*) action +{ + return [[[self alloc] initWithAction: action] autorelease ]; +} + +-(id) initWithAction: (CCActionInterval*) action +{ + NSAssert( action!=nil, @"Ease: arguments must be non-nil"); + + if( (self=[super initWithDuration: action.duration]) ) + other = [action retain]; + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone:zone] initWithAction:[[other copy] autorelease]]; + return copy; +} + +-(void) dealloc +{ + [other release]; + [super dealloc]; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + [other startWithTarget:target_]; +} + +-(void) stop +{ + [other stop]; + [super stop]; +} + +-(void) update: (ccTime) t +{ + [other update: t]; +} + +-(CCActionInterval*) reverse +{ + return [[self class] actionWithAction: [other reverse]]; +} +@end + + +#pragma mark - +#pragma mark EaseRate + +// +// EaseRateAction +// +@implementation CCEaseRateAction +@synthesize rate; ++(id) actionWithAction: (CCActionInterval*) action rate:(float)aRate +{ + return [[[self alloc] initWithAction: action rate:aRate] autorelease ]; +} + +-(id) initWithAction: (CCActionInterval*) action rate:(float)aRate +{ + if( (self=[super initWithAction:action ]) ) + self.rate = aRate; + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone:zone] initWithAction:[[other copy] autorelease] rate:rate]; + return copy; +} + +-(void) dealloc +{ + [super dealloc]; +} + +-(CCActionInterval*) reverse +{ + return [[self class] actionWithAction: [other reverse] rate:1/rate]; +} +@end + +// +// EeseIn +// +@implementation CCEaseIn +-(void) update: (ccTime) t +{ + [other update: powf(t,rate)]; +} +@end + +// +// EaseOut +// +@implementation CCEaseOut +-(void) update: (ccTime) t +{ + [other update: powf(t,1/rate)]; +} +@end + +// +// EaseInOut +// +@implementation CCEaseInOut +-(void) update: (ccTime) t +{ + int sign =1; + int r = (int) rate; + if (r % 2 == 0) + sign = -1; + t *= 2; + if (t < 1) + [other update: 0.5f * powf (t, rate)]; + else + [other update: sign*0.5f * (powf (t-2, rate) + sign*2)]; +} + +// InOut and OutIn are symmetrical +-(CCActionInterval*) reverse +{ + return [[self class] actionWithAction: [other reverse] rate:rate]; +} + +@end + +#pragma mark - +#pragma mark EaseExponential + +// +// EaseExponentialIn +// +@implementation CCEaseExponentialIn +-(void) update: (ccTime) t +{ + [other update: (t==0) ? 0 : powf(2, 10 * (t/1 - 1)) - 1 * 0.001f]; +} + +- (CCActionInterval*) reverse +{ + return [CCEaseExponentialOut actionWithAction: [other reverse]]; +} +@end + +// +// EaseExponentialOut +// +@implementation CCEaseExponentialOut +-(void) update: (ccTime) t +{ + [other update: (t==1) ? 1 : (-powf(2, -10 * t/1) + 1)]; +} + +- (CCActionInterval*) reverse +{ + return [CCEaseExponentialIn actionWithAction: [other reverse]]; +} +@end + +// +// EaseExponentialInOut +// +@implementation CCEaseExponentialInOut +-(void) update: (ccTime) t +{ + t /= 0.5f; + if (t < 1) + t = 0.5f * powf(2, 10 * (t - 1)); + else + t = 0.5f * (-powf(2, -10 * (t -1) ) + 2); + + [other update:t]; +} +@end + + +#pragma mark - +#pragma mark EaseSin actions + +// +// EaseSineIn +// +@implementation CCEaseSineIn +-(void) update: (ccTime) t +{ + [other update:-1*cosf(t * (float)M_PI_2) +1]; +} + +- (CCActionInterval*) reverse +{ + return [CCEaseSineOut actionWithAction: [other reverse]]; +} +@end + +// +// EaseSineOut +// +@implementation CCEaseSineOut +-(void) update: (ccTime) t +{ + [other update:sinf(t * (float)M_PI_2)]; +} + +- (CCActionInterval*) reverse +{ + return [CCEaseSineIn actionWithAction: [other reverse]]; +} +@end + +// +// EaseSineInOut +// +@implementation CCEaseSineInOut +-(void) update: (ccTime) t +{ + [other update:-0.5f*(cosf( (float)M_PI*t) - 1)]; +} +@end + +#pragma mark - +#pragma mark EaseElastic actions + +// +// EaseElastic +// +@implementation CCEaseElastic + +@synthesize period = period_; + ++(id) actionWithAction: (CCActionInterval*) action +{ + return [[[self alloc] initWithAction:action period:0.3f] autorelease]; +} + ++(id) actionWithAction: (CCActionInterval*) action period:(float)period +{ + return [[[self alloc] initWithAction:action period:period] autorelease]; +} + +-(id) initWithAction: (CCActionInterval*) action +{ + return [self initWithAction:action period:0.3f]; +} + +-(id) initWithAction: (CCActionInterval*) action period:(float)period +{ + if( (self=[super initWithAction:action]) ) + period_ = period; + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone:zone] initWithAction:[[other copy] autorelease] period:period_]; + return copy; +} + +-(CCActionInterval*) reverse +{ + NSAssert(NO,@"Override me"); + return nil; +} + +@end + +// +// EaseElasticIn +// + +@implementation CCEaseElasticIn +-(void) update: (ccTime) t +{ + ccTime newT = 0; + if (t == 0 || t == 1) + newT = t; + + else { + float s = period_ / 4; + t = t - 1; + newT = -powf(2, 10 * t) * sinf( (t-s) *M_PI_X_2 / period_); + } + [other update:newT]; +} + +- (CCActionInterval*) reverse +{ + return [CCEaseElasticOut actionWithAction: [other reverse] period:period_]; +} + +@end + +// +// EaseElasticOut +// +@implementation CCEaseElasticOut + +-(void) update: (ccTime) t +{ + ccTime newT = 0; + if (t == 0 || t == 1) { + newT = t; + + } else { + float s = period_ / 4; + newT = powf(2, -10 * t) * sinf( (t-s) *M_PI_X_2 / period_) + 1; + } + [other update:newT]; +} + +- (CCActionInterval*) reverse +{ + return [CCEaseElasticIn actionWithAction: [other reverse] period:period_]; +} + +@end + +// +// EaseElasticInOut +// +@implementation CCEaseElasticInOut +-(void) update: (ccTime) t +{ + ccTime newT = 0; + + if( t == 0 || t == 1 ) + newT = t; + else { + t = t * 2; + if(! period_ ) + period_ = 0.3f * 1.5f; + ccTime s = period_ / 4; + + t = t -1; + if( t < 0 ) + newT = -0.5f * powf(2, 10 * t) * sinf((t - s) * M_PI_X_2 / period_); + else + newT = powf(2, -10 * t) * sinf((t - s) * M_PI_X_2 / period_) * 0.5f + 1; + } + [other update:newT]; +} + +- (CCActionInterval*) reverse +{ + return [CCEaseElasticInOut actionWithAction: [other reverse] period:period_]; +} + +@end + +#pragma mark - +#pragma mark EaseBounce actions + +// +// EaseBounce +// +@implementation CCEaseBounce +-(ccTime) bounceTime:(ccTime) t +{ + if (t < 1 / 2.75) { + return 7.5625f * t * t; + } + else if (t < 2 / 2.75) { + t -= 1.5f / 2.75f; + return 7.5625f * t * t + 0.75f; + } + else if (t < 2.5 / 2.75) { + t -= 2.25f / 2.75f; + return 7.5625f * t * t + 0.9375f; + } + + t -= 2.625f / 2.75f; + return 7.5625f * t * t + 0.984375f; +} +@end + +// +// EaseBounceIn +// + +@implementation CCEaseBounceIn + +-(void) update: (ccTime) t +{ + ccTime newT = 1 - [self bounceTime:1-t]; + [other update:newT]; +} + +- (CCActionInterval*) reverse +{ + return [CCEaseBounceOut actionWithAction: [other reverse]]; +} + +@end + +@implementation CCEaseBounceOut + +-(void) update: (ccTime) t +{ + ccTime newT = [self bounceTime:t]; + [other update:newT]; +} + +- (CCActionInterval*) reverse +{ + return [CCEaseBounceIn actionWithAction: [other reverse]]; +} + +@end + +@implementation CCEaseBounceInOut + +-(void) update: (ccTime) t +{ + ccTime newT = 0; + if (t < 0.5) { + t = t * 2; + newT = (1 - [self bounceTime:1-t] ) * 0.5f; + } else + newT = [self bounceTime:t * 2 - 1] * 0.5f + 0.5f; + + [other update:newT]; +} +@end + +#pragma mark - +#pragma mark Ease Back actions + +// +// EaseBackIn +// +@implementation CCEaseBackIn + +-(void) update: (ccTime) t +{ + ccTime overshoot = 1.70158f; + [other update: t * t * ((overshoot + 1) * t - overshoot)]; +} + +- (CCActionInterval*) reverse +{ + return [CCEaseBackOut actionWithAction: [other reverse]]; +} +@end + +// +// EaseBackOut +// +@implementation CCEaseBackOut +-(void) update: (ccTime) t +{ + ccTime overshoot = 1.70158f; + + t = t - 1; + [other update: t * t * ((overshoot + 1) * t + overshoot) + 1]; +} + +- (CCActionInterval*) reverse +{ + return [CCEaseBackIn actionWithAction: [other reverse]]; +} +@end + +// +// EaseBackInOut +// +@implementation CCEaseBackInOut + +-(void) update: (ccTime) t +{ + ccTime overshoot = 1.70158f * 1.525f; + + t = t * 2; + if (t < 1) + [other update: (t * t * ((overshoot + 1) * t - overshoot)) / 2]; + else { + t = t - 2; + [other update: (t * t * ((overshoot + 1) * t + overshoot)) / 2 + 1]; + } +} +@end diff --git a/libs/cocos2d/CCActionGrid.h b/libs/cocos2d/CCActionGrid.h new file mode 100644 index 0000000..13b6bc7 --- /dev/null +++ b/libs/cocos2d/CCActionGrid.h @@ -0,0 +1,165 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2009 On-Core + * + * 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 "CCActionInterval.h" +#import "CCActionInstant.h" +#import "CCGrid.h" + +@class CCGridBase; + +/** Base class for Grid actions */ +@interface CCGridAction : CCActionInterval +{ + ccGridSize gridSize_; +} + +/** size of the grid */ +@property (nonatomic,readwrite) ccGridSize gridSize; + +/** creates the action with size and duration */ ++(id) actionWithSize:(ccGridSize)size duration:(ccTime)d; +/** initializes the action with size and duration */ +-(id) initWithSize:(ccGridSize)gridSize duration:(ccTime)d; +/** returns the grid */ +-(CCGridBase *)grid; + +@end + +//////////////////////////////////////////////////////////// + +/** Base class for CCGrid3D actions. + Grid3D actions can modify a non-tiled grid. + */ +@interface CCGrid3DAction : CCGridAction +{ +} + +/** returns the vertex than belongs to certain position in the grid */ +-(ccVertex3F)vertex:(ccGridSize)pos; +/** returns the non-transformed vertex than belongs to certain position in the grid */ +-(ccVertex3F)originalVertex:(ccGridSize)pos; +/** sets a new vertex to a certain position of the grid */ +-(void)setVertex:(ccGridSize)pos vertex:(ccVertex3F)vertex; + +@end + +//////////////////////////////////////////////////////////// + +/** Base class for CCTiledGrid3D actions */ +@interface CCTiledGrid3DAction : CCGridAction +{ +} + +/** returns the tile that belongs to a certain position of the grid */ +-(ccQuad3)tile:(ccGridSize)pos; +/** returns the non-transformed tile that belongs to a certain position of the grid */ +-(ccQuad3)originalTile:(ccGridSize)pos; +/** sets a new tile to a certain position of the grid */ +-(void)setTile:(ccGridSize)pos coords:(ccQuad3)coords; + +@end + +//////////////////////////////////////////////////////////// + +/** CCAccelDeccelAmplitude action */ +@interface CCAccelDeccelAmplitude : CCActionInterval +{ + float rate; + CCActionInterval *other; +} + +/** amplitude rate */ +@property (nonatomic,readwrite) float rate; + +/** creates the action with an inner action that has the amplitude property, and a duration time */ ++(id)actionWithAction:(CCAction*)action duration:(ccTime)d; +/** initializes the action with an inner action that has the amplitude property, and a duration time */ +-(id)initWithAction:(CCAction*)action duration:(ccTime)d; + +@end + +//////////////////////////////////////////////////////////// + +/** CCAccelAmplitude action */ +@interface CCAccelAmplitude : CCActionInterval +{ + float rate; + CCActionInterval *other; +} + +/** amplitude rate */ +@property (nonatomic,readwrite) float rate; + +/** creates the action with an inner action that has the amplitude property, and a duration time */ ++(id)actionWithAction:(CCAction*)action duration:(ccTime)d; +/** initializes the action with an inner action that has the amplitude property, and a duration time */ +-(id)initWithAction:(CCAction*)action duration:(ccTime)d; + +@end + +//////////////////////////////////////////////////////////// + +/** CCDeccelAmplitude action */ +@interface CCDeccelAmplitude : CCActionInterval +{ + float rate; + CCActionInterval *other; +} + +/** amplitude rate */ +@property (nonatomic,readwrite) float rate; + +/** creates the action with an inner action that has the amplitude property, and a duration time */ ++(id)actionWithAction:(CCAction*)action duration:(ccTime)d; +/** initializes the action with an inner action that has the amplitude property, and a duration time */ +-(id)initWithAction:(CCAction*)action duration:(ccTime)d; + +@end + +//////////////////////////////////////////////////////////// + +/** CCStopGrid action. + Don't call this action if another grid action is active. + Call if you want to remove the the grid effect. Example: + [Sequence actions:[Lens ...], [StopGrid action], nil]; + */ +@interface CCStopGrid : CCActionInstant +{ +} +@end + +//////////////////////////////////////////////////////////// + +/** CCReuseGrid action */ +@interface CCReuseGrid : CCActionInstant +{ + int t; +} +/** creates an action with the number of times that the current grid will be reused */ ++(id) actionWithTimes: (int) times; +/** initializes an action with the number of times that the current grid will be reused */ +-(id) initWithTimes: (int) times; +@end diff --git a/libs/cocos2d/CCActionGrid.m b/libs/cocos2d/CCActionGrid.m new file mode 100644 index 0000000..b2d8f98 --- /dev/null +++ b/libs/cocos2d/CCActionGrid.m @@ -0,0 +1,386 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2009 On-Core + * + * 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 "CCActionGrid.h" +#import "CCDirector.h" + +#pragma mark - +#pragma mark GridAction + +@implementation CCGridAction + +@synthesize gridSize = gridSize_; + ++(id) actionWithSize:(ccGridSize)size duration:(ccTime)d +{ + return [[[self alloc] initWithSize:size duration:d ] autorelease]; +} + +-(id) initWithSize:(ccGridSize)gSize duration:(ccTime)d +{ + if ( (self = [super initWithDuration:d]) ) + { + gridSize_ = gSize; + } + + return self; +} + +-(void)startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + + CCGridBase *newgrid = [self grid]; + + CCNode *t = (CCNode*) target_; + CCGridBase *targetGrid = [t grid]; + + if ( targetGrid && targetGrid.reuseGrid > 0 ) + { + if ( targetGrid.active && targetGrid.gridSize.x == gridSize_.x && targetGrid.gridSize.y == gridSize_.y && [targetGrid isKindOfClass:[newgrid class]] ) + [targetGrid reuse]; + else + [NSException raise:@"GridBase" format:@"Cannot reuse grid"]; + } + else + { + if ( targetGrid && targetGrid.active ) + targetGrid.active = NO; + + t.grid = newgrid; + t.grid.active = YES; + } +} + +-(CCGridBase *)grid +{ + [NSException raise:@"GridBase" format:@"Abstract class needs implementation"]; + return nil; +} + +- (CCActionInterval*) reverse +{ + return [CCReverseTime actionWithAction:self]; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithSize:gridSize_ duration:duration_]; + return copy; +} +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark Grid3DAction + +@implementation CCGrid3DAction + +-(CCGridBase *)grid +{ + return [CCGrid3D gridWithSize:gridSize_]; +} + +-(ccVertex3F)vertex:(ccGridSize)pos +{ + CCGrid3D *g = (CCGrid3D *)[target_ grid]; + return [g vertex:pos]; +} + +-(ccVertex3F)originalVertex:(ccGridSize)pos +{ + CCGrid3D *g = (CCGrid3D *)[target_ grid]; + return [g originalVertex:pos]; +} + +-(void)setVertex:(ccGridSize)pos vertex:(ccVertex3F)vertex +{ + CCGrid3D *g = (CCGrid3D *)[target_ grid]; + return [g setVertex:pos vertex:vertex]; +} +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark TiledGrid3DAction + +@implementation CCTiledGrid3DAction + +-(CCGridBase *)grid +{ + return [CCTiledGrid3D gridWithSize:gridSize_]; +} + +-(ccQuad3)tile:(ccGridSize)pos +{ + CCTiledGrid3D *g = (CCTiledGrid3D *)[target_ grid]; + return [g tile:pos]; +} + +-(ccQuad3)originalTile:(ccGridSize)pos +{ + CCTiledGrid3D *g = (CCTiledGrid3D *)[target_ grid]; + return [g originalTile:pos]; +} + +-(void)setTile:(ccGridSize)pos coords:(ccQuad3)coords +{ + CCTiledGrid3D *g = (CCTiledGrid3D *)[target_ grid]; + [g setTile:pos coords:coords]; +} + +@end + +//////////////////////////////////////////////////////////// + +@interface CCActionInterval (Amplitude) +-(void)setAmplitudeRate:(CGFloat)amp; +-(CGFloat)getAmplitudeRate; +@end + +@implementation CCActionInterval (Amplitude) +-(void)setAmplitudeRate:(CGFloat)amp +{ + [NSException raise:@"IntervalAction (Amplitude)" format:@"Abstract class needs implementation"]; +} + +-(CGFloat)getAmplitudeRate +{ + [NSException raise:@"IntervalAction (Amplitude)" format:@"Abstract class needs implementation"]; + return 0; +} +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark AccelDeccelAmplitude + +@implementation CCAccelDeccelAmplitude + +@synthesize rate; + ++(id)actionWithAction:(CCAction*)action duration:(ccTime)d +{ + return [[[self alloc] initWithAction:action duration:d ] autorelease]; +} + +-(id)initWithAction:(CCAction *)action duration:(ccTime)d +{ + if ( (self = [super initWithDuration:d]) ) + { + rate = 1.0f; + other = [action retain]; + } + + return self; +} + +-(void)dealloc +{ + [other release]; + [super dealloc]; +} + +-(void)startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + [other startWithTarget:target_]; +} + +-(void) update: (ccTime) time +{ + float f = time*2; + + if (f > 1) + { + f -= 1; + f = 1 - f; + } + + [other setAmplitudeRate:powf(f, rate)]; + [other update:time]; +} + +- (CCActionInterval*) reverse +{ + return [CCAccelDeccelAmplitude actionWithAction:[other reverse] duration:duration_]; +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark AccelAmplitude + +@implementation CCAccelAmplitude + +@synthesize rate; + ++(id)actionWithAction:(CCAction*)action duration:(ccTime)d +{ + return [[[self alloc] initWithAction:action duration:d ] autorelease]; +} + +-(id)initWithAction:(CCAction *)action duration:(ccTime)d +{ + if ( (self = [super initWithDuration:d]) ) + { + rate = 1.0f; + other = [action retain]; + } + + return self; +} + +-(void)dealloc +{ + [other release]; + [super dealloc]; +} + +-(void)startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + [other startWithTarget:target_]; +} + +-(void) update: (ccTime) time +{ + [other setAmplitudeRate:powf(time, rate)]; + [other update:time]; +} + +- (CCActionInterval*) reverse +{ + return [CCAccelAmplitude actionWithAction:[other reverse] duration:self.duration]; +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark DeccelAmplitude + +@implementation CCDeccelAmplitude + +@synthesize rate; + ++(id)actionWithAction:(CCAction*)action duration:(ccTime)d +{ + return [[[self alloc] initWithAction:action duration:d ] autorelease]; +} + +-(id)initWithAction:(CCAction *)action duration:(ccTime)d +{ + if ( (self = [super initWithDuration:d]) ) + { + rate = 1.0f; + other = [action retain]; + } + + return self; +} + +-(void)dealloc +{ + [other release]; + [super dealloc]; +} + +-(void)startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + [other startWithTarget:target_]; +} + +-(void) update: (ccTime) time +{ + [other setAmplitudeRate:powf((1-time), rate)]; + [other update:time]; +} + +- (CCActionInterval*) reverse +{ + return [CCDeccelAmplitude actionWithAction:[other reverse] duration:self.duration]; +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark StopGrid + +@implementation CCStopGrid + +-(void)startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + + if ( [[self target] grid] && [[[self target] grid] active] ) { + [[[self target] grid] setActive: NO]; + +// [[self target] setGrid: nil]; + } +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark ReuseGrid + +@implementation CCReuseGrid + ++(id)actionWithTimes:(int)times +{ + return [[[self alloc] initWithTimes:times ] autorelease]; +} + +-(id)initWithTimes:(int)times +{ + if ( (self = [super init]) ) + t = times; + + return self; +} + +-(void)startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + + CCNode *myTarget = (CCNode*) [self target]; + if ( myTarget.grid && myTarget.grid.active ) + myTarget.grid.reuseGrid += t; +} + +@end diff --git a/libs/cocos2d/CCActionGrid3D.h b/libs/cocos2d/CCActionGrid3D.h new file mode 100644 index 0000000..a8003f4 --- /dev/null +++ b/libs/cocos2d/CCActionGrid3D.h @@ -0,0 +1,208 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2009 On-Core + * + * 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 "CCActionGrid.h" + +/** CCWaves3D action */ +@interface CCWaves3D : CCGrid3DAction +{ + int waves; + float amplitude; + float amplitudeRate; +} + +/** amplitude of the wave */ +@property (nonatomic,readwrite) float amplitude; +/** amplitude rate of the wave */ +@property (nonatomic,readwrite) float amplitudeRate; + ++(id)actionWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d; +-(id)initWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d; + +@end + +//////////////////////////////////////////////////////////// + +/** CCFlipX3D action */ +@interface CCFlipX3D : CCGrid3DAction +{ +} + +/** creates the action with duration */ ++(id) actionWithDuration:(ccTime)d; +/** initizlies the action with duration */ +-(id) initWithDuration:(ccTime)d; + +@end + +//////////////////////////////////////////////////////////// + +/** CCFlipY3D action */ +@interface CCFlipY3D : CCFlipX3D +{ +} + +@end + +//////////////////////////////////////////////////////////// + +/** CCLens3D action */ +@interface CCLens3D : CCGrid3DAction +{ + CGPoint position_; + CGPoint positionInPixels_; + float radius_; + float lensEffect_; + BOOL dirty_; +} + +/** lens effect. Defaults to 0.7 - 0 means no effect, 1 is very strong effect */ +@property (nonatomic,readwrite) float lensEffect; +/** lens center position in Points */ +@property (nonatomic,readwrite) CGPoint position; + +/** creates the action with center position in Points, radius, a grid size and duration */ ++(id)actionWithPosition:(CGPoint)pos radius:(float)r grid:(ccGridSize)gridSize duration:(ccTime)d; +/** initializes the action with center position in Points, radius, a grid size and duration */ +-(id)initWithPosition:(CGPoint)pos radius:(float)r grid:(ccGridSize)gridSize duration:(ccTime)d; + +@end + +//////////////////////////////////////////////////////////// + +/** CCRipple3D action */ +@interface CCRipple3D : CCGrid3DAction +{ + CGPoint position_; + CGPoint positionInPixels_; + float radius_; + int waves_; + float amplitude_; + float amplitudeRate_; +} + +/** center position in Points */ +@property (nonatomic,readwrite) CGPoint position; +/** amplitude */ +@property (nonatomic,readwrite) float amplitude; +/** amplitude rate */ +@property (nonatomic,readwrite) float amplitudeRate; + +/** creates the action with a position in points, radius, number of waves, amplitude, a grid size and duration */ ++(id)actionWithPosition:(CGPoint)pos radius:(float)r waves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d; +/** initializes the action with a position in points, radius, number of waves, amplitude, a grid size and duration */ +-(id)initWithPosition:(CGPoint)pos radius:(float)r waves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d; + +@end + +//////////////////////////////////////////////////////////// + +/** CCShaky3D action */ +@interface CCShaky3D : CCGrid3DAction +{ + int randrange; + BOOL shakeZ; +} + +/** creates the action with a range, shake Z vertices, a grid and duration */ ++(id)actionWithRange:(int)range shakeZ:(BOOL)shakeZ grid:(ccGridSize)gridSize duration:(ccTime)d; +/** initializes the action with a range, shake Z vertices, a grid and duration */ +-(id)initWithRange:(int)range shakeZ:(BOOL)shakeZ grid:(ccGridSize)gridSize duration:(ccTime)d; + +@end + +//////////////////////////////////////////////////////////// + +/** CCLiquid action */ +@interface CCLiquid : CCGrid3DAction +{ + int waves; + float amplitude; + float amplitudeRate; + +} + +/** amplitude */ +@property (nonatomic,readwrite) float amplitude; +/** amplitude rate */ +@property (nonatomic,readwrite) float amplitudeRate; + +/** creates the action with amplitude, a grid and duration */ ++(id)actionWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d; +/** initializes the action with amplitude, a grid and duration */ +-(id)initWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d; + +@end + +//////////////////////////////////////////////////////////// + +/** CCWaves action */ +@interface CCWaves : CCGrid3DAction +{ + int waves; + float amplitude; + float amplitudeRate; + BOOL vertical; + BOOL horizontal; +} + +/** amplitude */ +@property (nonatomic,readwrite) float amplitude; +/** amplitude rate */ +@property (nonatomic,readwrite) float amplitudeRate; + +/** initializes the action with amplitude, horizontal sin, vertical sin, a grid and duration */ ++(id)actionWithWaves:(int)wav amplitude:(float)amp horizontal:(BOOL)h vertical:(BOOL)v grid:(ccGridSize)gridSize duration:(ccTime)d; +/** creates the action with amplitude, horizontal sin, vertical sin, a grid and duration */ +-(id)initWithWaves:(int)wav amplitude:(float)amp horizontal:(BOOL)h vertical:(BOOL)v grid:(ccGridSize)gridSize duration:(ccTime)d; + +@end + +//////////////////////////////////////////////////////////// + +/** CCTwirl action */ +@interface CCTwirl : CCGrid3DAction +{ + CGPoint position_; + CGPoint positionInPixels_; + int twirls_; + float amplitude_; + float amplitudeRate_; +} + +/** twirl center */ +@property (nonatomic,readwrite) CGPoint position; +/** amplitude */ +@property (nonatomic,readwrite) float amplitude; +/** amplitude rate */ +@property (nonatomic,readwrite) float amplitudeRate; + +/** creates the action with center position, number of twirls, amplitude, a grid size and duration */ ++(id)actionWithPosition:(CGPoint)pos twirls:(int)t amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d; +/** initializes the action with center position, number of twirls, amplitude, a grid size and duration */ +-(id)initWithPosition:(CGPoint)pos twirls:(int)t amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d; + +@end diff --git a/libs/cocos2d/CCActionGrid3D.m b/libs/cocos2d/CCActionGrid3D.m new file mode 100644 index 0000000..1d4a783 --- /dev/null +++ b/libs/cocos2d/CCActionGrid3D.m @@ -0,0 +1,659 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2009 On-Core + * + * 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 "CCActionGrid3D.h" +#import "ccMacros.h" +#import "Support/CGPointExtension.h" + +#pragma mark - +#pragma mark Waves3D + +@implementation CCWaves3D + +@synthesize amplitude; +@synthesize amplitudeRate; + ++(id)actionWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d +{ + return [[[self alloc] initWithWaves:wav amplitude:amp grid:gridSize duration:d] autorelease]; +} + +-(id)initWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gSize duration:(ccTime)d +{ + if ( (self = [super initWithSize:gSize duration:d]) ) + { + waves = wav; + amplitude = amp; + amplitudeRate = 1.0f; + } + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithWaves:waves amplitude:amplitude grid:gridSize_ duration:duration_]; + return copy; +} + + +-(void)update:(ccTime)time +{ + int i, j; + + for( i = 0; i < (gridSize_.x+1); i++ ) + { + for( j = 0; j < (gridSize_.y+1); j++ ) + { + ccVertex3F v = [self originalVertex:ccg(i,j)]; + v.z += (sinf((CGFloat)M_PI*time*waves*2 + (v.y+v.x) * .01f) * amplitude * amplitudeRate); + [self setVertex:ccg(i,j) vertex:v]; + } + } +} +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark FlipX3D + +@implementation CCFlipX3D + ++(id) actionWithDuration:(ccTime)d +{ + return [[[self alloc] initWithSize:ccg(1,1) duration:d] autorelease]; +} + +-(id) initWithDuration:(ccTime)d +{ + return [super initWithSize:ccg(1,1) duration:d]; +} + +-(id)initWithSize:(ccGridSize)gSize duration:(ccTime)d +{ + if ( gSize.x != 1 || gSize.y != 1 ) + { + [NSException raise:@"FlipX3D" format:@"Grid size must be (1,1)"]; + } + + return [super initWithSize:gSize duration:d]; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithSize:gridSize_ duration:duration_]; + return copy; +} + + +-(void)update:(ccTime)time +{ + CGFloat angle = (CGFloat)M_PI * time; // 180 degrees + CGFloat mz = sinf( angle ); + angle = angle / 2.0f; // x calculates degrees from 0 to 90 + CGFloat mx = cosf( angle ); + + ccVertex3F v0, v1, v, diff; + + v0 = [self originalVertex:ccg(1,1)]; + v1 = [self originalVertex:ccg(0,0)]; + + CGFloat x0 = v0.x; + CGFloat x1 = v1.x; + CGFloat x; + ccGridSize a, b, c, d; + + if ( x0 > x1 ) + { + // Normal Grid + a = ccg(0,0); + b = ccg(0,1); + c = ccg(1,0); + d = ccg(1,1); + x = x0; + } + else + { + // Reversed Grid + c = ccg(0,0); + d = ccg(0,1); + a = ccg(1,0); + b = ccg(1,1); + x = x1; + } + + diff.x = ( x - x * mx ); + diff.z = fabsf( floorf( (x * mz) / 4.0f ) ); + +// bottom-left + v = [self originalVertex:a]; + v.x = diff.x; + v.z += diff.z; + [self setVertex:a vertex:v]; + +// upper-left + v = [self originalVertex:b]; + v.x = diff.x; + v.z += diff.z; + [self setVertex:b vertex:v]; + +// bottom-right + v = [self originalVertex:c]; + v.x -= diff.x; + v.z -= diff.z; + [self setVertex:c vertex:v]; + +// upper-right + v = [self originalVertex:d]; + v.x -= diff.x; + v.z -= diff.z; + [self setVertex:d vertex:v]; +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark FlipY3D + +@implementation CCFlipY3D + +-(void)update:(ccTime)time +{ + CGFloat angle = (CGFloat)M_PI * time; // 180 degrees + CGFloat mz = sinf( angle ); + angle = angle / 2.0f; // x calculates degrees from 0 to 90 + CGFloat my = cosf( angle ); + + ccVertex3F v0, v1, v, diff; + + v0 = [self originalVertex:ccg(1,1)]; + v1 = [self originalVertex:ccg(0,0)]; + + CGFloat y0 = v0.y; + CGFloat y1 = v1.y; + CGFloat y; + ccGridSize a, b, c, d; + + if ( y0 > y1 ) + { + // Normal Grid + a = ccg(0,0); + b = ccg(0,1); + c = ccg(1,0); + d = ccg(1,1); + y = y0; + } + else + { + // Reversed Grid + b = ccg(0,0); + a = ccg(0,1); + d = ccg(1,0); + c = ccg(1,1); + y = y1; + } + + diff.y = y - y * my; + diff.z = fabsf( floorf( (y * mz) / 4.0f ) ); + + // bottom-left + v = [self originalVertex:a]; + v.y = diff.y; + v.z += diff.z; + [self setVertex:a vertex:v]; + + // upper-left + v = [self originalVertex:b]; + v.y -= diff.y; + v.z -= diff.z; + [self setVertex:b vertex:v]; + + // bottom-right + v = [self originalVertex:c]; + v.y = diff.y; + v.z += diff.z; + [self setVertex:c vertex:v]; + + // upper-right + v = [self originalVertex:d]; + v.y -= diff.y; + v.z -= diff.z; + [self setVertex:d vertex:v]; +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark Lens3D + +@implementation CCLens3D + +@synthesize lensEffect=lensEffect_; + ++(id)actionWithPosition:(CGPoint)pos radius:(float)r grid:(ccGridSize)gridSize duration:(ccTime)d +{ + return [[[self alloc] initWithPosition:pos radius:r grid:gridSize duration:d] autorelease]; +} + +-(id)initWithPosition:(CGPoint)pos radius:(float)r grid:(ccGridSize)gSize duration:(ccTime)d +{ + if ( (self = [super initWithSize:gSize duration:d]) ) + { + position_ = ccp(-1,-1); + self.position = pos; + radius_ = r; + lensEffect_ = 0.7f; + dirty_ = YES; + } + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithPosition:position_ radius:radius_ grid:gridSize_ duration:duration_]; + return copy; +} + +-(void) setPosition:(CGPoint)pos +{ + if( ! CGPointEqualToPoint(pos, position_) ) { + position_ = pos; + positionInPixels_.x = pos.x * CC_CONTENT_SCALE_FACTOR(); + positionInPixels_.y = pos.y * CC_CONTENT_SCALE_FACTOR(); + + dirty_ = YES; + } +} + +-(CGPoint) position +{ + return position_; +} + +-(void)update:(ccTime)time +{ + if ( dirty_ ) + { + int i, j; + + for( i = 0; i < gridSize_.x+1; i++ ) + { + for( j = 0; j < gridSize_.y+1; j++ ) + { + ccVertex3F v = [self originalVertex:ccg(i,j)]; + CGPoint vect = ccpSub(positionInPixels_, ccp(v.x,v.y)); + CGFloat r = ccpLength(vect); + + if ( r < radius_ ) + { + r = radius_ - r; + CGFloat pre_log = r / radius_; + if ( pre_log == 0 ) pre_log = 0.001f; + float l = logf(pre_log) * lensEffect_; + float new_r = expf( l ) * radius_; + + if ( ccpLength(vect) > 0 ) + { + vect = ccpNormalize(vect); + CGPoint new_vect = ccpMult(vect, new_r); + v.z += ccpLength(new_vect) * lensEffect_; + } + } + + [self setVertex:ccg(i,j) vertex:v]; + } + } + + dirty_ = NO; + } +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark Ripple3D + +@implementation CCRipple3D + +@synthesize amplitude = amplitude_; +@synthesize amplitudeRate = amplitudeRate_; + ++(id)actionWithPosition:(CGPoint)pos radius:(float)r waves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d +{ + return [[[self alloc] initWithPosition:pos radius:r waves:wav amplitude:amp grid:gridSize duration:d] autorelease]; +} + +-(id)initWithPosition:(CGPoint)pos radius:(float)r waves:(int)wav amplitude:(float)amp grid:(ccGridSize)gSize duration:(ccTime)d +{ + if ( (self = [super initWithSize:gSize duration:d]) ) + { + self.position = pos; + radius_ = r; + waves_ = wav; + amplitude_ = amp; + amplitudeRate_ = 1.0f; + } + + return self; +} + +-(CGPoint) position +{ + return position_; +} + +-(void) setPosition:(CGPoint)pos +{ + position_ = pos; + positionInPixels_.x = pos.x * CC_CONTENT_SCALE_FACTOR(); + positionInPixels_.y = pos.y * CC_CONTENT_SCALE_FACTOR(); +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithPosition:position_ radius:radius_ waves:waves_ amplitude:amplitude_ grid:gridSize_ duration:duration_]; + return copy; +} + + +-(void)update:(ccTime)time +{ + int i, j; + + for( i = 0; i < (gridSize_.x+1); i++ ) + { + for( j = 0; j < (gridSize_.y+1); j++ ) + { + ccVertex3F v = [self originalVertex:ccg(i,j)]; + CGPoint vect = ccpSub(positionInPixels_, ccp(v.x,v.y)); + CGFloat r = ccpLength(vect); + + if ( r < radius_ ) + { + r = radius_ - r; + CGFloat rate = powf( r / radius_, 2); + v.z += (sinf( time*(CGFloat)M_PI*waves_*2 + r * 0.1f) * amplitude_ * amplitudeRate_ * rate ); + } + + [self setVertex:ccg(i,j) vertex:v]; + } + } +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark Shaky3D + +@implementation CCShaky3D + ++(id)actionWithRange:(int)range shakeZ:(BOOL)sz grid:(ccGridSize)gridSize duration:(ccTime)d +{ + return [[[self alloc] initWithRange:range shakeZ:sz grid:gridSize duration:d] autorelease]; +} + +-(id)initWithRange:(int)range shakeZ:(BOOL)sz grid:(ccGridSize)gSize duration:(ccTime)d +{ + if ( (self = [super initWithSize:gSize duration:d]) ) + { + randrange = range; + shakeZ = sz; + } + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithRange:randrange shakeZ:shakeZ grid:gridSize_ duration:duration_]; + return copy; +} + +-(void)update:(ccTime)time +{ + int i, j; + + for( i = 0; i < (gridSize_.x+1); i++ ) + { + for( j = 0; j < (gridSize_.y+1); j++ ) + { + ccVertex3F v = [self originalVertex:ccg(i,j)]; + v.x += ( rand() % (randrange*2) ) - randrange; + v.y += ( rand() % (randrange*2) ) - randrange; + if( shakeZ ) + v.z += ( rand() % (randrange*2) ) - randrange; + + [self setVertex:ccg(i,j) vertex:v]; + } + } +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark Liquid + +@implementation CCLiquid + +@synthesize amplitude; +@synthesize amplitudeRate; + ++(id)actionWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d +{ + return [[[self alloc] initWithWaves:wav amplitude:amp grid:gridSize duration:d] autorelease]; +} + +-(id)initWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gSize duration:(ccTime)d +{ + if ( (self = [super initWithSize:gSize duration:d]) ) + { + waves = wav; + amplitude = amp; + amplitudeRate = 1.0f; + } + + return self; +} + +-(void)update:(ccTime)time +{ + int i, j; + + for( i = 1; i < gridSize_.x; i++ ) + { + for( j = 1; j < gridSize_.y; j++ ) + { + ccVertex3F v = [self originalVertex:ccg(i,j)]; + v.x = (v.x + (sinf(time*(CGFloat)M_PI*waves*2 + v.x * .01f) * amplitude * amplitudeRate)); + v.y = (v.y + (sinf(time*(CGFloat)M_PI*waves*2 + v.y * .01f) * amplitude * amplitudeRate)); + [self setVertex:ccg(i,j) vertex:v]; + } + } +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithWaves:waves amplitude:amplitude grid:gridSize_ duration:duration_]; + return copy; +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark Waves + +@implementation CCWaves + +@synthesize amplitude; +@synthesize amplitudeRate; + ++(id)actionWithWaves:(int)wav amplitude:(float)amp horizontal:(BOOL)h vertical:(BOOL)v grid:(ccGridSize)gridSize duration:(ccTime)d +{ + return [[[self alloc] initWithWaves:wav amplitude:amp horizontal:h vertical:v grid:gridSize duration:d] autorelease]; +} + +-(id)initWithWaves:(int)wav amplitude:(float)amp horizontal:(BOOL)h vertical:(BOOL)v grid:(ccGridSize)gSize duration:(ccTime)d +{ + if ( (self = [super initWithSize:gSize duration:d]) ) + { + waves = wav; + amplitude = amp; + amplitudeRate = 1.0f; + horizontal = h; + vertical = v; + } + + return self; +} + +-(void)update:(ccTime)time +{ + int i, j; + + for( i = 0; i < (gridSize_.x+1); i++ ) + { + for( j = 0; j < (gridSize_.y+1); j++ ) + { + ccVertex3F v = [self originalVertex:ccg(i,j)]; + + if ( vertical ) + v.x = (v.x + (sinf(time*(CGFloat)M_PI*waves*2 + v.y * .01f) * amplitude * amplitudeRate)); + + if ( horizontal ) + v.y = (v.y + (sinf(time*(CGFloat)M_PI*waves*2 + v.x * .01f) * amplitude * amplitudeRate)); + + [self setVertex:ccg(i,j) vertex:v]; + } + } +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithWaves:waves amplitude:amplitude horizontal:horizontal vertical:vertical grid:gridSize_ duration:duration_]; + return copy; +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark Twirl + +@implementation CCTwirl + +@synthesize amplitude = amplitude_; +@synthesize amplitudeRate = amplitudeRate_; + ++(id)actionWithPosition:(CGPoint)pos twirls:(int)t amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d +{ + return [[[self alloc] initWithPosition:pos twirls:t amplitude:amp grid:gridSize duration:d] autorelease]; +} + +-(id)initWithPosition:(CGPoint)pos twirls:(int)t amplitude:(float)amp grid:(ccGridSize)gSize duration:(ccTime)d +{ + if ( (self = [super initWithSize:gSize duration:d]) ) + { + self.position = pos; + twirls_ = t; + amplitude_ = amp; + amplitudeRate_ = 1.0f; + } + + return self; +} + +-(void) setPosition:(CGPoint)pos +{ + position_ = pos; + positionInPixels_.x = pos.x * CC_CONTENT_SCALE_FACTOR(); + positionInPixels_.y = pos.y * CC_CONTENT_SCALE_FACTOR(); +} + +-(CGPoint) position +{ + return position_; +} + +-(void)update:(ccTime)time +{ + int i, j; + CGPoint c = positionInPixels_; + + for( i = 0; i < (gridSize_.x+1); i++ ) + { + for( j = 0; j < (gridSize_.y+1); j++ ) + { + ccVertex3F v = [self originalVertex:ccg(i,j)]; + + CGPoint avg = ccp(i-(gridSize_.x/2.0f), j-(gridSize_.y/2.0f)); + CGFloat r = ccpLength( avg ); + + CGFloat amp = 0.1f * amplitude_ * amplitudeRate_; + CGFloat a = r * cosf( (CGFloat)M_PI/2.0f + time * (CGFloat)M_PI * twirls_ * 2 ) * amp; + + float cosA = cosf(a); + float sinA = sinf(a); + + CGPoint d = { + sinA * (v.y-c.y) + cosA * (v.x-c.x), + cosA * (v.y-c.y) - sinA * (v.x-c.x) + }; + + v.x = c.x + d.x; + v.y = c.y + d.y; + + [self setVertex:ccg(i,j) vertex:v]; + } + } +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithPosition:position_ + twirls:twirls_ + amplitude:amplitude_ + grid:gridSize_ + duration:duration_]; + return copy; +} + + +@end diff --git a/libs/cocos2d/CCActionInstant.h b/libs/cocos2d/CCActionInstant.h new file mode 100644 index 0000000..5a1bc2d --- /dev/null +++ b/libs/cocos2d/CCActionInstant.h @@ -0,0 +1,205 @@ +/* + * 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. + * + */ + + +#import "CCAction.h" + +/** Instant actions are immediate actions. They don't have a duration like + the CCIntervalAction actions. +*/ +@interface CCActionInstant : CCFiniteTimeAction +{ +} +@end + +/** Show the node + */ + @interface CCShow : CCActionInstant +{ +} +@end + +/** Hide the node + */ +@interface CCHide : CCActionInstant +{ +} +@end + +/** Toggles the visibility of a node + */ +@interface CCToggleVisibility : CCActionInstant +{ +} +@end + +/** Flips the sprite horizontally + @since v0.99.0 + */ +@interface CCFlipX : CCActionInstant +{ + BOOL flipX; +} ++(id) actionWithFlipX:(BOOL)x; +-(id) initWithFlipX:(BOOL)x; +@end + +/** Flips the sprite vertically + @since v0.99.0 + */ +@interface CCFlipY : CCActionInstant +{ + BOOL flipY; +} ++(id) actionWithFlipY:(BOOL)y; +-(id) initWithFlipY:(BOOL)y; +@end + +/** Places the node in a certain position + */ +@interface CCPlace : CCActionInstant +{ + CGPoint position; +} +/** creates a Place action with a position */ ++(id) actionWithPosition: (CGPoint) pos; +/** Initializes a Place action with a position */ +-(id) initWithPosition: (CGPoint) pos; +@end + +/** Calls a 'callback' + */ +@interface CCCallFunc : CCActionInstant +{ + id targetCallback_; + SEL selector_; +} + +/** Target that will be called */ +@property (nonatomic, readwrite, retain) id targetCallback; + +/** creates the action with the callback */ ++(id) actionWithTarget: (id) t selector:(SEL) s; +/** initializes the action with the callback */ +-(id) initWithTarget: (id) t selector:(SEL) s; +/** exeuctes the callback */ +-(void) execute; +@end + +/** Calls a 'callback' with the node as the first argument. + N means Node + */ +@interface CCCallFuncN : CCCallFunc +{ +} +@end + +typedef void (*CC_CALLBACK_ND)(id, SEL, id, void *); +/** Calls a 'callback' with the node as the first argument and the 2nd argument is data. + * ND means: Node and Data. Data is void *, so it could be anything. + */ +@interface CCCallFuncND : CCCallFuncN +{ + void *data_; + CC_CALLBACK_ND callbackMethod_; +} + +/** Invocation object that has the target#selector and the parameters */ +@property (nonatomic,readwrite) CC_CALLBACK_ND callbackMethod; + +/** creates the action with the callback and the data to pass as an argument */ ++(id) actionWithTarget: (id) t selector:(SEL) s data:(void*)d; +/** initializes the action with the callback and the data to pass as an argument */ +-(id) initWithTarget:(id) t selector:(SEL) s data:(void*) d; +@end + +/** Calls a 'callback' with an object as the first argument. + O means Object. + @since v0.99.5 + */ +@interface CCCallFuncO : CCCallFunc +{ + id object_; +} +/** object to be passed as argument */ +@property (nonatomic, readwrite, retain) id object; + +/** creates the action with the callback and the object to pass as an argument */ ++(id) actionWithTarget: (id) t selector:(SEL) s object:(id)object; +/** initializes the action with the callback and the object to pass as an argument */ +-(id) initWithTarget:(id) t selector:(SEL) s object:(id)object; + +@end + +#pragma mark Blocks Support + +#if NS_BLOCKS_AVAILABLE + +/** Executes a callback using a block. + */ +@interface CCCallBlock : CCActionInstant +{ + void (^block_)(); +} + +/** creates the action with the specified block, to be used as a callback. + The block will be "copied". + */ ++(id) actionWithBlock:(void(^)())block; + +/** initialized the action with the specified block, to be used as a callback. + The block will be "copied". + */ +-(id) initWithBlock:(void(^)())block; + +/** executes the callback */ +-(void) execute; +@end + +@class CCNode; + +/** Executes a callback using a block with a single CCNode parameter. + */ +@interface CCCallBlockN : CCActionInstant +{ + void (^block_)(CCNode *); +} + +/** creates the action with the specified block, to be used as a callback. + The block will be "copied". + */ ++(id) actionWithBlock:(void(^)(CCNode *node))block; + +/** initialized the action with the specified block, to be used as a callback. + The block will be "copied". + */ +-(id) initWithBlock:(void(^)(CCNode *node))block; + +/** executes the callback */ +-(void) execute; +@end + +#endif diff --git a/libs/cocos2d/CCActionInstant.m b/libs/cocos2d/CCActionInstant.m new file mode 100644 index 0000000..e7f6fad --- /dev/null +++ b/libs/cocos2d/CCActionInstant.m @@ -0,0 +1,477 @@ +/* + * 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. + * + */ + + +#import "CCBlockSupport.h" +#import "CCActionInstant.h" +#import "CCNode.h" +#import "CCSprite.h" + + +// +// InstantAction +// +#pragma mark CCActionInstant + +@implementation CCActionInstant + +-(id) init +{ + if( (self=[super init]) ) + duration_ = 0; + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCActionInstant *copy = [[[self class] allocWithZone: zone] init]; + return copy; +} + +- (BOOL) isDone +{ + return YES; +} + +-(void) step: (ccTime) dt +{ + [self update: 1]; +} + +-(void) update: (ccTime) t +{ + // ignore +} + +-(CCFiniteTimeAction*) reverse +{ + return [[self copy] autorelease]; +} +@end + +// +// Show +// +#pragma mark CCShow + +@implementation CCShow +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + ((CCNode *)target_).visible = YES; +} + +-(CCFiniteTimeAction*) reverse +{ + return [CCHide action]; +} +@end + +// +// Hide +// +#pragma mark CCHide + +@implementation CCHide +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + ((CCNode *)target_).visible = NO; +} + +-(CCFiniteTimeAction*) reverse +{ + return [CCShow action]; +} +@end + +// +// ToggleVisibility +// +#pragma mark CCToggleVisibility + +@implementation CCToggleVisibility +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + ((CCNode *)target_).visible = !((CCNode *)target_).visible; +} +@end + +// +// FlipX +// +#pragma mark CCFlipX + +@implementation CCFlipX ++(id) actionWithFlipX:(BOOL)x +{ + return [[[self alloc] initWithFlipX:x] autorelease]; +} + +-(id) initWithFlipX:(BOOL)x +{ + if(( self=[super init])) + flipX = x; + + return self; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + [(CCSprite*)aTarget setFlipX:flipX]; +} + +-(CCFiniteTimeAction*) reverse +{ + return [CCFlipX actionWithFlipX:!flipX]; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCActionInstant *copy = [[[self class] allocWithZone: zone] initWithFlipX:flipX]; + return copy; +} +@end + +// +// FlipY +// +#pragma mark CCFlipY + +@implementation CCFlipY ++(id) actionWithFlipY:(BOOL)y +{ + return [[[self alloc] initWithFlipY:y] autorelease]; +} + +-(id) initWithFlipY:(BOOL)y +{ + if(( self=[super init])) + flipY = y; + + return self; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + [(CCSprite*)aTarget setFlipY:flipY]; +} + +-(CCFiniteTimeAction*) reverse +{ + return [CCFlipY actionWithFlipY:!flipY]; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCActionInstant *copy = [[[self class] allocWithZone: zone] initWithFlipY:flipY]; + return copy; +} +@end + + +// +// Place +// +#pragma mark CCPlace + +@implementation CCPlace ++(id) actionWithPosition: (CGPoint) pos +{ + return [[[self alloc]initWithPosition:pos]autorelease]; +} + +-(id) initWithPosition: (CGPoint) pos +{ + if( (self=[super init]) ) + position = pos; + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCActionInstant *copy = [[[self class] allocWithZone: zone] initWithPosition: position]; + return copy; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + ((CCNode *)target_).position = position; +} + +@end + +// +// CallFunc +// +#pragma mark CCCallFunc + +@implementation CCCallFunc + +@synthesize targetCallback = targetCallback_; + ++(id) actionWithTarget: (id) t selector:(SEL) s +{ + return [[[self alloc] initWithTarget: t selector: s] autorelease]; +} + +-(id) initWithTarget: (id) t selector:(SEL) s +{ + if( (self=[super init]) ) { + self.targetCallback = t; + selector_ = s; + } + return self; +} + +-(NSString*) description +{ + return [NSString stringWithFormat:@"<%@ = %08X | Tag = %i | target = %@ | selector = %@>", + [self class], + self, + tag_, + [targetCallback_ class], + NSStringFromSelector(selector_) + ]; +} + +-(void) dealloc +{ + [targetCallback_ release]; + [super dealloc]; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCActionInstant *copy = [[[self class] allocWithZone: zone] initWithTarget:targetCallback_ selector:selector_]; + return copy; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + [self execute]; +} + +-(void) execute +{ + [targetCallback_ performSelector:selector_]; +} +@end + +// +// CallFuncN +// +#pragma mark CCCallFuncN + +@implementation CCCallFuncN + +-(void) execute +{ + [targetCallback_ performSelector:selector_ withObject:target_]; +} +@end + +// +// CallFuncND +// +#pragma mark CCCallFuncND + +@implementation CCCallFuncND + +@synthesize callbackMethod = callbackMethod_; + ++(id) actionWithTarget:(id)t selector:(SEL)s data:(void*)d +{ + return [[[self alloc] initWithTarget:t selector:s data:d] autorelease]; +} + +-(id) initWithTarget:(id)t selector:(SEL)s data:(void*)d +{ + if( (self=[super initWithTarget:t selector:s]) ) { + data_ = d; + +#if COCOS2D_DEBUG + NSMethodSignature * sig = [t methodSignatureForSelector:s]; // added + NSAssert(sig !=0 , @"Signature not found for selector - does it have the following form? -(void)name:(id)sender data:(void*)data"); +#endif + callbackMethod_ = (CC_CALLBACK_ND) [t methodForSelector:s]; + } + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCActionInstant *copy = [[[self class] allocWithZone: zone] initWithTarget:targetCallback_ selector:selector_ data:data_]; + return copy; +} + +-(void) dealloc +{ + // nothing to dealloc really. Everything is dealloc on super (CCCallFuncN) + [super dealloc]; +} + +-(void) execute +{ + callbackMethod_(targetCallback_,selector_,target_, data_); +} +@end + +@implementation CCCallFuncO +@synthesize object = object_; + ++(id) actionWithTarget: (id) t selector:(SEL) s object:(id)object +{ + return [[[self alloc] initWithTarget:t selector:s object:object] autorelease]; +} + +-(id) initWithTarget:(id) t selector:(SEL) s object:(id)object +{ + if( (self=[super initWithTarget:t selector:s] ) ) + self.object = object; + + return self; +} + +- (void) dealloc +{ + [object_ release]; + [super dealloc]; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCActionInstant *copy = [[[self class] allocWithZone: zone] initWithTarget:targetCallback_ selector:selector_ object:object_]; + return copy; +} + + +-(void) execute +{ + [targetCallback_ performSelector:selector_ withObject:object_]; +} + +@end + + +#pragma mark - +#pragma mark Blocks + +#if NS_BLOCKS_AVAILABLE + +#pragma mark CCCallBlock + +@implementation CCCallBlock + ++(id) actionWithBlock:(void(^)())block +{ + return [[[self alloc] initWithBlock:block] autorelease]; +} + +-(id) initWithBlock:(void(^)())block +{ + if ((self = [super init])) + block_ = [block copy]; + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCActionInstant *copy = [[[self class] allocWithZone: zone] initWithBlock:block_]; + return copy; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + [self execute]; +} + +-(void) execute +{ + block_(); +} + +-(void) dealloc +{ + [block_ release]; + [super dealloc]; +} + +@end + +#pragma mark CCCallBlockN + +@implementation CCCallBlockN + ++(id) actionWithBlock:(void(^)(CCNode *node))block +{ + return [[[self alloc] initWithBlock:block] autorelease]; +} + +-(id) initWithBlock:(void(^)(CCNode *node))block +{ + if ((self = [super init])) + block_ = [block copy]; + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCActionInstant *copy = [[[self class] allocWithZone: zone] initWithBlock:block_]; + return copy; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + [self execute]; +} + +-(void) execute +{ + block_(target_); +} + +-(void) dealloc +{ + [block_ release]; + [super dealloc]; +} + +@end + + +#endif // NS_BLOCKS_AVAILABLE diff --git a/libs/cocos2d/CCActionInterval.h b/libs/cocos2d/CCActionInterval.h new file mode 100644 index 0000000..c667963 --- /dev/null +++ b/libs/cocos2d/CCActionInterval.h @@ -0,0 +1,421 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2008-2011 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 "CCNode.h" +#import "CCAction.h" +#import "CCProtocols.h" + +#include + +/** An interval action is an action that takes place within a certain period of time. +It has an start time, and a finish time. The finish time is the parameter +duration plus the start time. + +These CCActionInterval actions have some interesting properties, like: + - They can run normally (default) + - They can run reversed with the reverse method + - They can run with the time altered with the Accelerate, AccelDeccel and Speed actions. + +For example, you can simulate a Ping Pong effect running the action normally and +then running it again in Reverse mode. + +Example: + + CCAction * pingPongAction = [CCSequence actions: action, [action reverse], nil]; +*/ +@interface CCActionInterval: CCFiniteTimeAction +{ + ccTime elapsed_; + BOOL firstTick_; +} + +/** how many seconds had elapsed since the actions started to run. */ +@property (nonatomic,readonly) ccTime elapsed; + +/** creates the action */ ++(id) actionWithDuration: (ccTime) d; +/** initializes the action */ +-(id) initWithDuration: (ccTime) d; +/** returns YES if the action has finished */ +-(BOOL) isDone; +/** returns a reversed action */ +- (CCActionInterval*) reverse; +@end + +/** Runs actions sequentially, one after another + */ +@interface CCSequence : CCActionInterval +{ + CCFiniteTimeAction *actions_[2]; + ccTime split_; + int last_; +} +/** helper contructor to create an array of sequenceable actions */ ++(id) actions: (CCFiniteTimeAction*) action1, ... NS_REQUIRES_NIL_TERMINATION; +/** helper contructor to create an array of sequenceable actions given an array */ ++(id) actionsWithArray: (NSArray*) actions; +/** creates the action */ ++(id) actionOne:(CCFiniteTimeAction*)actionOne two:(CCFiniteTimeAction*)actionTwo; +/** initializes the action */ +-(id) initOne:(CCFiniteTimeAction*)actionOne two:(CCFiniteTimeAction*)actionTwo; +@end + + +/** Repeats an action a number of times. + * To repeat an action forever use the CCRepeatForever action. + */ +@interface CCRepeat : CCActionInterval +{ + NSUInteger times_; + NSUInteger total_; + CCFiniteTimeAction *innerAction_; +} + +/** Inner action */ +@property (nonatomic,readwrite,retain) CCFiniteTimeAction *innerAction; + +/** creates a CCRepeat action. Times is an unsigned integer between 1 and MAX_UINT */ ++(id) actionWithAction:(CCFiniteTimeAction*)action times: (NSUInteger)times; +/** initializes a CCRepeat action. Times is an unsigned integer between 1 and MAX_UINT */ +-(id) initWithAction:(CCFiniteTimeAction*)action times: (NSUInteger)times; +@end + +/** Spawn a new action immediately + */ +@interface CCSpawn : CCActionInterval +{ + CCFiniteTimeAction *one_; + CCFiniteTimeAction *two_; +} +/** helper constructor to create an array of spawned actions */ ++(id) actions: (CCFiniteTimeAction*) action1, ... NS_REQUIRES_NIL_TERMINATION; +/** helper contructor to create an array of spawned actions given an array */ ++(id) actionsWithArray: (NSArray*) actions; +/** creates the Spawn action */ ++(id) actionOne: (CCFiniteTimeAction*) one two:(CCFiniteTimeAction*) two; +/** initializes the Spawn action with the 2 actions to spawn */ +-(id) initOne: (CCFiniteTimeAction*) one two:(CCFiniteTimeAction*) two; +@end + +/** Rotates a CCNode object to a certain angle by modifying it's + rotation attribute. + The direction will be decided by the shortest angle. +*/ +@interface CCRotateTo : CCActionInterval +{ + float dstAngle_; + float startAngle_; + float diffAngle_; +} +/** creates the action */ ++(id) actionWithDuration:(ccTime)duration angle:(float)angle; +/** initializes the action */ +-(id) initWithDuration:(ccTime)duration angle:(float)angle; +@end + +/** Rotates a CCNode object clockwise a number of degrees by modiying it's rotation attribute. +*/ +@interface CCRotateBy : CCActionInterval +{ + float angle_; + float startAngle_; +} +/** creates the action */ ++(id) actionWithDuration:(ccTime)duration angle:(float)deltaAngle; +/** initializes the action */ +-(id) initWithDuration:(ccTime)duration angle:(float)deltaAngle; +@end + +/** Moves a CCNode object to the position x,y. x and y are absolute coordinates by modifying it's position attribute. +*/ +@interface CCMoveTo : CCActionInterval +{ + CGPoint endPosition_; + CGPoint startPosition_; + CGPoint delta_; +} +/** creates the action */ ++(id) actionWithDuration:(ccTime)duration position:(CGPoint)position; +/** initializes the action */ +-(id) initWithDuration:(ccTime)duration position:(CGPoint)position; +@end + +/** Moves a CCNode object x,y pixels by modifying it's position attribute. + x and y are relative to the position of the object. + Duration is is seconds. +*/ +@interface CCMoveBy : CCMoveTo +{ +} +/** creates the action */ ++(id) actionWithDuration: (ccTime)duration position:(CGPoint)deltaPosition; +/** initializes the action */ +-(id) initWithDuration: (ccTime)duration position:(CGPoint)deltaPosition; +@end + +/** Skews a CCNode object to given angles by modifying it's skewX and skewY attributes + @since v1.0 + */ +@interface CCSkewTo : CCActionInterval +{ + float skewX_; + float skewY_; + float startSkewX_; + float startSkewY_; + float endSkewX_; + float endSkewY_; + float deltaX_; + float deltaY_; +} +/** creates the action */ ++(id) actionWithDuration:(ccTime)t skewX:(float)sx skewY:(float)sy; +/** initializes the action */ +-(id) initWithDuration:(ccTime)t skewX:(float)sx skewY:(float)sy; +@end + +/** Skews a CCNode object by skewX and skewY degrees + @since v1.0 + */ +@interface CCSkewBy : CCSkewTo +{ +} +@end + +/** Moves a CCNode object simulating a parabolic jump movement by modifying it's position attribute. +*/ + @interface CCJumpBy : CCActionInterval +{ + CGPoint startPosition_; + CGPoint delta_; + ccTime height_; + NSUInteger jumps_; +} +/** creates the action */ ++(id) actionWithDuration: (ccTime)duration position:(CGPoint)position height:(ccTime)height jumps:(NSUInteger)jumps; +/** initializes the action */ +-(id) initWithDuration: (ccTime)duration position:(CGPoint)position height:(ccTime)height jumps:(NSUInteger)jumps; +@end + +/** Moves a CCNode object to a parabolic position simulating a jump movement by modifying it's position attribute. +*/ + @interface CCJumpTo : CCJumpBy +{ +} +@end + +/** bezier configuration structure + */ +typedef struct _ccBezierConfig { + //! end position of the bezier + CGPoint endPosition; + //! Bezier control point 1 + CGPoint controlPoint_1; + //! Bezier control point 2 + CGPoint controlPoint_2; +} ccBezierConfig; + +/** An action that moves the target with a cubic Bezier curve by a certain distance. + */ +@interface CCBezierBy : CCActionInterval +{ + ccBezierConfig config_; + CGPoint startPosition_; +} + +/** creates the action with a duration and a bezier configuration */ ++(id) actionWithDuration: (ccTime) t bezier:(ccBezierConfig) c; + +/** initializes the action with a duration and a bezier configuration */ +-(id) initWithDuration: (ccTime) t bezier:(ccBezierConfig) c; +@end + +/** An action that moves the target with a cubic Bezier curve to a destination point. + @since v0.8.2 + */ +@interface CCBezierTo : CCBezierBy +{ +} +@end + +/** Scales a CCNode object to a zoom factor by modifying it's scale attribute. + @warning This action doesn't support "reverse" + */ +@interface CCScaleTo : CCActionInterval +{ + float scaleX_; + float scaleY_; + float startScaleX_; + float startScaleY_; + float endScaleX_; + float endScaleY_; + float deltaX_; + float deltaY_; +} +/** creates the action with the same scale factor for X and Y */ ++(id) actionWithDuration: (ccTime)duration scale:(float) s; +/** initializes the action with the same scale factor for X and Y */ +-(id) initWithDuration: (ccTime)duration scale:(float) s; +/** creates the action with and X factor and a Y factor */ ++(id) actionWithDuration: (ccTime)duration scaleX:(float) sx scaleY:(float)sy; +/** initializes the action with and X factor and a Y factor */ +-(id) initWithDuration: (ccTime)duration scaleX:(float) sx scaleY:(float)sy; +@end + +/** Scales a CCNode object a zoom factor by modifying it's scale attribute. +*/ +@interface CCScaleBy : CCScaleTo +{ +} +@end + +/** Blinks a CCNode object by modifying it's visible attribute +*/ +@interface CCBlink : CCActionInterval +{ + NSUInteger times_; +} +/** creates the action */ ++(id) actionWithDuration: (ccTime)duration blinks:(NSUInteger)blinks; +/** initilizes the action */ +-(id) initWithDuration: (ccTime)duration blinks:(NSUInteger)blinks; +@end + +/** Fades In an object that implements the CCRGBAProtocol protocol. It modifies the opacity from 0 to 255. + The "reverse" of this action is FadeOut + */ +@interface CCFadeIn : CCActionInterval +{ +} +@end + +/** Fades Out an object that implements the CCRGBAProtocol protocol. It modifies the opacity from 255 to 0. + The "reverse" of this action is FadeIn +*/ +@interface CCFadeOut : CCActionInterval +{ +} +@end + +/** Fades an object that implements the CCRGBAProtocol protocol. It modifies the opacity from the current value to a custom one. + @warning This action doesn't support "reverse" + */ +@interface CCFadeTo : CCActionInterval +{ + GLubyte toOpacity_; + GLubyte fromOpacity_; +} +/** creates an action with duration and opactiy */ ++(id) actionWithDuration:(ccTime)duration opacity:(GLubyte)opactiy; +/** initializes the action with duration and opacity */ +-(id) initWithDuration:(ccTime)duration opacity:(GLubyte)opacity; +@end + +/** Tints a CCNode that implements the CCNodeRGB protocol from current tint to a custom one. + @warning This action doesn't support "reverse" + @since v0.7.2 +*/ +@interface CCTintTo : CCActionInterval +{ + ccColor3B to_; + ccColor3B from_; +} +/** creates an action with duration and color */ ++(id) actionWithDuration:(ccTime)duration red:(GLubyte)red green:(GLubyte)green blue:(GLubyte)blue; +/** initializes the action with duration and color */ +-(id) initWithDuration:(ccTime)duration red:(GLubyte)red green:(GLubyte)green blue:(GLubyte)blue; +@end + +/** Tints a CCNode that implements the CCNodeRGB protocol from current tint to a custom one. + @since v0.7.2 + */ +@interface CCTintBy : CCActionInterval +{ + GLshort deltaR_, deltaG_, deltaB_; + GLshort fromR_, fromG_, fromB_; +} +/** creates an action with duration and color */ ++(id) actionWithDuration:(ccTime)duration red:(GLshort)deltaRed green:(GLshort)deltaGreen blue:(GLshort)deltaBlue; +/** initializes the action with duration and color */ +-(id) initWithDuration:(ccTime)duration red:(GLshort)deltaRed green:(GLshort)deltaGreen blue:(GLshort)deltaBlue; +@end + +/** Delays the action a certain amount of seconds +*/ +@interface CCDelayTime : CCActionInterval +{ +} +@end + +/** Executes an action in reverse order, from time=duration to time=0 + + @warning Use this action carefully. This action is not + sequenceable. Use it as the default "reversed" method + of your own actions, but using it outside the "reversed" + scope is not recommended. +*/ +@interface CCReverseTime : CCActionInterval +{ + CCFiniteTimeAction * other_; +} +/** creates the action */ ++(id) actionWithAction: (CCFiniteTimeAction*) action; +/** initializes the action */ +-(id) initWithAction: (CCFiniteTimeAction*) action; +@end + + +@class CCAnimation; +@class CCTexture2D; +/** Animates a sprite given the name of an Animation */ +@interface CCAnimate : CCActionInterval +{ + CCAnimation *animation_; + id origFrame_; + BOOL restoreOriginalFrame_; +} +/** animation used for the animage */ +@property (readwrite,nonatomic,retain) CCAnimation * animation; + +/** creates the action with an Animation and will restore the original frame when the animation is over */ ++(id) actionWithAnimation:(CCAnimation*) a; +/** initializes the action with an Animation and will restore the original frame when the animtion is over */ +-(id) initWithAnimation:(CCAnimation*) a; +/** creates the action with an Animation */ ++(id) actionWithAnimation:(CCAnimation*) a restoreOriginalFrame:(BOOL)b; +/** initializes the action with an Animation */ +-(id) initWithAnimation:(CCAnimation*) a restoreOriginalFrame:(BOOL)b; +/** creates an action with a duration, animation and depending of the restoreOriginalFrame, it will restore the original frame or not. + The 'delay' parameter of the animation will be overrided by the duration parameter. + @since v0.99.0 + */ ++(id) actionWithDuration:(ccTime)duration animation:(CCAnimation*)animation restoreOriginalFrame:(BOOL)b; +/** initializes an action with a duration, animation and depending of the restoreOriginalFrame, it will restore the original frame or not. + The 'delay' parameter of the animation will be overrided by the duration parameter. + @since v0.99.0 + */ +-(id) initWithDuration:(ccTime)duration animation:(CCAnimation*)animation restoreOriginalFrame:(BOOL)b; +@end diff --git a/libs/cocos2d/CCActionInterval.m b/libs/cocos2d/CCActionInterval.m new file mode 100644 index 0000000..ef374bf --- /dev/null +++ b/libs/cocos2d/CCActionInterval.m @@ -0,0 +1,1354 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2008-2011 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 "CCActionInterval.h" +#import "CCSprite.h" +#import "CCSpriteFrame.h" +#import "CCAnimation.h" +#import "CCNode.h" +#import "Support/CGPointExtension.h" + +// +// IntervalAction +// +#pragma mark - +#pragma mark IntervalAction +@implementation CCActionInterval + +@synthesize elapsed = elapsed_; + +-(id) init +{ + NSAssert(NO, @"IntervalActionInit: Init not supported. Use InitWithDuration"); + [self release]; + return nil; +} + ++(id) actionWithDuration: (ccTime) d +{ + return [[[self alloc] initWithDuration:d ] autorelease]; +} + +-(id) initWithDuration: (ccTime) d +{ + if( (self=[super init]) ) { + duration_ = d; + + // prevent division by 0 + // This comparison could be in step:, but it might decrease the performance + // by 3% in heavy based action games. + if( duration_ == 0 ) + duration_ = FLT_EPSILON; + elapsed_ = 0; + firstTick_ = YES; + } + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration: [self duration] ]; + return copy; +} + +- (BOOL) isDone +{ + return (elapsed_ >= duration_); +} + +-(void) step: (ccTime) dt +{ + if( firstTick_ ) { + firstTick_ = NO; + elapsed_ = 0; + } else + elapsed_ += dt; + + [self update: MIN(1, elapsed_/duration_)]; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + elapsed_ = 0.0f; + firstTick_ = YES; +} + +- (CCActionInterval*) reverse +{ + NSAssert(NO, @"CCIntervalAction: reverse not implemented."); + return nil; +} +@end + +// +// Sequence +// +#pragma mark - +#pragma mark Sequence +@implementation CCSequence ++(id) actions: (CCFiniteTimeAction*) action1, ... +{ + va_list params; + va_start(params,action1); + + CCFiniteTimeAction *now; + CCFiniteTimeAction *prev = action1; + + while( action1 ) { + now = va_arg(params,CCFiniteTimeAction*); + if ( now ) + prev = [self actionOne: prev two: now]; + else + break; + } + va_end(params); + return prev; +} + ++(id) actionsWithArray: (NSArray*) actions +{ + CCFiniteTimeAction *prev = [actions objectAtIndex:0]; + + for (int i = 1; i < [actions count]; i++) + prev = [self actionOne:prev two:[actions objectAtIndex:i]]; + + return prev; +} + ++(id) actionOne: (CCFiniteTimeAction*) one two: (CCFiniteTimeAction*) two +{ + return [[[self alloc] initOne:one two:two ] autorelease]; +} + +-(id) initOne: (CCFiniteTimeAction*) one two: (CCFiniteTimeAction*) two +{ + NSAssert( one!=nil && two!=nil, @"Sequence: arguments must be non-nil"); + NSAssert( one!=actions_[0] && one!=actions_[1], @"Sequence: re-init using the same parameters is not supported"); + NSAssert( two!=actions_[1] && two!=actions_[0], @"Sequence: re-init using the same parameters is not supported"); + + ccTime d = [one duration] + [two duration]; + + if( (self=[super initWithDuration: d]) ) { + + // XXX: Supports re-init without leaking. Fails if one==one_ || two==two_ + [actions_[0] release]; + [actions_[1] release]; + + actions_[0] = [one retain]; + actions_[1] = [two retain]; + } + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone:zone] initOne:[[actions_[0] copy] autorelease] two:[[actions_[1] copy] autorelease] ]; + return copy; +} + +-(void) dealloc +{ + [actions_[0] release]; + [actions_[1] release]; + [super dealloc]; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + split_ = [actions_[0] duration] / duration_; + last_ = -1; +} + +-(void) stop +{ + [actions_[0] stop]; + [actions_[1] stop]; + [super stop]; +} + +-(void) update: (ccTime) t +{ + int found = 0; + ccTime new_t = 0.0f; + + if( t >= split_ ) { + found = 1; + if ( split_ == 1 ) + new_t = 1; + else + new_t = (t-split_) / (1 - split_ ); + } else { + found = 0; + if( split_ != 0 ) + new_t = t / split_; + else + new_t = 1; + } + + if (last_ == -1 && found==1) { + [actions_[0] startWithTarget:target_]; + [actions_[0] update:1.0f]; + [actions_[0] stop]; + } + + if (last_ != found ) { + if( last_ != -1 ) { + [actions_[last_] update: 1.0f]; + [actions_[last_] stop]; + } + [actions_[found] startWithTarget:target_]; + } + [actions_[found] update: new_t]; + last_ = found; +} + +- (CCActionInterval *) reverse +{ + return [[self class] actionOne: [actions_[1] reverse] two: [actions_[0] reverse ] ]; +} +@end + +// +// Repeat +// +#pragma mark - +#pragma mark CCRepeat +@implementation CCRepeat +@synthesize innerAction=innerAction_; + ++(id) actionWithAction:(CCFiniteTimeAction*)action times:(NSUInteger)times +{ + return [[[self alloc] initWithAction:action times:times] autorelease]; +} + +-(id) initWithAction:(CCFiniteTimeAction*)action times:(NSUInteger)times +{ + ccTime d = [action duration] * times; + + if( (self=[super initWithDuration: d ]) ) { + times_ = times; + self.innerAction = action; + + total_ = 0; + } + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone:zone] initWithAction:[[innerAction_ copy] autorelease] times:times_]; + return copy; +} + +-(void) dealloc +{ + [innerAction_ release]; + [super dealloc]; +} + +-(void) startWithTarget:(id)aTarget +{ + total_ = 0; + [super startWithTarget:aTarget]; + [innerAction_ startWithTarget:aTarget]; +} + +-(void) stop +{ + [innerAction_ stop]; + [super stop]; +} + + +// issue #80. Instead of hooking step:, hook update: since it can be called by any +// container action like Repeat, Sequence, AccelDeccel, etc.. +-(void) update:(ccTime) dt +{ + ccTime t = dt * times_; + if( t > total_+1 ) { + [innerAction_ update:1.0f]; + total_++; + [innerAction_ stop]; + [innerAction_ startWithTarget:target_]; + + // repeat is over ? + if( total_== times_ ) + // so, set it in the original position + [innerAction_ update:0]; + else { + // no ? start next repeat with the right update + // to prevent jerk (issue #390) + [innerAction_ update: t-total_]; + } + + } else { + + float r = fmodf(t, 1.0f); + + // fix last repeat position + // else it could be 0. + if( dt== 1.0f) { + r = 1.0f; + total_++; // this is the added line + } + [innerAction_ update: MIN(r,1)]; + } +} + +-(BOOL) isDone +{ + return ( total_ == times_ ); +} + +- (CCActionInterval *) reverse +{ + return [[self class] actionWithAction:[innerAction_ reverse] times:times_]; +} +@end + +// +// Spawn +// +#pragma mark - +#pragma mark Spawn + +@implementation CCSpawn ++(id) actions: (CCFiniteTimeAction*) action1, ... +{ + va_list params; + va_start(params,action1); + + CCFiniteTimeAction *now; + CCFiniteTimeAction *prev = action1; + + while( action1 ) { + now = va_arg(params,CCFiniteTimeAction*); + if ( now ) + prev = [self actionOne: prev two: now]; + else + break; + } + va_end(params); + return prev; +} + ++(id) actionsWithArray: (NSArray*) actions +{ + CCFiniteTimeAction *prev = [actions objectAtIndex:0]; + + for (int i = 1; i < [actions count]; i++) + prev = [self actionOne:prev two:[actions objectAtIndex:i]]; + + return prev; +} + ++(id) actionOne: (CCFiniteTimeAction*) one two: (CCFiniteTimeAction*) two +{ + return [[[self alloc] initOne:one two:two ] autorelease]; +} + +-(id) initOne: (CCFiniteTimeAction*) one two: (CCFiniteTimeAction*) two +{ + NSAssert( one!=nil && two!=nil, @"Spawn: arguments must be non-nil"); + NSAssert( one!=one_ && one!=two_, @"Spawn: reinit using same parameters is not supported"); + NSAssert( two!=two_ && two!=one_, @"Spawn: reinit using same parameters is not supported"); + + ccTime d1 = [one duration]; + ccTime d2 = [two duration]; + + [super initWithDuration: MAX(d1,d2)]; + + // XXX: Supports re-init without leaking. Fails if one==one_ || two==two_ + [one_ release]; + [two_ release]; + + one_ = one; + two_ = two; + + if( d1 > d2 ) + two_ = [CCSequence actionOne:two two:[CCDelayTime actionWithDuration: (d1-d2)] ]; + else if( d1 < d2) + one_ = [CCSequence actionOne:one two: [CCDelayTime actionWithDuration: (d2-d1)] ]; + + [one_ retain]; + [two_ retain]; + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] initOne: [[one_ copy] autorelease] two: [[two_ copy] autorelease] ]; + return copy; +} + +-(void) dealloc +{ + [one_ release]; + [two_ release]; + [super dealloc]; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + [one_ startWithTarget:target_]; + [two_ startWithTarget:target_]; +} + +-(void) stop +{ + [one_ stop]; + [two_ stop]; + [super stop]; +} + +-(void) update: (ccTime) t +{ + [one_ update:t]; + [two_ update:t]; +} + +- (CCActionInterval *) reverse +{ + return [[self class] actionOne: [one_ reverse] two: [two_ reverse ] ]; +} +@end + +// +// RotateTo +// +#pragma mark - +#pragma mark RotateTo + +@implementation CCRotateTo ++(id) actionWithDuration: (ccTime) t angle:(float) a +{ + return [[[self alloc] initWithDuration:t angle:a ] autorelease]; +} + +-(id) initWithDuration: (ccTime) t angle:(float) a +{ + if( (self=[super initWithDuration: t]) ) + dstAngle_ = a; + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration:[self duration] angle:dstAngle_]; + return copy; +} + +-(void) startWithTarget:(CCNode *)aTarget +{ + [super startWithTarget:aTarget]; + + startAngle_ = [target_ rotation]; + if (startAngle_ > 0) + startAngle_ = fmodf(startAngle_, 360.0f); + else + startAngle_ = fmodf(startAngle_, -360.0f); + + diffAngle_ =dstAngle_ - startAngle_; + if (diffAngle_ > 180) + diffAngle_ -= 360; + if (diffAngle_ < -180) + diffAngle_ += 360; +} +-(void) update: (ccTime) t +{ + [target_ setRotation: startAngle_ + diffAngle_ * t]; +} +@end + + +// +// RotateBy +// +#pragma mark - +#pragma mark RotateBy + +@implementation CCRotateBy ++(id) actionWithDuration: (ccTime) t angle:(float) a +{ + return [[[self alloc] initWithDuration:t angle:a ] autorelease]; +} + +-(id) initWithDuration: (ccTime) t angle:(float) a +{ + if( (self=[super initWithDuration: t]) ) + angle_ = a; + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration: [self duration] angle: angle_]; + return copy; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + startAngle_ = [target_ rotation]; +} + +-(void) update: (ccTime) t +{ + // XXX: shall I add % 360 + [target_ setRotation: (startAngle_ +angle_ * t )]; +} + +-(CCActionInterval*) reverse +{ + return [[self class] actionWithDuration:duration_ angle:-angle_]; +} + +@end + +// +// MoveTo +// +#pragma mark - +#pragma mark MoveTo + +@implementation CCMoveTo ++(id) actionWithDuration: (ccTime) t position: (CGPoint) p +{ + return [[[self alloc] initWithDuration:t position:p ] autorelease]; +} + +-(id) initWithDuration: (ccTime) t position: (CGPoint) p +{ + if( (self=[super initWithDuration: t]) ) + endPosition_ = p; + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration: [self duration] position: endPosition_]; + return copy; +} + +-(void) startWithTarget:(CCNode *)aTarget +{ + [super startWithTarget:aTarget]; + startPosition_ = [(CCNode*)target_ position]; + delta_ = ccpSub( endPosition_, startPosition_ ); +} + +-(void) update: (ccTime) t +{ + [target_ setPosition: ccp( (startPosition_.x + delta_.x * t ), (startPosition_.y + delta_.y * t ) )]; +} +@end + +// +// MoveBy +// +#pragma mark - +#pragma mark MoveBy + +@implementation CCMoveBy ++(id) actionWithDuration: (ccTime) t position: (CGPoint) p +{ + return [[[self alloc] initWithDuration:t position:p ] autorelease]; +} + +-(id) initWithDuration: (ccTime) t position: (CGPoint) p +{ + if( (self=[super initWithDuration: t]) ) + delta_ = p; + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration: [self duration] position: delta_]; + return copy; +} + +-(void) startWithTarget:(CCNode *)aTarget +{ + CGPoint dTmp = delta_; + [super startWithTarget:aTarget]; + delta_ = dTmp; +} + +-(CCActionInterval*) reverse +{ + return [[self class] actionWithDuration:duration_ position:ccp( -delta_.x, -delta_.y)]; +} +@end + + +// +// SkewTo +// +#pragma mark - +#pragma mark SkewTo + +@implementation CCSkewTo ++(id) actionWithDuration:(ccTime)t skewX:(float)sx skewY:(float)sy +{ + return [[[self alloc] initWithDuration: t skewX:sx skewY:sy] autorelease]; +} + +-(id) initWithDuration:(ccTime)t skewX:(float)sx skewY:(float)sy +{ + if( (self=[super initWithDuration:t]) ) { + endSkewX_ = sx; + endSkewY_ = sy; + } + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration:[self duration] skewX:endSkewX_ skewY:endSkewY_]; + return copy; +} + +-(void) startWithTarget:(CCNode *)aTarget +{ + [super startWithTarget:aTarget]; + + startSkewX_ = [target_ skewX]; + + if (startSkewX_ > 0) + startSkewX_ = fmodf(startSkewX_, 180.0f); + else + startSkewX_ = fmodf(startSkewX_, -180.0f); + + deltaX_ = endSkewX_ - startSkewX_; + + if ( deltaX_ > 180 ) { + deltaX_ -= 360; + } + if ( deltaX_ < -180 ) { + deltaX_ += 360; + } + + startSkewY_ = [target_ skewY]; + + if (startSkewY_ > 0) + startSkewY_ = fmodf(startSkewY_, 360.0f); + else + startSkewY_ = fmodf(startSkewY_, -360.0f); + + deltaY_ = endSkewY_ - startSkewY_; + + if ( deltaY_ > 180 ) { + deltaY_ -= 360; + } + if ( deltaY_ < -180 ) { + deltaY_ += 360; + } +} + +-(void) update: (ccTime) t +{ + [target_ setSkewX: (startSkewX_ + deltaX_ * t ) ]; + [target_ setSkewY: (startSkewY_ + deltaY_ * t ) ]; +} + +@end + +// +// CCSkewBy +// +@implementation CCSkewBy + +-(id) initWithDuration:(ccTime)t skewX:(float)deltaSkewX skewY:(float)deltaSkewY +{ + if( (self=[super initWithDuration:t skewX:deltaSkewX skewY:deltaSkewY]) ) { + skewX_ = deltaSkewX; + skewY_ = deltaSkewY; + } + return self; +} + +-(void) startWithTarget:(CCNode *)aTarget +{ + [super startWithTarget:aTarget]; + deltaX_ = skewX_; + deltaY_ = skewY_; + endSkewX_ = startSkewX_ + deltaX_; + endSkewY_ = startSkewY_ + deltaY_; +} + +-(CCActionInterval*) reverse +{ + return [[self class] actionWithDuration:duration_ skewX:-skewX_ skewY:-skewY_]; +} +@end + + +// +// JumpBy +// +#pragma mark - +#pragma mark JumpBy + +@implementation CCJumpBy ++(id) actionWithDuration: (ccTime) t position: (CGPoint) pos height: (ccTime) h jumps:(NSUInteger)j +{ + return [[[self alloc] initWithDuration: t position: pos height: h jumps:j] autorelease]; +} + +-(id) initWithDuration: (ccTime) t position: (CGPoint) pos height: (ccTime) h jumps:(NSUInteger)j +{ + if( (self=[super initWithDuration:t]) ) { + delta_ = pos; + height_ = h; + jumps_ = j; + } + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration:[self duration] position:delta_ height:height_ jumps:jumps_]; + return copy; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + startPosition_ = [(CCNode*)target_ position]; +} + +-(void) update: (ccTime) t +{ + // Sin jump. Less realistic +// ccTime y = height * fabsf( sinf(t * (CGFloat)M_PI * jumps ) ); +// y += delta.y * t; +// ccTime x = delta.x * t; +// [target setPosition: ccp( startPosition.x + x, startPosition.y + y )]; + + // parabolic jump (since v0.8.2) + ccTime frac = fmodf( t * jumps_, 1.0f ); + ccTime y = height_ * 4 * frac * (1 - frac); + y += delta_.y * t; + ccTime x = delta_.x * t; + [target_ setPosition: ccp( startPosition_.x + x, startPosition_.y + y )]; + +} + +-(CCActionInterval*) reverse +{ + return [[self class] actionWithDuration:duration_ position: ccp(-delta_.x,-delta_.y) height:height_ jumps:jumps_]; +} +@end + +// +// JumpTo +// +#pragma mark - +#pragma mark JumpTo + +@implementation CCJumpTo +-(void) startWithTarget:(CCNode *)aTarget +{ + [super startWithTarget:aTarget]; + delta_ = ccp( delta_.x - startPosition_.x, delta_.y - startPosition_.y ); +} +@end + + +#pragma mark - +#pragma mark BezierBy + +// Bezier cubic formula: +// ((1 - t) + t)3 = 1 +// Expands to… +// (1 - t)3 + 3t(1-t)2 + 3t2(1 - t) + t3 = 1 +static inline float bezierat( float a, float b, float c, float d, ccTime t ) +{ + return (powf(1-t,3) * a + + 3*t*(powf(1-t,2))*b + + 3*powf(t,2)*(1-t)*c + + powf(t,3)*d ); +} + +// +// BezierBy +// +@implementation CCBezierBy ++(id) actionWithDuration: (ccTime) t bezier:(ccBezierConfig) c +{ + return [[[self alloc] initWithDuration:t bezier:c ] autorelease]; +} + +-(id) initWithDuration: (ccTime) t bezier:(ccBezierConfig) c +{ + if( (self=[super initWithDuration: t]) ) { + config_ = c; + } + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration:[self duration] bezier:config_]; + return copy; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + startPosition_ = [(CCNode*)target_ position]; +} + +-(void) update: (ccTime) t +{ + float xa = 0; + float xb = config_.controlPoint_1.x; + float xc = config_.controlPoint_2.x; + float xd = config_.endPosition.x; + + float ya = 0; + float yb = config_.controlPoint_1.y; + float yc = config_.controlPoint_2.y; + float yd = config_.endPosition.y; + + float x = bezierat(xa, xb, xc, xd, t); + float y = bezierat(ya, yb, yc, yd, t); + [target_ setPosition: ccpAdd( startPosition_, ccp(x,y))]; +} + +- (CCActionInterval*) reverse +{ + ccBezierConfig r; + + r.endPosition = ccpNeg(config_.endPosition); + r.controlPoint_1 = ccpAdd(config_.controlPoint_2, ccpNeg(config_.endPosition)); + r.controlPoint_2 = ccpAdd(config_.controlPoint_1, ccpNeg(config_.endPosition)); + + CCBezierBy *action = [[self class] actionWithDuration:[self duration] bezier:r]; + return action; +} +@end + +// +// BezierTo +// +#pragma mark - +#pragma mark BezierTo +@implementation CCBezierTo +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + config_.controlPoint_1 = ccpSub(config_.controlPoint_1, startPosition_); + config_.controlPoint_2 = ccpSub(config_.controlPoint_2, startPosition_); + config_.endPosition = ccpSub(config_.endPosition, startPosition_); +} +@end + + +// +// ScaleTo +// +#pragma mark - +#pragma mark ScaleTo +@implementation CCScaleTo ++(id) actionWithDuration: (ccTime) t scale:(float) s +{ + return [[[self alloc] initWithDuration: t scale:s] autorelease]; +} + +-(id) initWithDuration: (ccTime) t scale:(float) s +{ + if( (self=[super initWithDuration: t]) ) { + endScaleX_ = s; + endScaleY_ = s; + } + return self; +} + ++(id) actionWithDuration: (ccTime) t scaleX:(float)sx scaleY:(float)sy +{ + return [[[self alloc] initWithDuration: t scaleX:sx scaleY:sy] autorelease]; +} + +-(id) initWithDuration: (ccTime) t scaleX:(float)sx scaleY:(float)sy +{ + if( (self=[super initWithDuration: t]) ) { + endScaleX_ = sx; + endScaleY_ = sy; + } + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration:[self duration] scaleX:endScaleX_ scaleY:endScaleY_]; + return copy; +} + +-(void) startWithTarget:(CCNode *)aTarget +{ + [super startWithTarget:aTarget]; + startScaleX_ = [target_ scaleX]; + startScaleY_ = [target_ scaleY]; + deltaX_ = endScaleX_ - startScaleX_; + deltaY_ = endScaleY_ - startScaleY_; +} + +-(void) update: (ccTime) t +{ + [target_ setScaleX: (startScaleX_ + deltaX_ * t ) ]; + [target_ setScaleY: (startScaleY_ + deltaY_ * t ) ]; +} +@end + +// +// ScaleBy +// +#pragma mark - +#pragma mark ScaleBy +@implementation CCScaleBy +-(void) startWithTarget:(CCNode *)aTarget +{ + [super startWithTarget:aTarget]; + deltaX_ = startScaleX_ * endScaleX_ - startScaleX_; + deltaY_ = startScaleY_ * endScaleY_ - startScaleY_; +} + +-(CCActionInterval*) reverse +{ + return [[self class] actionWithDuration:duration_ scaleX:1/endScaleX_ scaleY:1/endScaleY_]; +} +@end + +// +// Blink +// +#pragma mark - +#pragma mark Blink +@implementation CCBlink ++(id) actionWithDuration: (ccTime) t blinks: (NSUInteger) b +{ + return [[[ self alloc] initWithDuration: t blinks: b] autorelease]; +} + +-(id) initWithDuration: (ccTime) t blinks: (NSUInteger) b +{ + if( (self=[super initWithDuration: t] ) ) + times_ = b; + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration: [self duration] blinks: times_]; + return copy; +} + +-(void) update: (ccTime) t +{ + if( ! [self isDone] ) { + ccTime slice = 1.0f / times_; + ccTime m = fmodf(t, slice); + [target_ setVisible: (m > slice/2) ? YES : NO]; + } +} + +-(CCActionInterval*) reverse +{ + // return 'self' + return [[self class] actionWithDuration:duration_ blinks: times_]; +} +@end + +// +// FadeIn +// +#pragma mark - +#pragma mark FadeIn +@implementation CCFadeIn +-(void) update: (ccTime) t +{ + [(id) target_ setOpacity: 255 *t]; +} + +-(CCActionInterval*) reverse +{ + return [CCFadeOut actionWithDuration:duration_]; +} +@end + +// +// FadeOut +// +#pragma mark - +#pragma mark FadeOut +@implementation CCFadeOut +-(void) update: (ccTime) t +{ + [(id) target_ setOpacity: 255 *(1-t)]; +} + +-(CCActionInterval*) reverse +{ + return [CCFadeIn actionWithDuration:duration_]; +} +@end + +// +// FadeTo +// +#pragma mark - +#pragma mark FadeTo +@implementation CCFadeTo ++(id) actionWithDuration: (ccTime) t opacity: (GLubyte) o +{ + return [[[ self alloc] initWithDuration: t opacity: o] autorelease]; +} + +-(id) initWithDuration: (ccTime) t opacity: (GLubyte) o +{ + if( (self=[super initWithDuration: t] ) ) + toOpacity_ = o; + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration:[self duration] opacity:toOpacity_]; + return copy; +} + +-(void) startWithTarget:(CCNode *)aTarget +{ + [super startWithTarget:aTarget]; + fromOpacity_ = [(id)target_ opacity]; +} + +-(void) update: (ccTime) t +{ + [(id)target_ setOpacity:fromOpacity_ + ( toOpacity_ - fromOpacity_ ) * t]; +} +@end + +// +// TintTo +// +#pragma mark - +#pragma mark TintTo +@implementation CCTintTo ++(id) actionWithDuration:(ccTime)t red:(GLubyte)r green:(GLubyte)g blue:(GLubyte)b +{ + return [[(CCTintTo*)[ self alloc] initWithDuration:t red:r green:g blue:b] autorelease]; +} + +-(id) initWithDuration: (ccTime) t red:(GLubyte)r green:(GLubyte)g blue:(GLubyte)b +{ + if( (self=[super initWithDuration:t] ) ) + to_ = ccc3(r,g,b); + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [(CCTintTo*)[[self class] allocWithZone: zone] initWithDuration:[self duration] red:to_.r green:to_.g blue:to_.b]; + return copy; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + + id tn = (id) target_; + from_ = [tn color]; +} + +-(void) update: (ccTime) t +{ + id tn = (id) target_; + [tn setColor:ccc3(from_.r + (to_.r - from_.r) * t, from_.g + (to_.g - from_.g) * t, from_.b + (to_.b - from_.b) * t)]; +} +@end + +// +// TintBy +// +#pragma mark - +#pragma mark TintBy +@implementation CCTintBy ++(id) actionWithDuration:(ccTime)t red:(GLshort)r green:(GLshort)g blue:(GLshort)b +{ + return [[(CCTintBy*)[ self alloc] initWithDuration:t red:r green:g blue:b] autorelease]; +} + +-(id) initWithDuration:(ccTime)t red:(GLshort)r green:(GLshort)g blue:(GLshort)b +{ + if( (self=[super initWithDuration: t] ) ) { + deltaR_ = r; + deltaG_ = g; + deltaB_ = b; + } + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + return[(CCTintBy*)[[self class] allocWithZone: zone] initWithDuration: [self duration] red:deltaR_ green:deltaG_ blue:deltaB_]; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + + id tn = (id) target_; + ccColor3B color = [tn color]; + fromR_ = color.r; + fromG_ = color.g; + fromB_ = color.b; +} + +-(void) update: (ccTime) t +{ + id tn = (id) target_; + [tn setColor:ccc3( fromR_ + deltaR_ * t, fromG_ + deltaG_ * t, fromB_ + deltaB_ * t)]; +} + +- (CCActionInterval*) reverse +{ + return [CCTintBy actionWithDuration:duration_ red:-deltaR_ green:-deltaG_ blue:-deltaB_]; +} +@end + +// +// DelayTime +// +#pragma mark - +#pragma mark DelayTime +@implementation CCDelayTime +-(void) update: (ccTime) t +{ + return; +} + +-(id)reverse +{ + return [[self class] actionWithDuration:duration_]; +} +@end + +// +// ReverseTime +// +#pragma mark - +#pragma mark ReverseTime +@implementation CCReverseTime ++(id) actionWithAction: (CCFiniteTimeAction*) action +{ + // casting to prevent warnings + CCReverseTime *a = [super alloc]; + return [[a initWithAction:action] autorelease]; +} + +-(id) initWithAction: (CCFiniteTimeAction*) action +{ + NSAssert(action != nil, @"CCReverseTime: action should not be nil"); + NSAssert(action != other_, @"CCReverseTime: re-init doesn't support using the same arguments"); + + if( (self=[super initWithDuration: [action duration]]) ) { + // Don't leak if action is reused + [other_ release]; + other_ = [action retain]; + } + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + return [[[self class] allocWithZone: zone] initWithAction:[[other_ copy] autorelease] ]; +} + +-(void) dealloc +{ + [other_ release]; + [super dealloc]; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + [other_ startWithTarget:target_]; +} + +-(void) stop +{ + [other_ stop]; + [super stop]; +} + +-(void) update:(ccTime)t +{ + [other_ update:1-t]; +} + +-(CCActionInterval*) reverse +{ + return [[other_ copy] autorelease]; +} +@end + +// +// Animate +// + +#pragma mark - +#pragma mark Animate +@implementation CCAnimate + +@synthesize animation = animation_; + ++(id) actionWithAnimation: (CCAnimation*)anim +{ + return [[[self alloc] initWithAnimation:anim restoreOriginalFrame:YES] autorelease]; +} + ++(id) actionWithAnimation: (CCAnimation*)anim restoreOriginalFrame:(BOOL)b +{ + return [[[self alloc] initWithAnimation:anim restoreOriginalFrame:b] autorelease]; +} + ++(id) actionWithDuration:(ccTime)duration animation: (CCAnimation*)anim restoreOriginalFrame:(BOOL)b +{ + return [[[self alloc] initWithDuration:duration animation:anim restoreOriginalFrame:b] autorelease]; +} + +-(id) initWithAnimation: (CCAnimation*)anim +{ + NSAssert( anim!=nil, @"Animate: argument Animation must be non-nil"); + return [self initWithAnimation:anim restoreOriginalFrame:YES]; +} + +-(id) initWithAnimation: (CCAnimation*)anim restoreOriginalFrame:(BOOL) b +{ + NSAssert( anim!=nil, @"Animate: argument Animation must be non-nil"); + + if( (self=[super initWithDuration: [[anim frames] count] * [anim delay]]) ) { + + restoreOriginalFrame_ = b; + self.animation = anim; + origFrame_ = nil; + } + return self; +} + +-(id) initWithDuration:(ccTime)aDuration animation: (CCAnimation*)anim restoreOriginalFrame:(BOOL) b +{ + NSAssert( anim!=nil, @"Animate: argument Animation must be non-nil"); + + if( (self=[super initWithDuration:aDuration] ) ) { + + restoreOriginalFrame_ = b; + self.animation = anim; + origFrame_ = nil; + } + return self; +} + + +-(id) copyWithZone: (NSZone*) zone +{ + return [[[self class] allocWithZone: zone] initWithDuration:duration_ animation:animation_ restoreOriginalFrame:restoreOriginalFrame_]; +} + +-(void) dealloc +{ + [animation_ release]; + [origFrame_ release]; + [super dealloc]; +} + +-(void) startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + CCSprite *sprite = target_; + + [origFrame_ release]; + + if( restoreOriginalFrame_ ) + origFrame_ = [[sprite displayedFrame] retain]; +} + +-(void) stop +{ + if( restoreOriginalFrame_ ) { + CCSprite *sprite = target_; + [sprite setDisplayFrame:origFrame_]; + } + + [super stop]; +} + +-(void) update: (ccTime) t +{ + NSArray *frames = [animation_ frames]; + NSUInteger numberOfFrames = [frames count]; + + NSUInteger idx = t * numberOfFrames; + + if( idx >= numberOfFrames ) + idx = numberOfFrames -1; + + CCSprite *sprite = target_; + if (! [sprite isFrameDisplayed: [frames objectAtIndex: idx]] ) + [sprite setDisplayFrame: [frames objectAtIndex:idx]]; +} + +- (CCActionInterval *) reverse +{ + NSArray *oldArray = [animation_ frames]; + NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:[oldArray count]]; + NSEnumerator *enumerator = [oldArray reverseObjectEnumerator]; + for (id element in enumerator) + [newArray addObject:[[element copy] autorelease]]; + + CCAnimation *newAnim = [CCAnimation animationWithFrames:newArray delay:animation_.delay]; + return [[self class] actionWithDuration:duration_ animation:newAnim restoreOriginalFrame:restoreOriginalFrame_]; +} + +@end diff --git a/libs/cocos2d/CCActionManager.h b/libs/cocos2d/CCActionManager.h new file mode 100644 index 0000000..0eeda91 --- /dev/null +++ b/libs/cocos2d/CCActionManager.h @@ -0,0 +1,111 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2009 Valentin Milea + * + * 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. + * + */ + + +#import "CCAction.h" +#import "Support/ccCArray.h" +#import "Support/uthash.h" + +typedef struct _hashElement +{ + struct ccArray *actions; + id target; + NSUInteger actionIndex; + CCAction *currentAction; + BOOL currentActionSalvaged; + BOOL paused; + UT_hash_handle hh; +} tHashElement; + + +/** CCActionManager is a singleton that manages all the actions. + Normally you won't need to use this singleton directly. 99% of the cases you will use the CCNode interface, + which uses this singleton. + But there are some cases where you might need to use this singleton. + Examples: + - When you want to run an action where the target is different from a CCNode. + - When you want to pause / resume the actions + + @since v0.8 + */ +@interface CCActionManager : NSObject +{ + tHashElement *targets; + tHashElement *currentTarget; + BOOL currentTargetSalvaged; +} + +/** returns a shared instance of the CCActionManager */ ++ (CCActionManager *)sharedManager; + +/** purges the shared action manager. It releases the retained instance. + @since v0.99.0 + */ ++(void)purgeSharedManager; + +// actions + +/** Adds an action with a target. + If the target is already present, then the action will be added to the existing target. + If the target is not present, a new instance of this target will be created either paused or paused, and the action will be added to the newly created target. + When the target is paused, the queued actions won't be 'ticked'. + */ +-(void) addAction: (CCAction*) action target:(id)target paused:(BOOL)paused; +/** Removes all actions from all the targers. + */ +-(void) removeAllActions; + +/** Removes all actions from a certain target. + All the actions that belongs to the target will be removed. + */ +-(void) removeAllActionsFromTarget:(id)target; +/** Removes an action given an action reference. + */ +-(void) removeAction: (CCAction*) action; +/** Removes an action given its tag and the target */ +-(void) removeActionByTag:(NSInteger)tag target:(id)target; +/** Gets an action given its tag an a target + @return the Action the with the given tag + */ +-(CCAction*) getActionByTag:(NSInteger) tag target:(id)target; +/** Returns the numbers of actions that are running in a certain target + * Composable actions are counted as 1 action. Example: + * If you are running 1 Sequence of 7 actions, it will return 1. + * If you are running 7 Sequences of 2 actions, it will return 7. + */ +-(NSUInteger) numberOfRunningActionsInTarget:(id)target; + +/** Pauses the target: all running actions and newly added actions will be paused. + */ +-(void) pauseTarget:(id)target; +/** Resumes the target. All queued actions will be resumed. + */ +-(void) resumeTarget:(id)target; + +@end + diff --git a/libs/cocos2d/CCActionManager.m b/libs/cocos2d/CCActionManager.m new file mode 100644 index 0000000..9dc5a9e --- /dev/null +++ b/libs/cocos2d/CCActionManager.m @@ -0,0 +1,346 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2009 Valentin Milea + * + * 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. + * + */ + + +#import "CCActionManager.h" +#import "CCScheduler.h" +#import "ccMacros.h" + + +// +// singleton stuff +// +static CCActionManager *sharedManager_ = nil; + +@interface CCActionManager (Private) +-(void) removeActionAtIndex:(NSUInteger)index hashElement:(tHashElement*)element; +-(void) deleteHashElement:(tHashElement*)element; +-(void) actionAllocWithHashElement:(tHashElement*)element; +@end + + +@implementation CCActionManager + +#pragma mark ActionManager - init ++ (CCActionManager *)sharedManager +{ + if (!sharedManager_) + sharedManager_ = [[self alloc] init]; + + return sharedManager_; +} + ++(id)alloc +{ + NSAssert(sharedManager_ == nil, @"Attempted to allocate a second instance of a singleton."); + return [super alloc]; +} + ++(void)purgeSharedManager +{ + [[CCScheduler sharedScheduler] unscheduleUpdateForTarget:self]; + [sharedManager_ release]; + sharedManager_ = nil; +} + +-(id) init +{ + if ((self=[super init]) ) { + [[CCScheduler sharedScheduler] scheduleUpdateForTarget:self priority:0 paused:NO]; + targets = NULL; + } + + return self; +} + +- (void) dealloc +{ + CCLOGINFO( @"cocos2d: deallocing %@", self); + + [self removeAllActions]; + + sharedManager_ = nil; + + [super dealloc]; +} + +#pragma mark ActionManager - Private + +-(void) deleteHashElement:(tHashElement*)element +{ + ccArrayFree(element->actions); + HASH_DEL(targets, element); +// CCLOG(@"cocos2d: ---- buckets: %d/%d - %@", targets->entries, targets->size, element->target); + [element->target release]; + free(element); +} + +-(void) actionAllocWithHashElement:(tHashElement*)element +{ + // 4 actions per Node by default + if( element->actions == nil ) + element->actions = ccArrayNew(4); + else if( element->actions->num == element->actions->max ) + ccArrayDoubleCapacity(element->actions); +} + +-(void) removeActionAtIndex:(NSUInteger)index hashElement:(tHashElement*)element +{ + id action = element->actions->arr[index]; + + if( action == element->currentAction && !element->currentActionSalvaged ) { + [element->currentAction retain]; + element->currentActionSalvaged = YES; + } + + ccArrayRemoveObjectAtIndex(element->actions, index); + + // update actionIndex in case we are in tick:, looping over the actions + if( element->actionIndex >= index ) + element->actionIndex--; + + if( element->actions->num == 0 ) { + if( currentTarget == element ) + currentTargetSalvaged = YES; + else + [self deleteHashElement: element]; + } +} + +#pragma mark ActionManager - Pause / Resume + +-(void) pauseTarget:(id)target +{ + tHashElement *element = NULL; + HASH_FIND_INT(targets, &target, element); + if( element ) + element->paused = YES; +// else +// CCLOG(@"cocos2d: pauseAllActions: Target not found"); +} + +-(void) resumeTarget:(id)target +{ + tHashElement *element = NULL; + HASH_FIND_INT(targets, &target, element); + if( element ) + element->paused = NO; +// else +// CCLOG(@"cocos2d: resumeAllActions: Target not found"); +} + +#pragma mark ActionManager - run + +-(void) addAction:(CCAction*)action target:(id)target paused:(BOOL)paused +{ + NSAssert( action != nil, @"Argument action must be non-nil"); + NSAssert( target != nil, @"Argument target must be non-nil"); + + tHashElement *element = NULL; + HASH_FIND_INT(targets, &target, element); + if( ! element ) { + element = calloc( sizeof( *element ), 1 ); + element->paused = paused; + element->target = [target retain]; + HASH_ADD_INT(targets, target, element); +// CCLOG(@"cocos2d: ---- buckets: %d/%d - %@", targets->entries, targets->size, element->target); + + } + + [self actionAllocWithHashElement:element]; + + NSAssert( !ccArrayContainsObject(element->actions, action), @"runAction: Action already running"); + ccArrayAppendObject(element->actions, action); + + [action startWithTarget:target]; +} + +#pragma mark ActionManager - remove + +-(void) removeAllActions +{ + for(tHashElement *element=targets; element != NULL; ) { + id target = element->target; + element = element->hh.next; + [self removeAllActionsFromTarget:target]; + } +} +-(void) removeAllActionsFromTarget:(id)target +{ + // explicit nil handling + if( target == nil ) + return; + + tHashElement *element = NULL; + HASH_FIND_INT(targets, &target, element); + if( element ) { + if( ccArrayContainsObject(element->actions, element->currentAction) && !element->currentActionSalvaged ) { + [element->currentAction retain]; + element->currentActionSalvaged = YES; + } + ccArrayRemoveAllObjects(element->actions); + if( currentTarget == element ) + currentTargetSalvaged = YES; + else + [self deleteHashElement:element]; + } +// else { +// CCLOG(@"cocos2d: removeAllActionsFromTarget: Target not found"); +// } +} + +-(void) removeAction: (CCAction*) action +{ + // explicit nil handling + if (action == nil) + return; + + tHashElement *element = NULL; + id target = [action originalTarget]; + HASH_FIND_INT(targets, &target, element ); + if( element ) { + NSUInteger i = ccArrayGetIndexOfObject(element->actions, action); + if( i != NSNotFound ) + [self removeActionAtIndex:i hashElement:element]; + } +// else { +// CCLOG(@"cocos2d: removeAction: Target not found"); +// } +} + +-(void) removeActionByTag:(NSInteger)aTag target:(id)target +{ + NSAssert( aTag != kCCActionTagInvalid, @"Invalid tag"); + NSAssert( target != nil, @"Target should be ! nil"); + + tHashElement *element = NULL; + HASH_FIND_INT(targets, &target, element); + + if( element ) { + NSUInteger limit = element->actions->num; + for( NSUInteger i = 0; i < limit; i++) { + CCAction *a = element->actions->arr[i]; + + if( a.tag == aTag && [a originalTarget]==target) + return [self removeActionAtIndex:i hashElement:element]; + } +// CCLOG(@"cocos2d: removeActionByTag: Action not found!"); + } +// else { +// CCLOG(@"cocos2d: removeActionByTag: Target not found!"); +// } +} + +#pragma mark ActionManager - get + +-(CCAction*) getActionByTag:(NSInteger)aTag target:(id)target +{ + NSAssert( aTag != kCCActionTagInvalid, @"Invalid tag"); + + tHashElement *element = NULL; + HASH_FIND_INT(targets, &target, element); + + if( element ) { + if( element->actions != nil ) { + NSUInteger limit = element->actions->num; + for( NSUInteger i = 0; i < limit; i++) { + CCAction *a = element->actions->arr[i]; + + if( a.tag == aTag ) + return a; + } + } +// CCLOG(@"cocos2d: getActionByTag: Action not found"); + } +// else { +// CCLOG(@"cocos2d: getActionByTag: Target not found"); +// } + return nil; +} + +-(NSUInteger) numberOfRunningActionsInTarget:(id) target +{ + tHashElement *element = NULL; + HASH_FIND_INT(targets, &target, element); + if( element ) + return element->actions ? element->actions->num : 0; + +// CCLOG(@"cocos2d: numberOfRunningActionsInTarget: Target not found"); + return 0; +} + +#pragma mark ActionManager - main loop + +-(void) update: (ccTime) dt +{ + for(tHashElement *elt = targets; elt != NULL; ) { + + currentTarget = elt; + currentTargetSalvaged = NO; + + if( ! currentTarget->paused ) { + + // The 'actions' ccArray may change while inside this loop. + for( currentTarget->actionIndex = 0; currentTarget->actionIndex < currentTarget->actions->num; currentTarget->actionIndex++) { + currentTarget->currentAction = currentTarget->actions->arr[currentTarget->actionIndex]; + currentTarget->currentActionSalvaged = NO; + + [currentTarget->currentAction step: dt]; + + if( currentTarget->currentActionSalvaged ) { + // The currentAction told the node to remove it. To prevent the action from + // accidentally deallocating itself before finishing its step, we retained + // it. Now that step is done, it's safe to release it. + [currentTarget->currentAction release]; + + } else if( [currentTarget->currentAction isDone] ) { + [currentTarget->currentAction stop]; + + CCAction *a = currentTarget->currentAction; + // Make currentAction nil to prevent removeAction from salvaging it. + currentTarget->currentAction = nil; + [self removeAction:a]; + } + + currentTarget->currentAction = nil; + } + } + + // elt, at this moment, is still valid + // so it is safe to ask this here (issue #490) + elt = elt->hh.next; + + // only delete currentTarget if no actions were scheduled during the cycle (issue #481) + if( currentTargetSalvaged && currentTarget->actions->num == 0 ) + [self deleteHashElement:currentTarget]; + } + + // issue #635 + currentTarget = nil; +} +@end diff --git a/libs/cocos2d/CCActionPageTurn3D.h b/libs/cocos2d/CCActionPageTurn3D.h new file mode 100644 index 0000000..39eb31d --- /dev/null +++ b/libs/cocos2d/CCActionPageTurn3D.h @@ -0,0 +1,42 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2009 Sindesso Pty Ltd http://www.sindesso.com/ + * + * 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 "CCActionGrid3D.h" + +/** + * This action simulates a page turn from the bottom right hand corner of the screen + * It's not much use by itself but is used by the PageTurnTransition. + * + * Based on an original paper by L Hong et al. + * http://www.parc.com/publication/1638/turning-pages-of-3d-electronic-books.html + * + * @since v0.8.2 + */ +@interface CCPageTurn3D : CCGrid3DAction +{ +} + +@end diff --git a/libs/cocos2d/CCActionPageTurn3D.m b/libs/cocos2d/CCActionPageTurn3D.m new file mode 100644 index 0000000..ee59500 --- /dev/null +++ b/libs/cocos2d/CCActionPageTurn3D.m @@ -0,0 +1,86 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2009 Sindesso Pty Ltd http://www.sindesso.com/ + * + * 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 "CCActionPageTurn3D.h" + +@implementation CCPageTurn3D + +/* + * Update each tick + * Time is the percentage of the way through the duration + */ +-(void)update:(ccTime)time +{ + float tt = MAX( 0, time - 0.25f ); + float deltaAy = ( tt * tt * 500); + float ay = -100 - deltaAy; + + float deltaTheta = - (float) M_PI_2 * sqrtf( time) ; + float theta = /*0.01f*/ + (float) M_PI_2 +deltaTheta; + + float sinTheta = sinf(theta); + float cosTheta = cosf(theta); + + for( int i = 0; i <=gridSize_.x; i++ ) + { + for( int j = 0; j <= gridSize_.y; j++ ) + { + // Get original vertex + ccVertex3F p = [self originalVertex:ccg(i,j)]; + + float R = sqrtf(p.x*p.x + (p.y - ay) * (p.y - ay)); + float r = R * sinTheta; + float alpha = asinf( p.x / R ); + float beta = alpha / sinTheta; + float cosBeta = cosf( beta ); + + // If beta > PI then we've wrapped around the cone + // Reduce the radius to stop these points interfering with others + if( beta <= M_PI) + p.x = ( r * sinf(beta)); + else + { + // Force X = 0 to stop wrapped + // points + p.x = 0; + } + + p.y = ( R + ay - ( r*(1 - cosBeta)*sinTheta)); + + // We scale z here to avoid the animation being + // too much bigger than the screen due to perspectve transform + p.z = (r * ( 1 - cosBeta ) * cosTheta) / 7; // "100" didn't work for + + // Stop z coord from dropping beneath underlying page in a transition + // issue #751 + if( p.z<0.5f ) + p.z = 0.5f; + + // Set new coords + [self setVertex:ccg(i,j) vertex:p]; + } + } +} +@end diff --git a/libs/cocos2d/CCActionProgressTimer.h b/libs/cocos2d/CCActionProgressTimer.h new file mode 100644 index 0000000..500631b --- /dev/null +++ b/libs/cocos2d/CCActionProgressTimer.h @@ -0,0 +1,59 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (C) 2010 Lam Pham + * + * 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 +#import "CCProgressTimer.h" +#import "CCActionInterval.h" + +/** + Progress to percentage +@since v0.99.1 +*/ +@interface CCProgressTo : CCActionInterval +{ + float to_; + float from_; +} +/** Creates and initializes with a duration and a percent */ ++(id) actionWithDuration:(ccTime)duration percent:(float)percent; +/** Initializes with a duration and a percent */ +-(id) initWithDuration:(ccTime)duration percent:(float)percent; +@end + +/** + Progress from a percentage to another percentage + @since v0.99.1 + */ +@interface CCProgressFromTo : CCActionInterval +{ + float to_; + float from_; +} +/** Creates and initializes the action with a duration, a "from" percentage and a "to" percentage */ ++(id) actionWithDuration:(ccTime)duration from:(float)fromPercentage to:(float) toPercentage; +/** Initializes the action with a duration, a "from" percentage and a "to" percentage */ +-(id) initWithDuration:(ccTime)duration from:(float)fromPercentage to:(float) toPercentage; +@end diff --git a/libs/cocos2d/CCActionProgressTimer.m b/libs/cocos2d/CCActionProgressTimer.m new file mode 100644 index 0000000..c242570 --- /dev/null +++ b/libs/cocos2d/CCActionProgressTimer.m @@ -0,0 +1,103 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (C) 2010 Lam Pham + * + * 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 "CCActionProgressTimer.h" + +#define kProgressTimerCast CCProgressTimer* + +@implementation CCProgressTo ++(id) actionWithDuration: (ccTime) t percent: (float) v +{ + return [[[ self alloc] initWithDuration: t percent: v] autorelease]; +} + +-(id) initWithDuration: (ccTime) t percent: (float) v +{ + if( (self=[super initWithDuration: t] ) ) + to_ = v; + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration:duration_ percent:to_]; + return copy; +} + +-(void) startWithTarget:(id) aTarget; +{ + [super startWithTarget:aTarget]; + from_ = [(kProgressTimerCast)target_ percentage]; + + // XXX: Is this correct ? + // Adding it to support CCRepeat + if( from_ == 100) + from_ = 0; +} + +-(void) update: (ccTime) t +{ + [(kProgressTimerCast)target_ setPercentage: from_ + ( to_ - from_ ) * t]; +} +@end + +@implementation CCProgressFromTo ++(id) actionWithDuration: (ccTime) t from:(float)fromPercentage to:(float) toPercentage +{ + return [[[self alloc] initWithDuration: t from: fromPercentage to: toPercentage] autorelease]; +} + +-(id) initWithDuration: (ccTime) t from:(float)fromPercentage to:(float) toPercentage +{ + if( (self=[super initWithDuration: t] ) ){ + to_ = toPercentage; + from_ = fromPercentage; + } + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration:duration_ from:from_ to:to_]; + return copy; +} + +- (CCActionInterval *) reverse +{ + return [[self class] actionWithDuration:duration_ from:to_ to:from_]; +} + +-(void) startWithTarget:(id) aTarget; +{ + [super startWithTarget:aTarget]; +} + +-(void) update: (ccTime) t +{ + [(kProgressTimerCast)target_ setPercentage: from_ + ( to_ - from_ ) * t]; +} +@end diff --git a/libs/cocos2d/CCActionTiledGrid.h b/libs/cocos2d/CCActionTiledGrid.h new file mode 100644 index 0000000..d66132d --- /dev/null +++ b/libs/cocos2d/CCActionTiledGrid.h @@ -0,0 +1,211 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2009 On-Core + * + * 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 "CCActionGrid.h" + +/** CCShakyTiles3D action */ +@interface CCShakyTiles3D : CCTiledGrid3DAction +{ + int randrange; + BOOL shakeZ; +} + +/** creates the action with a range, whether or not to shake Z vertices, a grid size, and duration */ ++(id)actionWithRange:(int)range shakeZ:(BOOL)shakeZ grid:(ccGridSize)gridSize duration:(ccTime)d; +/** initializes the action with a range, whether or not to shake Z vertices, a grid size, and duration */ +-(id)initWithRange:(int)range shakeZ:(BOOL)shakeZ grid:(ccGridSize)gridSize duration:(ccTime)d; + +@end + +//////////////////////////////////////////////////////////// + +/** CCShatteredTiles3D action */ +@interface CCShatteredTiles3D : CCTiledGrid3DAction +{ + int randrange; + BOOL once; + BOOL shatterZ; +} + +/** creates the action with a range, whether of not to shatter Z vertices, a grid size and duration */ ++(id)actionWithRange:(int)range shatterZ:(BOOL)shatterZ grid:(ccGridSize)gridSize duration:(ccTime)d; +/** initializes the action with a range, whether or not to shatter Z vertices, a grid size and duration */ +-(id)initWithRange:(int)range shatterZ:(BOOL)shatterZ grid:(ccGridSize)gridSize duration:(ccTime)d; + +@end + +//////////////////////////////////////////////////////////// + +/** CCShuffleTiles action + Shuffle the tiles in random order + */ +@interface CCShuffleTiles : CCTiledGrid3DAction +{ + int seed; + NSUInteger tilesCount; + int *tilesOrder; + void *tiles; +} + +/** creates the action with a random seed, the grid size and the duration */ ++(id)actionWithSeed:(int)s grid:(ccGridSize)gridSize duration:(ccTime)d; +/** initializes the action with a random seed, the grid size and the duration */ +-(id)initWithSeed:(int)s grid:(ccGridSize)gridSize duration:(ccTime)d; + +@end + +//////////////////////////////////////////////////////////// + +/** CCFadeOutTRTiles action + Fades out the tiles in a Top-Right direction + */ +@interface CCFadeOutTRTiles : CCTiledGrid3DAction +{ +} +@end + +//////////////////////////////////////////////////////////// + +/** CCFadeOutBLTiles action. + Fades out the tiles in a Bottom-Left direction + */ +@interface CCFadeOutBLTiles : CCFadeOutTRTiles +{ +} +@end + +//////////////////////////////////////////////////////////// + +/** CCFadeOutUpTiles action. + Fades out the tiles in upwards direction + */ +@interface CCFadeOutUpTiles : CCFadeOutTRTiles +{ +} +@end + +//////////////////////////////////////////////////////////// + +/** CCFadeOutDownTiles action. + Fades out the tiles in downwards direction + */ +@interface CCFadeOutDownTiles : CCFadeOutUpTiles +{ +} +@end + +//////////////////////////////////////////////////////////// + +/** CCTurnOffTiles action. + Turn off the files in random order + */ +@interface CCTurnOffTiles : CCTiledGrid3DAction +{ + int seed; + NSUInteger tilesCount; + int *tilesOrder; +} + +/** creates the action with a random seed, the grid size and the duration */ ++(id)actionWithSeed:(int)s grid:(ccGridSize)gridSize duration:(ccTime)d; +/** initializes the action with a random seed, the grid size and the duration */ +-(id)initWithSeed:(int)s grid:(ccGridSize)gridSize duration:(ccTime)d; +@end + +//////////////////////////////////////////////////////////// + +/** CCWavesTiles3D action. */ +@interface CCWavesTiles3D : CCTiledGrid3DAction +{ + int waves; + float amplitude; + float amplitudeRate; +} + +/** waves amplitude */ +@property (nonatomic,readwrite) float amplitude; +/** waves amplitude rate */ +@property (nonatomic,readwrite) float amplitudeRate; + +/** creates the action with a number of waves, the waves amplitude, the grid size and the duration */ ++(id)actionWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d; +/** initializes the action with a number of waves, the waves amplitude, the grid size and the duration */ +-(id)initWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d; + +@end + +//////////////////////////////////////////////////////////// + +/** CCJumpTiles3D action. + A sin function is executed to move the tiles across the Z axis + */ +@interface CCJumpTiles3D : CCTiledGrid3DAction +{ + int jumps; + float amplitude; + float amplitudeRate; +} + +/** amplitude of the sin*/ +@property (nonatomic,readwrite) float amplitude; +/** amplitude rate */ +@property (nonatomic,readwrite) float amplitudeRate; + +/** creates the action with the number of jumps, the sin amplitude, the grid size and the duration */ ++(id)actionWithJumps:(int)j amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d; +/** initializes the action with the number of jumps, the sin amplitude, the grid size and the duration */ +-(id)initWithJumps:(int)j amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d; + +@end + +//////////////////////////////////////////////////////////// + +/** CCSplitRows action */ +@interface CCSplitRows : CCTiledGrid3DAction +{ + int rows; + CGSize winSize; +} +/** creates the action with the number of rows to split and the duration */ ++(id)actionWithRows:(int)rows duration:(ccTime)duration; +/** initializes the action with the number of rows to split and the duration */ +-(id)initWithRows:(int)rows duration:(ccTime)duration; + +@end + +//////////////////////////////////////////////////////////// + +/** CCSplitCols action */ +@interface CCSplitCols : CCTiledGrid3DAction +{ + int cols; + CGSize winSize; +} +/** creates the action with the number of columns to split and the duration */ ++(id)actionWithCols:(int)cols duration:(ccTime)duration; +/** initializes the action with the number of columns to split and the duration */ +-(id)initWithCols:(int)cols duration:(ccTime)duration; + +@end diff --git a/libs/cocos2d/CCActionTiledGrid.m b/libs/cocos2d/CCActionTiledGrid.m new file mode 100644 index 0000000..75965ec --- /dev/null +++ b/libs/cocos2d/CCActionTiledGrid.m @@ -0,0 +1,768 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2009 On-Core + * + * 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 "CCActionTiledGrid.h" +#import "CCDirector.h" +#import "ccMacros.h" +#import "Support/CGPointExtension.h" + +typedef struct +{ + CGPoint position; + CGPoint startPosition; + ccGridSize delta; +} Tile; + +#pragma mark - +#pragma mark ShakyTiles3D + +@implementation CCShakyTiles3D + ++(id)actionWithRange:(int)range shakeZ:(BOOL)shakeZ grid:(ccGridSize)gridSize duration:(ccTime)d +{ + return [[[self alloc] initWithRange:range shakeZ:shakeZ grid:gridSize duration:d] autorelease]; +} + +-(id)initWithRange:(int)range shakeZ:(BOOL)sz grid:(ccGridSize)gSize duration:(ccTime)d +{ + if ( (self = [super initWithSize:gSize duration:d]) ) + { + randrange = range; + shakeZ = sz; + } + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithRange:randrange shakeZ:shakeZ grid:gridSize_ duration:duration_]; + return copy; +} + + +-(void)update:(ccTime)time +{ + int i, j; + + for( i = 0; i < gridSize_.x; i++ ) + { + for( j = 0; j < gridSize_.y; j++ ) + { + ccQuad3 coords = [self originalTile:ccg(i,j)]; + + // X + coords.bl.x += ( rand() % (randrange*2) ) - randrange; + coords.br.x += ( rand() % (randrange*2) ) - randrange; + coords.tl.x += ( rand() % (randrange*2) ) - randrange; + coords.tr.x += ( rand() % (randrange*2) ) - randrange; + + // Y + coords.bl.y += ( rand() % (randrange*2) ) - randrange; + coords.br.y += ( rand() % (randrange*2) ) - randrange; + coords.tl.y += ( rand() % (randrange*2) ) - randrange; + coords.tr.y += ( rand() % (randrange*2) ) - randrange; + + if( shakeZ ) { + coords.bl.z += ( rand() % (randrange*2) ) - randrange; + coords.br.z += ( rand() % (randrange*2) ) - randrange; + coords.tl.z += ( rand() % (randrange*2) ) - randrange; + coords.tr.z += ( rand() % (randrange*2) ) - randrange; + } + + [self setTile:ccg(i,j) coords:coords]; + } + } +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark CCShatteredTiles3D + +@implementation CCShatteredTiles3D + ++(id)actionWithRange:(int)range shatterZ:(BOOL)sz grid:(ccGridSize)gridSize duration:(ccTime)d +{ + return [[[self alloc] initWithRange:range shatterZ:sz grid:gridSize duration:d] autorelease]; +} + +-(id)initWithRange:(int)range shatterZ:(BOOL)sz grid:(ccGridSize)gSize duration:(ccTime)d +{ + if ( (self = [super initWithSize:gSize duration:d]) ) + { + once = NO; + randrange = range; + shatterZ = sz; + } + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithRange:randrange shatterZ:shatterZ grid:gridSize_ duration:duration_]; + return copy; +} + + +-(void)update:(ccTime)time +{ + int i, j; + + if ( once == NO ) + { + for( i = 0; i < gridSize_.x; i++ ) + { + for( j = 0; j < gridSize_.y; j++ ) + { + ccQuad3 coords = [self originalTile:ccg(i,j)]; + + // X + coords.bl.x += ( rand() % (randrange*2) ) - randrange; + coords.br.x += ( rand() % (randrange*2) ) - randrange; + coords.tl.x += ( rand() % (randrange*2) ) - randrange; + coords.tr.x += ( rand() % (randrange*2) ) - randrange; + + // Y + coords.bl.y += ( rand() % (randrange*2) ) - randrange; + coords.br.y += ( rand() % (randrange*2) ) - randrange; + coords.tl.y += ( rand() % (randrange*2) ) - randrange; + coords.tr.y += ( rand() % (randrange*2) ) - randrange; + + if( shatterZ ) { + coords.bl.z += ( rand() % (randrange*2) ) - randrange; + coords.br.z += ( rand() % (randrange*2) ) - randrange; + coords.tl.z += ( rand() % (randrange*2) ) - randrange; + coords.tr.z += ( rand() % (randrange*2) ) - randrange; + } + + [self setTile:ccg(i,j) coords:coords]; + } + } + + once = YES; + } +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark CCShuffleTiles + +@implementation CCShuffleTiles + ++(id)actionWithSeed:(int)s grid:(ccGridSize)gridSize duration:(ccTime)d +{ + return [[[self alloc] initWithSeed:s grid:gridSize duration:d] autorelease]; +} + +-(id)initWithSeed:(int)s grid:(ccGridSize)gSize duration:(ccTime)d +{ + if ( (self = [super initWithSize:gSize duration:d]) ) + { + seed = s; + tilesOrder = nil; + tiles = nil; + } + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithSeed:seed grid:gridSize_ duration:duration_]; + return copy; +} + + +-(void)dealloc +{ + if ( tilesOrder ) free(tilesOrder); + if ( tiles ) free(tiles); + [super dealloc]; +} + +-(void)shuffle:(int*)array count:(NSUInteger)len +{ + NSInteger i; + for( i = len - 1; i >= 0; i-- ) + { + NSInteger j = rand() % (i+1); + int v = array[i]; + array[i] = array[j]; + array[j] = v; + } +} + +-(ccGridSize)getDelta:(ccGridSize)pos +{ + CGPoint pos2; + + NSInteger idx = pos.x * gridSize_.y + pos.y; + + pos2.x = tilesOrder[idx] / (int)gridSize_.y; + pos2.y = tilesOrder[idx] % (int)gridSize_.y; + + return ccg(pos2.x - pos.x, pos2.y - pos.y); +} + +-(void)placeTile:(ccGridSize)pos tile:(Tile)t +{ + ccQuad3 coords = [self originalTile:pos]; + + CGPoint step = [[target_ grid] step]; + coords.bl.x += (int)(t.position.x * step.x); + coords.bl.y += (int)(t.position.y * step.y); + + coords.br.x += (int)(t.position.x * step.x); + coords.br.y += (int)(t.position.y * step.y); + + coords.tl.x += (int)(t.position.x * step.x); + coords.tl.y += (int)(t.position.y * step.y); + + coords.tr.x += (int)(t.position.x * step.x); + coords.tr.y += (int)(t.position.y * step.y); + + [self setTile:pos coords:coords]; +} + +-(void)startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + + if ( seed != -1 ) + srand(seed); + + tilesCount = gridSize_.x * gridSize_.y; + tilesOrder = (int*)malloc(tilesCount*sizeof(int)); + int i, j; + + for( i = 0; i < tilesCount; i++ ) + tilesOrder[i] = i; + + [self shuffle:tilesOrder count:tilesCount]; + + tiles = malloc(tilesCount*sizeof(Tile)); + Tile *tileArray = (Tile*)tiles; + + for( i = 0; i < gridSize_.x; i++ ) + { + for( j = 0; j < gridSize_.y; j++ ) + { + tileArray->position = ccp(i,j); + tileArray->startPosition = ccp(i,j); + tileArray->delta = [self getDelta:ccg(i,j)]; + tileArray++; + } + } +} + +-(void)update:(ccTime)time +{ + int i, j; + + Tile *tileArray = (Tile*)tiles; + + for( i = 0; i < gridSize_.x; i++ ) + { + for( j = 0; j < gridSize_.y; j++ ) + { + tileArray->position = ccpMult( ccp(tileArray->delta.x, tileArray->delta.y), time); + [self placeTile:ccg(i,j) tile:*tileArray]; + tileArray++; + } + } +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark CCFadeOutTRTiles + +@implementation CCFadeOutTRTiles + +-(float)testFunc:(ccGridSize)pos time:(ccTime)time +{ + CGPoint n = ccpMult( ccp(gridSize_.x,gridSize_.y), time); + if ( (n.x+n.y) == 0.0f ) + return 1.0f; + + return powf( (pos.x+pos.y) / (n.x+n.y), 6 ); +} + +-(void)turnOnTile:(ccGridSize)pos +{ + [self setTile:pos coords:[self originalTile:pos]]; +} + +-(void)turnOffTile:(ccGridSize)pos +{ + ccQuad3 coords; + bzero(&coords, sizeof(ccQuad3)); + [self setTile:pos coords:coords]; +} + +-(void)transformTile:(ccGridSize)pos distance:(float)distance +{ + ccQuad3 coords = [self originalTile:pos]; + CGPoint step = [[target_ grid] step]; + + coords.bl.x += (step.x / 2) * (1.0f - distance); + coords.bl.y += (step.y / 2) * (1.0f - distance); + + coords.br.x -= (step.x / 2) * (1.0f - distance); + coords.br.y += (step.y / 2) * (1.0f - distance); + + coords.tl.x += (step.x / 2) * (1.0f - distance); + coords.tl.y -= (step.y / 2) * (1.0f - distance); + + coords.tr.x -= (step.x / 2) * (1.0f - distance); + coords.tr.y -= (step.y / 2) * (1.0f - distance); + + [self setTile:pos coords:coords]; +} + +-(void)update:(ccTime)time +{ + int i, j; + + for( i = 0; i < gridSize_.x; i++ ) + { + for( j = 0; j < gridSize_.y; j++ ) + { + float distance = [self testFunc:ccg(i,j) time:time]; + if ( distance == 0 ) + [self turnOffTile:ccg(i,j)]; + else if ( distance < 1 ) + [self transformTile:ccg(i,j) distance:distance]; + else + [self turnOnTile:ccg(i,j)]; + } + } +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark CCFadeOutBLTiles + +@implementation CCFadeOutBLTiles + +-(float)testFunc:(ccGridSize)pos time:(ccTime)time +{ + CGPoint n = ccpMult(ccp(gridSize_.x, gridSize_.y), (1.0f-time)); + if ( (pos.x+pos.y) == 0 ) + return 1.0f; + + return powf( (n.x+n.y) / (pos.x+pos.y), 6 ); +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark CCFadeOutUpTiles + +@implementation CCFadeOutUpTiles + +-(float)testFunc:(ccGridSize)pos time:(ccTime)time +{ + CGPoint n = ccpMult(ccp(gridSize_.x, gridSize_.y), time); + if ( n.y == 0 ) + return 1.0f; + + return powf( pos.y / n.y, 6 ); +} + +-(void)transformTile:(ccGridSize)pos distance:(float)distance +{ + ccQuad3 coords = [self originalTile:pos]; + CGPoint step = [[target_ grid] step]; + + coords.bl.y += (step.y / 2) * (1.0f - distance); + coords.br.y += (step.y / 2) * (1.0f - distance); + coords.tl.y -= (step.y / 2) * (1.0f - distance); + coords.tr.y -= (step.y / 2) * (1.0f - distance); + + [self setTile:pos coords:coords]; +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark CCFadeOutDownTiles + +@implementation CCFadeOutDownTiles + +-(float)testFunc:(ccGridSize)pos time:(ccTime)time +{ + CGPoint n = ccpMult(ccp(gridSize_.x,gridSize_.y), (1.0f - time)); + if ( pos.y == 0 ) + return 1.0f; + + return powf( n.y / pos.y, 6 ); +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark TurnOffTiles + +@implementation CCTurnOffTiles + ++(id)actionWithSeed:(int)s grid:(ccGridSize)gridSize duration:(ccTime)d +{ + return [[[self alloc] initWithSeed:s grid:gridSize duration:d] autorelease]; +} + +-(id)initWithSeed:(int)s grid:(ccGridSize)gSize duration:(ccTime)d +{ + if ( (self = [super initWithSize:gSize duration:d]) ) + { + seed = s; + tilesOrder = nil; + } + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithSeed:seed grid:gridSize_ duration:duration_]; + return copy; +} + +-(void)dealloc +{ + if ( tilesOrder ) free(tilesOrder); + [super dealloc]; +} + +-(void)shuffle:(int*)array count:(NSUInteger)len +{ + NSInteger i; + for( i = len - 1; i >= 0; i-- ) + { + NSInteger j = rand() % (i+1); + int v = array[i]; + array[i] = array[j]; + array[j] = v; + } +} + +-(void)turnOnTile:(ccGridSize)pos +{ + [self setTile:pos coords:[self originalTile:pos]]; +} + +-(void)turnOffTile:(ccGridSize)pos +{ + ccQuad3 coords; + + bzero(&coords, sizeof(ccQuad3)); + [self setTile:pos coords:coords]; +} + +-(void)startWithTarget:(id)aTarget +{ + int i; + + [super startWithTarget:aTarget]; + + if ( seed != -1 ) + srand(seed); + + tilesCount = gridSize_.x * gridSize_.y; + tilesOrder = (int*)malloc(tilesCount*sizeof(int)); + + for( i = 0; i < tilesCount; i++ ) + tilesOrder[i] = i; + + [self shuffle:tilesOrder count:tilesCount]; +} + +-(void)update:(ccTime)time +{ + int i, l, t; + + l = (int)(time * (float)tilesCount); + + for( i = 0; i < tilesCount; i++ ) + { + t = tilesOrder[i]; + ccGridSize tilePos = ccg( t / gridSize_.y, t % gridSize_.y ); + + if ( i < l ) + [self turnOffTile:tilePos]; + else + [self turnOnTile:tilePos]; + } +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark CCWavesTiles3D + +@implementation CCWavesTiles3D + +@synthesize amplitude; +@synthesize amplitudeRate; + ++(id)actionWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d +{ + return [[[self alloc] initWithWaves:wav amplitude:amp grid:gridSize duration:d] autorelease]; +} + +-(id)initWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gSize duration:(ccTime)d +{ + if ( (self = [super initWithSize:gSize duration:d]) ) + { + waves = wav; + amplitude = amp; + amplitudeRate = 1.0f; + } + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithWaves:waves amplitude:amplitude grid:gridSize_ duration:duration_]; + return copy; +} + + +-(void)update:(ccTime)time +{ + int i, j; + + for( i = 0; i < gridSize_.x; i++ ) + { + for( j = 0; j < gridSize_.y; j++ ) + { + ccQuad3 coords = [self originalTile:ccg(i,j)]; + + coords.bl.z = (sinf(time*(CGFloat)M_PI*waves*2 + (coords.bl.y+coords.bl.x) * .01f) * amplitude * amplitudeRate ); + coords.br.z = coords.bl.z; + coords.tl.z = coords.bl.z; + coords.tr.z = coords.bl.z; + + [self setTile:ccg(i,j) coords:coords]; + } + } +} +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark CCJumpTiles3D + +@implementation CCJumpTiles3D + +@synthesize amplitude; +@synthesize amplitudeRate; + ++(id)actionWithJumps:(int)j amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d +{ + return [[[self alloc] initWithJumps:j amplitude:amp grid:gridSize duration:d] autorelease]; +} + +-(id)initWithJumps:(int)j amplitude:(float)amp grid:(ccGridSize)gSize duration:(ccTime)d +{ + if ( (self = [super initWithSize:gSize duration:d]) ) + { + jumps = j; + amplitude = amp; + amplitudeRate = 1.0f; + } + + return self; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithJumps:jumps amplitude:amplitude grid:gridSize_ duration:duration_]; + return copy; +} + + +-(void)update:(ccTime)time +{ + int i, j; + + float sinz = (sinf((CGFloat)M_PI*time*jumps*2) * amplitude * amplitudeRate ); + float sinz2 = (sinf((CGFloat)M_PI*(time*jumps*2 + 1)) * amplitude * amplitudeRate ); + + for( i = 0; i < gridSize_.x; i++ ) + { + for( j = 0; j < gridSize_.y; j++ ) + { + ccQuad3 coords = [self originalTile:ccg(i,j)]; + + if ( ((i+j) % 2) == 0 ) + { + coords.bl.z += sinz; + coords.br.z += sinz; + coords.tl.z += sinz; + coords.tr.z += sinz; + } + else + { + coords.bl.z += sinz2; + coords.br.z += sinz2; + coords.tl.z += sinz2; + coords.tr.z += sinz2; + } + + [self setTile:ccg(i,j) coords:coords]; + } + } +} +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark SplitRows + +@implementation CCSplitRows + ++(id)actionWithRows:(int)r duration:(ccTime)d +{ + return [[[self alloc] initWithRows:r duration:d] autorelease]; +} + +-(id)initWithRows:(int)r duration:(ccTime)d +{ + rows = r; + return [super initWithSize:ccg(1,r) duration:d]; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithRows:rows duration:duration_]; + return copy; +} + +-(void)startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + winSize = [[CCDirector sharedDirector] winSizeInPixels]; +} + +-(void)update:(ccTime)time +{ + int j; + + for( j = 0; j < gridSize_.y; j++ ) + { + ccQuad3 coords = [self originalTile:ccg(0,j)]; + float direction = 1; + + if ( (j % 2 ) == 0 ) + direction = -1; + + coords.bl.x += direction * winSize.width * time; + coords.br.x += direction * winSize.width * time; + coords.tl.x += direction * winSize.width * time; + coords.tr.x += direction * winSize.width * time; + + [self setTile:ccg(0,j) coords:coords]; + } +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark CCSplitCols + +@implementation CCSplitCols + ++(id)actionWithCols:(int)c duration:(ccTime)d +{ + return [[[self alloc] initWithCols:c duration:d] autorelease]; +} + +-(id)initWithCols:(int)c duration:(ccTime)d +{ + cols = c; + return [super initWithSize:ccg(c,1) duration:d]; +} + +-(id) copyWithZone: (NSZone*) zone +{ + CCGridAction *copy = [[[self class] allocWithZone:zone] initWithCols:cols duration:duration_]; + return copy; +} + +-(void)startWithTarget:(id)aTarget +{ + [super startWithTarget:aTarget]; + winSize = [[CCDirector sharedDirector] winSizeInPixels]; +} + +-(void)update:(ccTime)time +{ + int i; + + for( i = 0; i < gridSize_.x; i++ ) + { + ccQuad3 coords = [self originalTile:ccg(i,0)]; + float direction = 1; + + if ( (i % 2 ) == 0 ) + direction = -1; + + coords.bl.y += direction * winSize.height * time; + coords.br.y += direction * winSize.height * time; + coords.tl.y += direction * winSize.height * time; + coords.tr.y += direction * winSize.height * time; + + [self setTile:ccg(i,0) coords:coords]; + } +} + +@end diff --git a/libs/cocos2d/CCActionTween.h b/libs/cocos2d/CCActionTween.h new file mode 100644 index 0000000..69fdea5 --- /dev/null +++ b/libs/cocos2d/CCActionTween.h @@ -0,0 +1,62 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright 2009 lhunath (Maarten Billemont) + * + * 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 +#import "CCActionInterval.h" + +/** CCActionTween + + CCActionTween is an action that lets you update any property of an object. + For example, if you want to modify the "width" property of a target from 200 to 300 in 2 senconds, then: + + id modifyWidth = [CCActionTween actionWithDuration:2 key:@"width" from:200 to:300]; + [target runAction:modifyWidth]; + + + Another example: CCScaleTo action could be rewriten using CCPropertyAction: + + // scaleA and scaleB are equivalents + id scaleA = [CCScaleTo actionWithDuration:2 scale:3]; + id scaleB = [CCActionTween actionWithDuration:2 key:@"scale" from:1 to:3]; + + + @since v0.99.2 + */ +@interface CCActionTween : CCActionInterval +{ + NSString *key_; + + float from_, to_; + float delta_; +} + +/** creates an initializes the action with the property name (key), and the from and to parameters. */ ++ (id)actionWithDuration:(ccTime)aDuration key:(NSString *)key from:(float)from to:(float)to; + +/** initializes the action with the property name (key), and the from and to parameters. */ +- (id)initWithDuration:(ccTime)aDuration key:(NSString *)key from:(float)from to:(float)to; + +@end diff --git a/libs/cocos2d/CCActionTween.m b/libs/cocos2d/CCActionTween.m new file mode 100644 index 0000000..95ae572 --- /dev/null +++ b/libs/cocos2d/CCActionTween.m @@ -0,0 +1,72 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright 2009 lhunath (Maarten Billemont) + * + * 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 "CCActionTween.h" + + +@implementation CCActionTween + ++ (id)actionWithDuration:(ccTime)aDuration key:(NSString *)aKey from:(float)aFrom to:(float)aTo { + + return [[[[self class] alloc] initWithDuration:aDuration key:aKey from:aFrom to:aTo] autorelease]; +} + +- (id)initWithDuration:(ccTime)aDuration key:(NSString *)key from:(float)from to:(float)to { + + if ((self = [super initWithDuration:aDuration])) { + + key_ = [key copy]; + to_ = to; + from_ = from; + + } + + return self; +} + +- (void) dealloc +{ + [key_ release]; + [super dealloc]; +} + +- (void)startWithTarget:aTarget +{ + [super startWithTarget:aTarget]; + delta_ = to_ - from_; +} + +- (void) update:(ccTime) dt +{ + [target_ setValue:[NSNumber numberWithFloat:to_ - delta_ * (1 - dt)] forKey:key_]; +} + +- (CCActionInterval *) reverse +{ + return [[self class] actionWithDuration:duration_ key:key_ from:to_ to:from_]; +} + + +@end diff --git a/libs/cocos2d/CCAnimation.h b/libs/cocos2d/CCAnimation.h new file mode 100644 index 0000000..24b3d96 --- /dev/null +++ b/libs/cocos2d/CCAnimation.h @@ -0,0 +1,136 @@ +/* + * 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. + * + */ + +#import +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +#import +#endif // IPHONE + +@class CCSpriteFrame; +@class CCTexture2D; + +/** A CCAnimation object is used to perform animations on the CCSprite objects. + + The CCAnimation object contains CCSpriteFrame objects, and a possible delay between the frames. + You can animate a CCAnimation object by using the CCAnimate action. Example: + + [sprite runAction:[CCAnimate actionWithAnimation:animation]]; + + */ +@interface CCAnimation : NSObject +{ + NSString *name_; + float delay_; + NSMutableArray *frames_; +} + +/** name of the animation */ +@property (nonatomic,readwrite,retain) NSString *name; +/** delay between frames in seconds. */ +@property (nonatomic,readwrite,assign) float delay; +/** array of frames */ +@property (nonatomic,readwrite,retain) NSMutableArray *frames; + +/** Creates an animation + @since v0.99.5 + */ ++(id) animation; + +/** Creates an animation with frames. + @since v0.99.5 + */ ++(id) animationWithFrames:(NSArray*)frames; + +/* Creates an animation with frames and a delay between frames. + @since v0.99.5 + */ ++(id) animationWithFrames:(NSArray*)frames delay:(float)delay; + +/** Creates a CCAnimation with a name + @since v0.99.3 + @deprecated Will be removed in 1.0.1. Use "animation" instead. + */ ++(id) animationWithName:(NSString*)name DEPRECATED_ATTRIBUTE; + +/** Creates a CCAnimation with a name and frames + @since v0.99.3 + @deprecated Will be removed in 1.0.1. Use "animationWithFrames" instead. + */ ++(id) animationWithName:(NSString*)name frames:(NSArray*)frames DEPRECATED_ATTRIBUTE; + +/** Creates a CCAnimation with a name and delay between frames. */ ++(id) animationWithName:(NSString*)name delay:(float)delay DEPRECATED_ATTRIBUTE; + +/** Creates a CCAnimation with a name, delay and an array of CCSpriteFrames. */ ++(id) animationWithName:(NSString*)name delay:(float)delay frames:(NSArray*)frames DEPRECATED_ATTRIBUTE; + + +/** Initializes a CCAnimation with frames. + @since v0.99.5 +*/ +-(id) initWithFrames:(NSArray*)frames; + +/** Initializes a CCAnimation with frames and a delay between frames + @since v0.99.5 + */ +-(id) initWithFrames:(NSArray *)frames delay:(float)delay; + +/** Initializes a CCAnimation with a name + @since v0.99.3 + @deprecated Will be removed in 1.0.1. Use "init" instead. + */ +-(id) initWithName:(NSString*)name DEPRECATED_ATTRIBUTE; + +/** Initializes a CCAnimation with a name and frames + @since v0.99.3 + @deprecated Will be removed in 1.0.1. Use "initWithFrames" instead. + */ +-(id) initWithName:(NSString*)name frames:(NSArray*)frames DEPRECATED_ATTRIBUTE; + +/** Initializes a CCAnimation with a name and delay between frames. + @deprecated Will be removed in 1.0.1. Use "initWithFrames:nil delay:delay" instead. +*/ +-(id) initWithName:(NSString*)name delay:(float)delay DEPRECATED_ATTRIBUTE; + +/** Initializes a CCAnimation with a name, delay and an array of CCSpriteFrames. + @deprecated Will be removed in 1.0.1. Use "initWithFrames:frames delay:delay" instead. +*/ +-(id) initWithName:(NSString*)name delay:(float)delay frames:(NSArray*)frames DEPRECATED_ATTRIBUTE; + +/** Adds a frame to a CCAnimation. */ +-(void) addFrame:(CCSpriteFrame*)frame; + +/** Adds a frame with an image filename. Internally it will create a CCSpriteFrame and it will add it. + Added to facilitate the migration from v0.8 to v0.9. + */ +-(void) addFrameWithFilename:(NSString*)filename; + +/** Adds a frame with a texture and a rect. Internally it will create a CCSpriteFrame and it will add it. + Added to facilitate the migration from v0.8 to v0.9. + */ +-(void) addFrameWithTexture:(CCTexture2D*)texture rect:(CGRect)rect; + +@end diff --git a/libs/cocos2d/CCAnimation.m b/libs/cocos2d/CCAnimation.m new file mode 100644 index 0000000..eb674c6 --- /dev/null +++ b/libs/cocos2d/CCAnimation.m @@ -0,0 +1,153 @@ +/* + * 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. + * + */ + +#import "ccMacros.h" +#import "CCAnimation.h" +#import "CCSpriteFrame.h" +#import "CCTexture2D.h" +#import "CCTextureCache.h" + +@implementation CCAnimation +@synthesize name = name_, delay = delay_, frames = frames_; + ++(id) animation +{ + return [[[self alloc] init] autorelease]; +} + ++(id) animationWithFrames:(NSArray*)frames +{ + return [[[self alloc] initWithFrames:frames] autorelease]; +} + ++(id) animationWithFrames:(NSArray*)frames delay:(float)delay +{ + return [[[self alloc] initWithFrames:frames delay:delay] autorelease]; +} + ++(id) animationWithName:(NSString*)name +{ + return [[[self alloc] initWithName:name] autorelease]; +} + ++(id) animationWithName:(NSString*)name frames:(NSArray*)frames +{ + return [[[self alloc] initWithName:name frames:frames] autorelease]; +} + ++(id) animationWithName:(NSString*)aname delay:(float)d frames:(NSArray*)array +{ + return [[[self alloc] initWithName:aname delay:d frames:array] autorelease]; +} + ++(id) animationWithName:(NSString*)aname delay:(float)d +{ + return [[[self alloc] initWithName:aname delay:d] autorelease]; +} + +-(id) init +{ + return [self initWithFrames:nil delay:0]; +} + +-(id) initWithFrames:(NSArray*)frames +{ + return [self initWithFrames:frames delay:0]; +} + +-(id) initWithFrames:(NSArray*)array delay:(float)delay +{ + if( (self=[super init]) ) { + + delay_ = delay; + self.frames = [NSMutableArray arrayWithArray:array]; + } + return self; +} + +-(id) initWithName:(NSString*)name +{ + return [self initWithName:name delay:0 frames:nil]; +} + +-(id) initWithName:(NSString*)name frames:(NSArray*)frames +{ + return [self initWithName:name delay:0 frames:frames]; +} + +-(id) initWithName:(NSString*)t delay:(float)d +{ + return [self initWithName:t delay:d frames:nil]; +} + +-(id) initWithName:(NSString*)name delay:(float)delay frames:(NSArray*)array +{ + if( (self=[super init]) ) { + + delay_ = delay; + self.name = name; + self.frames = [NSMutableArray arrayWithArray:array]; + } + return self; +} + +- (NSString*) description +{ + return [NSString stringWithFormat:@"<%@ = %08X | frames=%d, delay:%f>", [self class], self, + [frames_ count], + delay_ + ]; +} + +-(void) dealloc +{ + CCLOGINFO( @"cocos2d: deallocing %@",self); + [name_ release]; + [frames_ release]; + [super dealloc]; +} + +-(void) addFrame:(CCSpriteFrame*)frame +{ + [frames_ addObject:frame]; +} + +-(void) addFrameWithFilename:(NSString*)filename +{ + CCTexture2D *texture = [[CCTextureCache sharedTextureCache] addImage:filename]; + CGRect rect = CGRectZero; + rect.size = texture.contentSize; + CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:texture rect:rect]; + [frames_ addObject:frame]; +} + +-(void) addFrameWithTexture:(CCTexture2D*)texture rect:(CGRect)rect +{ + CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:texture rect:rect]; + [frames_ addObject:frame]; +} + +@end diff --git a/libs/cocos2d/CCAnimationCache.h b/libs/cocos2d/CCAnimationCache.h new file mode 100644 index 0000000..075c836 --- /dev/null +++ b/libs/cocos2d/CCAnimationCache.h @@ -0,0 +1,64 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 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 + +@class CCAnimation; + +/** Singleton that manages the Animations. + It saves in a cache the animations. You should use this class if you want to save your animations in a cache. + + Before v0.99.5, the recommend way was to save them on the CCSprite. Since v0.99.5, you should use this class instead. + + @since v0.99.5 + */ +@interface CCAnimationCache : NSObject +{ + NSMutableDictionary *animations_; +} + +/** Retruns ths shared instance of the Animation cache */ ++ (CCAnimationCache *) sharedAnimationCache; + +/** Purges the cache. It releases all the CCAnimation objects and the shared instance. + */ ++(void)purgeSharedAnimationCache; + +/** Adds a CCAnimation with a name. + */ +-(void) addAnimation:(CCAnimation*)animation name:(NSString*)name; + +/** Deletes a CCAnimation from the cache. + */ +-(void) removeAnimationByName:(NSString*)name; + +/** Returns a CCAnimation that was previously added. + If the name is not found it will return nil. + You should retain the returned copy if you are going to use it. + */ +-(CCAnimation*) animationByName:(NSString*)name; + +@end diff --git a/libs/cocos2d/CCAnimationCache.m b/libs/cocos2d/CCAnimationCache.m new file mode 100644 index 0000000..f508227 --- /dev/null +++ b/libs/cocos2d/CCAnimationCache.m @@ -0,0 +1,101 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 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 "ccMacros.h" +#import "CCAnimationCache.h" +#import "CCAnimation.h" +#import "CCSprite.h" + + +@implementation CCAnimationCache + +#pragma mark CCAnimationCache - Alloc, Init & Dealloc + +static CCAnimationCache *sharedAnimationCache_=nil; + ++ (CCAnimationCache *)sharedAnimationCache +{ + if (!sharedAnimationCache_) + sharedAnimationCache_ = [[CCAnimationCache alloc] init]; + + return sharedAnimationCache_; +} + ++(id)alloc +{ + NSAssert(sharedAnimationCache_ == nil, @"Attempted to allocate a second instance of a singleton."); + return [super alloc]; +} + ++(void)purgeSharedAnimationCache +{ + [sharedAnimationCache_ release]; + sharedAnimationCache_ = nil; +} + +-(id) init +{ + if( (self=[super init]) ) { + animations_ = [[NSMutableDictionary alloc] initWithCapacity: 20]; + } + + return self; +} + +- (NSString*) description +{ + return [NSString stringWithFormat:@"<%@ = %08X | num of animations = %i>", [self class], self, [animations_ count]]; +} + +-(void) dealloc +{ + CCLOGINFO(@"cocos2d: deallocing %@", self); + + [animations_ release]; + [super dealloc]; +} + +#pragma mark CCAnimationCache - load/get/del + +-(void) addAnimation:(CCAnimation*)animation name:(NSString*)name +{ + [animations_ setObject:animation forKey:name]; +} + +-(void) removeAnimationByName:(NSString*)name +{ + if( ! name ) + return; + + [animations_ removeObjectForKey:name]; +} + +-(CCAnimation*) animationByName:(NSString*)name +{ + return [animations_ objectForKey:name]; +} + +@end diff --git a/libs/cocos2d/CCAtlasNode.h b/libs/cocos2d/CCAtlasNode.h new file mode 100644 index 0000000..c9da16f --- /dev/null +++ b/libs/cocos2d/CCAtlasNode.h @@ -0,0 +1,88 @@ +/* + * 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. + */ + + +#import "CCTextureAtlas.h" +#import "CCNode.h" +#import "CCProtocols.h" + +/** CCAtlasNode is a subclass of CCNode that implements the CCRGBAProtocol and + CCTextureProtocol protocol + + It knows how to render a TextureAtlas object. + If you are going to render a TextureAtlas consider subclassing CCAtlasNode (or a subclass of CCAtlasNode) + + All features from CCNode are valid, plus the following features: + - opacity and RGB colors + */ +@interface CCAtlasNode : CCNode +{ + // texture atlas + CCTextureAtlas *textureAtlas_; + + // chars per row + NSUInteger itemsPerRow_; + // chars per column + NSUInteger itemsPerColumn_; + + // width of each char + NSUInteger itemWidth_; + // height of each char + NSUInteger itemHeight_; + + // blend function + ccBlendFunc blendFunc_; + + // texture RGBA. + GLubyte opacity_; + ccColor3B color_; + ccColor3B colorUnmodified_; + BOOL opacityModifyRGB_; +} + +/** conforms to CCTextureProtocol protocol */ +@property (nonatomic,readwrite,retain) CCTextureAtlas *textureAtlas; + +/** conforms to CCTextureProtocol protocol */ +@property (nonatomic,readwrite) ccBlendFunc blendFunc; + +/** conforms to CCRGBAProtocol protocol */ +@property (nonatomic,readwrite) GLubyte opacity; +/** conforms to CCRGBAProtocol protocol */ +@property (nonatomic,readwrite) ccColor3B color; + + +/** creates a CCAtlasNode with an Atlas file the width and height of each item measured in points and the quantity of items to render*/ ++(id) atlasWithTileFile:(NSString*)tile tileWidth:(NSUInteger)w tileHeight:(NSUInteger)h itemsToRender: (NSUInteger) c; + +/** initializes an CCAtlasNode with an Atlas file the width and height of each item measured in points and the quantity of items to render*/ +-(id) initWithTileFile:(NSString*)tile tileWidth:(NSUInteger)w tileHeight:(NSUInteger)h itemsToRender: (NSUInteger) c; + +/** updates the Atlas (indexed vertex array). + * Shall be overriden in subclasses + */ +-(void) updateAtlasValues; +@end diff --git a/libs/cocos2d/CCAtlasNode.m b/libs/cocos2d/CCAtlasNode.m new file mode 100644 index 0000000..f715f5b --- /dev/null +++ b/libs/cocos2d/CCAtlasNode.m @@ -0,0 +1,206 @@ +/* + * 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. + */ + + +#import "CCAtlasNode.h" +#import "ccMacros.h" + + +@interface CCAtlasNode () +-(void) calculateMaxItems; +-(void) updateBlendFunc; +-(void) updateOpacityModifyRGB; +@end + +@implementation CCAtlasNode + +@synthesize textureAtlas = textureAtlas_; +@synthesize blendFunc = blendFunc_; + +#pragma mark CCAtlasNode - Creation & Init ++(id) atlasWithTileFile:(NSString*)tile tileWidth:(NSUInteger)w tileHeight:(NSUInteger)h itemsToRender: (NSUInteger) c +{ + return [[[self alloc] initWithTileFile:tile tileWidth:w tileHeight:h itemsToRender:c] autorelease]; +} + +-(id) initWithTileFile:(NSString*)tile tileWidth:(NSUInteger)w tileHeight:(NSUInteger)h itemsToRender: (NSUInteger) c +{ + if( (self=[super init]) ) { + + itemWidth_ = w * CC_CONTENT_SCALE_FACTOR(); + itemHeight_ = h * CC_CONTENT_SCALE_FACTOR(); + + opacity_ = 255; + color_ = colorUnmodified_ = ccWHITE; + opacityModifyRGB_ = YES; + + blendFunc_.src = CC_BLEND_SRC; + blendFunc_.dst = CC_BLEND_DST; + + // double retain to avoid the autorelease pool + // also, using: self.textureAtlas supports re-initialization without leaking + self.textureAtlas = [[CCTextureAtlas alloc] initWithFile:tile capacity:c]; + [textureAtlas_ release]; + + if( ! textureAtlas_ ) { + CCLOG(@"cocos2d: Could not initialize CCAtlasNode. Invalid Texture"); + [self release]; + return nil; + } + + [self updateBlendFunc]; + [self updateOpacityModifyRGB]; + + [self calculateMaxItems]; + + } + return self; +} + +-(void) dealloc +{ + [textureAtlas_ release]; + + [super dealloc]; +} + +#pragma mark CCAtlasNode - Atlas generation + +-(void) calculateMaxItems +{ + CGSize s = [[textureAtlas_ texture] contentSizeInPixels]; + itemsPerColumn_ = s.height / itemHeight_; + itemsPerRow_ = s.width / itemWidth_; +} + +-(void) updateAtlasValues +{ + [NSException raise:@"CCAtlasNode:Abstract" format:@"updateAtlasValue not overriden"]; +} + +#pragma mark CCAtlasNode - draw +- (void) 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_TEXTURE_COORD_ARRAY + // Unneeded states: GL_COLOR_ARRAY + glDisableClientState(GL_COLOR_ARRAY); + + glColor4ub( color_.r, color_.g, color_.b, opacity_); + + 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); + + // is this chepear than saving/restoring color state ? + // XXX: There is no need to restore the color to (255,255,255,255). Objects should use the color + // XXX: that they need +// glColor4ub( 255, 255, 255, 255); + + // restore default GL state + glEnableClientState(GL_COLOR_ARRAY); + +} + +#pragma mark CCAtlasNode - RGBA protocol + +- (ccColor3B) color +{ + if(opacityModifyRGB_) + return colorUnmodified_; + + return color_; +} + +-(void) setColor:(ccColor3B)color3 +{ + color_ = colorUnmodified_ = color3; + + if( opacityModifyRGB_ ){ + color_.r = color3.r * opacity_/255; + color_.g = color3.g * opacity_/255; + color_.b = color3.b * opacity_/255; + } +} + +-(GLubyte) opacity +{ + return opacity_; +} + +-(void) setOpacity:(GLubyte) anOpacity +{ + opacity_ = anOpacity; + + // special opacity for premultiplied textures + if( opacityModifyRGB_ ) + [self setColor: colorUnmodified_]; +} + +-(void) setOpacityModifyRGB:(BOOL)modify +{ + ccColor3B oldColor = self.color; + opacityModifyRGB_ = modify; + self.color = oldColor; +} + +-(BOOL) doesOpacityModifyRGB +{ + return opacityModifyRGB_; +} + +-(void) updateOpacityModifyRGB +{ + opacityModifyRGB_ = [textureAtlas_.texture hasPremultipliedAlpha]; +} + +#pragma mark CCAtlasNode - 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]; + [self updateOpacityModifyRGB]; +} + +-(CCTexture2D*) texture +{ + return textureAtlas_.texture; +} + +@end diff --git a/libs/cocos2d/CCBlockSupport.h b/libs/cocos2d/CCBlockSupport.h new file mode 100644 index 0000000..339d5aa --- /dev/null +++ b/libs/cocos2d/CCBlockSupport.h @@ -0,0 +1,51 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2010 Stuart Carnie + * + * 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 + +/** @file + cocos2d blocks support + */ + +// To comply with Apple Objective C runtime (this is defined in NSObjCRuntime.h) +#if !defined(NS_BLOCKS_AVAILABLE) + #if __BLOCKS__ + #define NS_BLOCKS_AVAILABLE 1 + #else + #define NS_BLOCKS_AVAILABLE 0 + #endif +#endif + +#if NS_BLOCKS_AVAILABLE + +@interface NSObject(CCBlocksAdditions) + +- (void)ccCallbackBlock; +- (void)ccCallbackBlockWithSender:(id)sender; + +@end + +#endif // NS_BLOCKS_AVAILABLE diff --git a/libs/cocos2d/CCBlockSupport.m b/libs/cocos2d/CCBlockSupport.m new file mode 100644 index 0000000..9ac99b3 --- /dev/null +++ b/libs/cocos2d/CCBlockSupport.m @@ -0,0 +1,46 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2010 Stuart Carnie + * + * 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 "CCBlockSupport.h" + +#if NS_BLOCKS_AVAILABLE + +@implementation NSObject(CCBlocksAdditions) + +- (void)ccCallbackBlock { + void (^block)(void) = (id)self; + block(); +} + +- (void)ccCallbackBlockWithSender:(id)sender { + void (^block)(id) = (id)self; + block(sender); +} + + +@end + +#endif diff --git a/libs/cocos2d/CCCamera.h b/libs/cocos2d/CCCamera.h new file mode 100644 index 0000000..19a7712 --- /dev/null +++ b/libs/cocos2d/CCCamera.h @@ -0,0 +1,95 @@ +/* + * 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. + */ + + + +#import "CCNode.h" + +/** + A CCCamera is used in every CCNode. + Useful to look at the object from different views. + The OpenGL gluLookAt() function is used to locate the + camera. + + If the object is transformed by any of the scale, rotation or + position attributes, then they will override the camera. + + IMPORTANT: Either your use the camera or the rotation/scale/position properties. You can't use both. + World coordinates won't work if you use the camera. + + Limitations: + + - Some nodes, like CCParallaxNode, CCParticle uses world node coordinates, and they won't work properly if you move them (or any of their ancestors) + using the camera. + + - It doesn't work on batched nodes like CCSprite objects when they are parented to a CCSpriteBatchNode object. + + - It is recommended to use it ONLY if you are going to create 3D effects. For 2D effecs, use the action CCFollow or position/scale/rotate. + +*/ + +@interface CCCamera : NSObject +{ + float eyeX_; + float eyeY_; + float eyeZ_; + + float centerX_; + float centerY_; + float centerZ_; + + float upX_; + float upY_; + float upZ_; + + BOOL dirty_; +} + +/** whether of not the camera is dirty */ +@property (nonatomic,readwrite) BOOL dirty; + +/** returns the Z eye */ ++(float) getZEye; + +/** sets the camera in the defaul position */ +-(void) restore; +/** Sets the camera using gluLookAt using its eye, center and up_vector */ +-(void) locate; +/** sets the eye values in points */ +-(void) setEyeX: (float)x eyeY:(float)y eyeZ:(float)z; +/** sets the center values in points */ +-(void) setCenterX: (float)x centerY:(float)y centerZ:(float)z; +/** sets the up values */ +-(void) setUpX: (float)x upY:(float)y upZ:(float)z; + +/** get the eye vector values in points */ +-(void) eyeX:(float*)x eyeY:(float*)y eyeZ:(float*)z; +/** get the center vector values in points */ +-(void) centerX:(float*)x centerY:(float*)y centerZ:(float*)z; +/** get the up vector values */ +-(void) upX:(float*)x upY:(float*)y upZ:(float*)z; + + +@end diff --git a/libs/cocos2d/CCCamera.m b/libs/cocos2d/CCCamera.m new file mode 100644 index 0000000..1ef6655 --- /dev/null +++ b/libs/cocos2d/CCCamera.m @@ -0,0 +1,131 @@ +/* + * 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. + */ + + +#import "Platforms/CCGL.h" +#import "CCCamera.h" +#import "ccMacros.h" +#import "CCDrawingPrimitives.h" + +@implementation CCCamera + +@synthesize dirty = dirty_; + +-(id) init +{ + if( (self=[super init]) ) + [self restore]; + + return self; +} + +- (NSString*) description +{ + return [NSString stringWithFormat:@"<%@ = %08X | center = (%.2f,%.2f,%.2f)>", [self class], self, centerX_, centerY_, centerZ_]; +} + + +- (void) dealloc +{ + CCLOGINFO(@"cocos2d: deallocing %@", self); + [super dealloc]; +} + +-(void) restore +{ + eyeX_ = eyeY_ = 0; + eyeZ_ = [CCCamera getZEye]; + + centerX_ = centerY_ = centerZ_ = 0; + + upX_ = 0.0f; + upY_ = 1.0f; + upZ_ = 0.0f; + + dirty_ = NO; +} + +-(void) locate +{ + if( dirty_ ) + gluLookAt( eyeX_, eyeY_, eyeZ_, + centerX_, centerY_, centerZ_, + upX_, upY_, upZ_ + ); +} + ++(float) getZEye +{ + return FLT_EPSILON; +// CGSize s = [[CCDirector sharedDirector] displaySize]; +// return ( s.height / 1.1566f ); +} + +-(void) setEyeX: (float)x eyeY:(float)y eyeZ:(float)z +{ + eyeX_ = x * CC_CONTENT_SCALE_FACTOR(); + eyeY_ = y * CC_CONTENT_SCALE_FACTOR(); + eyeZ_ = z * CC_CONTENT_SCALE_FACTOR(); + dirty_ = YES; +} + +-(void) setCenterX: (float)x centerY:(float)y centerZ:(float)z +{ + centerX_ = x * CC_CONTENT_SCALE_FACTOR(); + centerY_ = y * CC_CONTENT_SCALE_FACTOR(); + centerZ_ = z * CC_CONTENT_SCALE_FACTOR(); + dirty_ = YES; +} + +-(void) setUpX: (float)x upY:(float)y upZ:(float)z +{ + upX_ = x; + upY_ = y; + upZ_ = z; + dirty_ = YES; +} + +-(void) eyeX: (float*)x eyeY:(float*)y eyeZ:(float*)z +{ + *x = eyeX_ / CC_CONTENT_SCALE_FACTOR(); + *y = eyeY_ / CC_CONTENT_SCALE_FACTOR(); + *z = eyeZ_ / CC_CONTENT_SCALE_FACTOR(); +} + +-(void) centerX: (float*)x centerY:(float*)y centerZ:(float*)z +{ + *x = centerX_ / CC_CONTENT_SCALE_FACTOR(); + *y = centerY_ / CC_CONTENT_SCALE_FACTOR(); + *z = centerZ_ / CC_CONTENT_SCALE_FACTOR(); +} + +-(void) upX: (float*)x upY:(float*)y upZ:(float*)z +{ + *x = upX_; + *y = upY_; + *z = upZ_; +} + +@end diff --git a/libs/cocos2d/CCConfiguration.h b/libs/cocos2d/CCConfiguration.h new file mode 100644 index 0000000..04e1b55 --- /dev/null +++ b/libs/cocos2d/CCConfiguration.h @@ -0,0 +1,116 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 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 + +#import "Platforms/CCGL.h" + +/** OS version definitions. Includes both iOS and Mac OS versions + */ +enum { + kCCiOSVersion_3_0 = 0x03000000, + kCCiOSVersion_3_1 = 0x03010000, + kCCiOSVersion_3_1_1 = 0x03010100, + kCCiOSVersion_3_1_2 = 0x03010200, + kCCiOSVersion_3_1_3 = 0x03010300, + kCCiOSVersion_3_2 = 0x03020000, + kCCiOSVersion_3_2_1 = 0x03020100, + kCCiOSVersion_4_0 = 0x04000000, + kCCiOSVersion_4_0_1 = 0x04000100, + kCCiOSVersion_4_1 = 0x04010000, + kCCiOSVersion_4_2 = 0x04020000, + kCCiOSVersion_4_3 = 0x04030000, + kCCiOSVersion_4_3_1 = 0x04030100, + kCCiOSVersion_4_3_2 = 0x04030200, + kCCiOSVersion_4_3_3 = 0x04030300, + + kCCMacVersion_10_5 = 0x0a050000, + kCCMacVersion_10_6 = 0x0a060000, + kCCMacVersion_10_7 = 0x0a070000, +}; + +/** + CCConfiguration contains some openGL variables + @since v0.99.0 + */ +@interface CCConfiguration : NSObject { + + GLint maxTextureSize_; + GLint maxModelviewStackDepth_; + BOOL supportsPVRTC_; + BOOL supportsNPOT_; + BOOL supportsBGRA8888_; + BOOL supportsDiscardFramebuffer_; + unsigned int OSVersion_; + GLint maxSamplesAllowed_; +} + +/** OpenGL Max texture size. */ +@property (nonatomic, readonly) GLint maxTextureSize; + +/** OpenGL Max Modelview Stack Depth. */ +@property (nonatomic, readonly) GLint maxModelviewStackDepth; + +/** Whether or not the GPU supports NPOT (Non Power Of Two) textures. + NPOT textures have the following limitations: + - They can't have mipmaps + - They only accept GL_CLAMP_TO_EDGE in GL_TEXTURE_WRAP_{S,T} + + @since v0.99.2 + */ +@property (nonatomic, readonly) BOOL supportsNPOT; + +/** Whether or not PVR Texture Compressed is supported */ +@property (nonatomic, readonly) BOOL supportsPVRTC; + +/** Whether or not BGRA8888 textures are supported. + + @since v0.99.2 + */ +@property (nonatomic, readonly) BOOL supportsBGRA8888; + +/** Whether or not glDiscardFramebufferEXT is supported + + @since v0.99.2 + */ +@property (nonatomic, readonly) BOOL supportsDiscardFramebuffer; + +/** returns the OS version. + - On iOS devices it returns the firmware version. + - On Mac returns the OS version + + @since v0.99.5 + */ +@property (nonatomic, readonly) unsigned int OSVersion; + +/** returns a shared instance of the CCConfiguration */ ++(CCConfiguration *) sharedConfiguration; + +/** returns whether or not an OpenGL is supported */ +- (BOOL) checkForGLExtension:(NSString *)searchName; + + + +@end diff --git a/libs/cocos2d/CCConfiguration.m b/libs/cocos2d/CCConfiguration.m new file mode 100644 index 0000000..d51cd58 --- /dev/null +++ b/libs/cocos2d/CCConfiguration.m @@ -0,0 +1,193 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 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 + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +#import // Needed for UIDevice +#endif + +#import "Platforms/CCGL.h" +#import "CCBlockSupport.h" +#import "CCConfiguration.h" +#import "ccMacros.h" +#import "ccConfig.h" +#import "Support/OpenGL_Internal.h" + +@implementation CCConfiguration + +@synthesize maxTextureSize = maxTextureSize_; +@synthesize supportsPVRTC = supportsPVRTC_; +@synthesize maxModelviewStackDepth = maxModelviewStackDepth_; +@synthesize supportsNPOT = supportsNPOT_; +@synthesize supportsBGRA8888 = supportsBGRA8888_; +@synthesize supportsDiscardFramebuffer = supportsDiscardFramebuffer_; +@synthesize OSVersion = OSVersion_; + +// +// singleton stuff +// +static CCConfiguration *_sharedConfiguration = nil; + +static char * glExtensions; + ++ (CCConfiguration *)sharedConfiguration +{ + if (!_sharedConfiguration) + _sharedConfiguration = [[self alloc] init]; + + return _sharedConfiguration; +} + ++(id)alloc +{ + NSAssert(_sharedConfiguration == nil, @"Attempted to allocate a second instance of a singleton."); + return [super alloc]; +} + + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) +- (NSString*)getMacVersion +{ + SInt32 versionMajor, versionMinor, versionBugFix; + Gestalt(gestaltSystemVersionMajor, &versionMajor); + Gestalt(gestaltSystemVersionMinor, &versionMinor); + Gestalt(gestaltSystemVersionBugFix, &versionBugFix); + + return [NSString stringWithFormat:@"%d.%d.%d", versionMajor, versionMinor, versionBugFix]; +} +#endif // __MAC_OS_X_VERSION_MAX_ALLOWED + +-(id) init +{ + if( (self=[super init])) { + + // Obtain iOS version + OSVersion_ = 0; +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + NSString *OSVer = [[UIDevice currentDevice] systemVersion]; +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) + NSString *OSVer = [self getMacVersion]; +#endif + NSArray *arr = [OSVer componentsSeparatedByString:@"."]; + int idx=0x01000000; + for( NSString *str in arr ) { + int value = [str intValue]; + OSVersion_ += value * idx; + idx = idx >> 8; + } + CCLOG(@"cocos2d: OS version: %@ (0x%08x)", OSVer, OSVersion_); + + CCLOG(@"cocos2d: GL_VENDOR: %s", glGetString(GL_VENDOR) ); + CCLOG(@"cocos2d: GL_RENDERER: %s", glGetString ( GL_RENDERER ) ); + CCLOG(@"cocos2d: GL_VERSION: %s", glGetString ( GL_VERSION ) ); + + glExtensions = (char*) glGetString(GL_EXTENSIONS); + + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize_); + glGetIntegerv(GL_MAX_MODELVIEW_STACK_DEPTH, &maxModelviewStackDepth_); +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + if( OSVersion_ >= kCCiOSVersion_4_0 ) + glGetIntegerv(GL_MAX_SAMPLES_APPLE, &maxSamplesAllowed_); + else + maxSamplesAllowed_ = 0; +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) + glGetIntegerv(GL_MAX_SAMPLES, &maxSamplesAllowed_); +#endif + + supportsPVRTC_ = [self checkForGLExtension:@"GL_IMG_texture_compression_pvrtc"]; +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + supportsNPOT_ = [self checkForGLExtension:@"GL_APPLE_texture_2D_limited_npot"]; +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) + supportsNPOT_ = [self checkForGLExtension:@"GL_ARB_texture_non_power_of_two"]; +#endif + // It seems that somewhere between firmware iOS 3.0 and 4.2 Apple renamed + // GL_IMG_... to GL_APPLE.... So we should check both names + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + BOOL bgra8a = [self checkForGLExtension:@"GL_IMG_texture_format_BGRA8888"]; + BOOL bgra8b = [self checkForGLExtension:@"GL_APPLE_texture_format_BGRA8888"]; + supportsBGRA8888_ = bgra8a | bgra8b; +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) + supportsBGRA8888_ = [self checkForGLExtension:@"GL_EXT_bgra"]; +#endif + + supportsDiscardFramebuffer_ = [self checkForGLExtension:@"GL_EXT_discard_framebuffer"]; + + CCLOG(@"cocos2d: GL_MAX_TEXTURE_SIZE: %d", maxTextureSize_); + CCLOG(@"cocos2d: GL_MAX_MODELVIEW_STACK_DEPTH: %d",maxModelviewStackDepth_); + CCLOG(@"cocos2d: GL_MAX_SAMPLES: %d", maxSamplesAllowed_); + CCLOG(@"cocos2d: GL supports PVRTC: %s", (supportsPVRTC_ ? "YES" : "NO") ); + CCLOG(@"cocos2d: GL supports BGRA8888 textures: %s", (supportsBGRA8888_ ? "YES" : "NO") ); + CCLOG(@"cocos2d: GL supports NPOT textures: %s", (supportsNPOT_ ? "YES" : "NO") ); + CCLOG(@"cocos2d: GL supports discard_framebuffer: %s", (supportsDiscardFramebuffer_ ? "YES" : "NO") ); + CCLOG(@"cocos2d: compiled with NPOT support: %s", +#if CC_TEXTURE_NPOT_SUPPORT + "YES" +#else + "NO" +#endif + ); + CCLOG(@"cocos2d: compiled with VBO support in TextureAtlas : %s", +#if CC_USES_VBO + "YES" +#else + "NO" +#endif + ); + + CCLOG(@"cocos2d: compiled with Affine Matrix transformation in CCNode : %s", +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + "YES" +#else + "NO" +#endif + ); + + CCLOG(@"cocos2d: compiled with Profiling Support: %s", +#if CC_ENABLE_PROFILERS + + "YES - *** Disable it when you finish profiling ***" +#else + "NO" +#endif + ); + + CHECK_GL_ERROR(); + } + + return self; +} + +- (BOOL) checkForGLExtension:(NSString *)searchName +{ + // For best results, extensionsNames should be stored in your renderer so that it does not + // need to be recreated on each invocation. + NSString *extensionsString = [NSString stringWithCString:glExtensions encoding: NSASCIIStringEncoding]; + NSArray *extensionsNames = [extensionsString componentsSeparatedByString:@" "]; + return [extensionsNames containsObject: searchName]; +} +@end diff --git a/libs/cocos2d/CCDirector.h b/libs/cocos2d/CCDirector.h new file mode 100644 index 0000000..15d60e5 --- /dev/null +++ b/libs/cocos2d/CCDirector.h @@ -0,0 +1,305 @@ +/* + * 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. + */ + + +#import "ccConfig.h" +#import "ccTypes.h" + +// OpenGL related +#import "Platforms/CCGL.h" +#import "CCProtocols.h" + +/** @typedef ccDirectorProjection + Possible OpenGL projections used by director + */ +typedef enum { + /// sets a 2D projection (orthogonal projection). + kCCDirectorProjection2D, + + /// sets a 3D projection with a fovy=60, znear=0.5f and zfar=1500. + kCCDirectorProjection3D, + + /// it calls "updateProjection" on the projection delegate. + kCCDirectorProjectionCustom, + + /// Detault projection is 3D projection + kCCDirectorProjectionDefault = kCCDirectorProjection3D, + + // backward compatibility stuff + CCDirectorProjection2D = kCCDirectorProjection2D, + CCDirectorProjection3D = kCCDirectorProjection3D, + CCDirectorProjectionCustom = kCCDirectorProjectionCustom, + +} ccDirectorProjection; + + +@class CCLabelAtlas; +@class CCScene; + +/**Class that creates and handle the main Window and manages how +and when to execute the Scenes. + + The CCDirector is also resposible for: + - initializing the OpenGL ES context + - setting the OpenGL pixel format (default on is RGB565) + - setting the OpenGL buffer depth (default one is 0-bit) + - setting the projection (default one is 3D) + - setting the orientation (default one is Protrait) + + Since the CCDirector is a singleton, the standard way to use it is by calling: + - [[CCDirector sharedDirector] methodName]; + + The CCDirector also sets the default OpenGL context: + - GL_TEXTURE_2D is enabled + - GL_VERTEX_ARRAY is enabled + - GL_COLOR_ARRAY is enabled + - GL_TEXTURE_COORD_ARRAY is enabled +*/ +@interface CCDirector : NSObject +{ + CC_GLVIEW *openGLView_; + + // internal timer + NSTimeInterval animationInterval_; + NSTimeInterval oldAnimationInterval_; + + /* display FPS ? */ + BOOL displayFPS_; + + NSUInteger frames_; + ccTime accumDt_; + ccTime frameRate_; +#if CC_DIRECTOR_FAST_FPS + CCLabelAtlas *FPSLabel_; +#endif + + /* is the running scene paused */ + BOOL isPaused_; + + /* The running scene */ + CCScene *runningScene_; + + /* This object will be visited after the scene. Useful to hook a notification node */ + id notificationNode_; + + /* will be the next 'runningScene' in the next frame + nextScene is a weak reference. */ + CCScene *nextScene_; + + /* If YES, then "old" scene will receive the cleanup message */ + BOOL sendCleanupToScene_; + + /* scheduled scenes */ + NSMutableArray *scenesStack_; + + /* last time the main loop was updated */ + struct timeval lastUpdate_; + /* delta time since last tick to main loop */ + ccTime dt; + /* whether or not the next delta time will be zero */ + BOOL nextDeltaTimeZero_; + + /* projection used */ + ccDirectorProjection projection_; + + /* Projection protocol delegate */ + id projectionDelegate_; + + /* window size in points */ + CGSize winSizeInPoints_; + + /* window size in pixels */ + CGSize winSizeInPixels_; + + /* the cocos2d running thread */ + NSThread *runningThread_; + + // profiler +#if CC_ENABLE_PROFILERS + ccTime accumDtForProfiler_; +#endif +} + +/** returns the cocos2d thread. + If you want to run any cocos2d task, run it in this thread. + On iOS usually it is the main thread. + @since v0.99.5 + */ +@property (readonly, nonatomic ) NSThread *runningThread; +/** The current running Scene. Director can only run one Scene at the time */ +@property (nonatomic,readonly) CCScene* runningScene; +/** The FPS value */ +@property (nonatomic,readwrite, assign) NSTimeInterval animationInterval; +/** Whether or not to display the FPS on the bottom-left corner */ +@property (nonatomic,readwrite, assign) BOOL displayFPS; +/** The OpenGLView, where everything is rendered */ +@property (nonatomic,readwrite,retain) CC_GLVIEW *openGLView; +/** whether or not the next delta time will be zero */ +@property (nonatomic,readwrite,assign) BOOL nextDeltaTimeZero; +/** Whether or not the Director is paused */ +@property (nonatomic,readonly) BOOL isPaused; +/** Sets an OpenGL projection + @since v0.8.2 + */ +@property (nonatomic,readwrite) ccDirectorProjection projection; + +/** Whether or not the replaced scene will receive the cleanup message. + If the new scene is pushed, then the old scene won't receive the "cleanup" message. + If the new scene replaces the old one, the it will receive the "cleanup" message. + @since v0.99.0 + */ +@property (nonatomic, readonly) BOOL sendCleanupToScene; + +/** This object will be visited after the main scene is visited. + This object MUST implement the "visit" selector. + Useful to hook a notification object, like CCNotifications (http://github.com/manucorporat/CCNotifications) + @since v0.99.5 + */ +@property (nonatomic, readwrite, retain) id notificationNode; + +/** This object will be called when the OpenGL projection is udpated and only when the kCCDirectorProjectionCustom projection is used. + @since v0.99.5 + */ +@property (nonatomic, readwrite, retain) id projectionDelegate; + +/** returns a shared instance of the director */ ++(CCDirector *)sharedDirector; + + + +// Window size + +/** returns the size of the OpenGL view in points. + It takes into account any possible rotation (device orientation) of the window + */ +- (CGSize) winSize; + +/** returns the size of the OpenGL view in pixels. + It takes into account any possible rotation (device orientation) of the window. + On Mac winSize and winSizeInPixels return the same value. + */ +- (CGSize) winSizeInPixels; +/** returns the display size of the OpenGL view in pixels. + It doesn't take into account any possible rotation of the window. + */ +-(CGSize) displaySizeInPixels; +/** changes the projection size */ +-(void) reshapeProjection:(CGSize)newWindowSize; + +/** converts a UIKit coordinate to an OpenGL coordinate + Useful to convert (multi) touchs coordinates to the current layout (portrait or landscape) + */ +-(CGPoint) convertToGL: (CGPoint) p; +/** converts an OpenGL coordinate to a UIKit coordinate + Useful to convert node points to window points for calls such as glScissor + */ +-(CGPoint) convertToUI:(CGPoint)p; + +/// XXX: missing description +-(float) getZEye; + +// Scene Management + +/**Enters the Director's main loop with the given Scene. + * Call it to run only your FIRST scene. + * Don't call it if there is already a running scene. + */ +- (void) runWithScene:(CCScene*) scene; + +/**Suspends the execution of the running scene, pushing it on the stack of suspended scenes. + * The new scene will be executed. + * Try to avoid big stacks of pushed scenes to reduce memory allocation. + * ONLY call it if there is a running scene. + */ +- (void) pushScene:(CCScene*) scene; + +/**Pops out a scene from the queue. + * This scene will replace the running one. + * The running scene will be deleted. If there are no more scenes in the stack the execution is terminated. + * ONLY call it if there is a running scene. + */ +- (void) popScene; + +/** Replaces the running scene with a new one. The running scene is terminated. + * ONLY call it if there is a running scene. + */ +-(void) replaceScene: (CCScene*) scene; + +/** Ends the execution, releases the running scene. + It doesn't remove the OpenGL view from its parent. You have to do it manually. + */ +-(void) end; + +/** Pauses the running scene. + The running scene will be _drawed_ but all scheduled timers will be paused + While paused, the draw rate will be 4 FPS to reduce CPU consuption + */ +-(void) pause; + +/** Resumes the paused scene + The scheduled timers will be activated again. + The "delta time" will be 0 (as if the game wasn't paused) + */ +-(void) resume; + +/** Stops the animation. Nothing will be drawn. The main loop won't be triggered anymore. + If you wan't to pause your animation call [pause] instead. + */ +-(void) stopAnimation; + +/** The main loop is triggered again. + Call this function only if [stopAnimation] was called earlier + @warning Dont' call this function to start the main loop. To run the main loop call runWithScene + */ +-(void) startAnimation; + +/** Draw the scene. + This method is called every frame. Don't call it manually. + */ +-(void) drawScene; + +// Memory Helper + +/** Removes all the cocos2d data that was cached automatically. + It will purge the CCTextureCache, CCBitmapFont cache. + IMPORTANT: The CCSpriteFrameCache won't be purged. If you want to purge it, you have to purge it manually. + @since v0.99.3 + */ +-(void) purgeCachedData; + +// OpenGL Helper + +/** sets the OpenGL default values */ +-(void) setGLDefaultValues; + +/** enables/disables OpenGL alpha blending */ +- (void) setAlphaBlending: (BOOL) on; +/** enables/disables OpenGL depth test */ +- (void) setDepthTest: (BOOL) on; + +// Profiler +-(void) showProfilers; + +@end diff --git a/libs/cocos2d/CCDirector.m b/libs/cocos2d/CCDirector.m new file mode 100644 index 0000000..427620d --- /dev/null +++ b/libs/cocos2d/CCDirector.m @@ -0,0 +1,564 @@ +/* + * 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. + */ + + +/* Idea of decoupling Window from Director taken from OC3D project: http://code.google.com/p/oc3d/ + */ + +#import +#import + +// cocos2d imports +#import "CCDirector.h" +#import "CCScheduler.h" +#import "CCActionManager.h" +#import "CCTextureCache.h" +#import "CCAnimationCache.h" +#import "CCLabelAtlas.h" +#import "ccMacros.h" +#import "CCTransition.h" +#import "CCScene.h" +#import "CCSpriteFrameCache.h" +#import "CCTexture2D.h" +#import "CCLabelBMFont.h" +#import "CCLayer.h" + +// support imports +#import "Platforms/CCGL.h" +#import "Platforms/CCNS.h" + +#import "Support/OpenGL_Internal.h" +#import "Support/CGPointExtension.h" + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +#import "Platforms/iOS/CCDirectorIOS.h" +#define CC_DIRECTOR_DEFAULT CCDirectorTimer +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) +#import "Platforms/Mac/CCDirectorMac.h" +#define CC_DIRECTOR_DEFAULT CCDirectorDisplayLink +#endif + +#import "Support/CCProfiling.h" + +#define kDefaultFPS 60.0 // 60 frames per second + +extern NSString * cocos2dVersion(void); + + +@interface CCDirector (Private) +-(void) setNextScene; +// shows the FPS in the screen +-(void) showFPS; +// calculates delta time since last time it was called +-(void) calculateDeltaTime; +@end + +@implementation CCDirector + +@synthesize animationInterval = animationInterval_; +@synthesize runningScene = runningScene_; +@synthesize displayFPS = displayFPS_; +@synthesize nextDeltaTimeZero = nextDeltaTimeZero_; +@synthesize isPaused = isPaused_; +@synthesize sendCleanupToScene = sendCleanupToScene_; +@synthesize runningThread = runningThread_; +@synthesize notificationNode = notificationNode_; +@synthesize projectionDelegate = projectionDelegate_; +// +// singleton stuff +// +static CCDirector *_sharedDirector = nil; + ++ (CCDirector *)sharedDirector +{ + if (!_sharedDirector) { + + // + // Default Director is TimerDirector + // + if( [ [CCDirector class] isEqual:[self class]] ) + _sharedDirector = [[CC_DIRECTOR_DEFAULT alloc] init]; + else + _sharedDirector = [[self alloc] init]; + } + + return _sharedDirector; +} + ++(id)alloc +{ + NSAssert(_sharedDirector == nil, @"Attempted to allocate a second instance of a singleton."); + return [super alloc]; +} + +- (id) init +{ + CCLOG(@"cocos2d: %@", cocos2dVersion() ); + + if( (self=[super init]) ) { + + CCLOG(@"cocos2d: Using Director Type:%@", [self class]); + + // scenes + runningScene_ = nil; + nextScene_ = nil; + + notificationNode_ = nil; + + oldAnimationInterval_ = animationInterval_ = 1.0 / kDefaultFPS; + scenesStack_ = [[NSMutableArray alloc] initWithCapacity:10]; + + // Set default projection (3D) + projection_ = kCCDirectorProjectionDefault; + + // projection delegate if "Custom" projection is used + projectionDelegate_ = nil; + + // FPS + displayFPS_ = NO; + frames_ = 0; + + // paused ? + isPaused_ = NO; + + // running thread + runningThread_ = nil; + + winSizeInPixels_ = winSizeInPoints_ = CGSizeZero; + } + + return self; +} + +- (void) dealloc +{ + CCLOGINFO(@"cocos2d: deallocing %@", self); + +#if CC_DIRECTOR_FAST_FPS + [FPSLabel_ release]; +#endif + [runningScene_ release]; + [notificationNode_ release]; + [scenesStack_ release]; + + [projectionDelegate_ release]; + + _sharedDirector = nil; + + [super dealloc]; +} + +-(void) setGLDefaultValues +{ + // This method SHOULD be called only after openGLView_ was initialized + NSAssert( openGLView_, @"openGLView_ must be initialized"); + + [self setAlphaBlending: YES]; + [self setDepthTest: YES]; + [self setProjection: projection_]; + + // set other opengl default values + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + +#if CC_DIRECTOR_FAST_FPS + if (!FPSLabel_) { + CCTexture2DPixelFormat currentFormat = [CCTexture2D defaultAlphaPixelFormat]; + [CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGBA4444]; + FPSLabel_ = [[CCLabelAtlas labelWithString:@"00.0" charMapFile:@"fps_images.png" itemWidth:16 itemHeight:24 startCharMap:'.'] retain]; + [CCTexture2D setDefaultAlphaPixelFormat:currentFormat]; + } +#endif // CC_DIRECTOR_FAST_FPS +} + +// +// Draw the Scene +// +- (void) drawScene +{ + // Override me +} + +-(void) calculateDeltaTime +{ + struct timeval now; + + if( gettimeofday( &now, NULL) != 0 ) { + CCLOG(@"cocos2d: error in gettimeofday"); + dt = 0; + return; + } + + // new delta time + if( nextDeltaTimeZero_ ) { + dt = 0; + nextDeltaTimeZero_ = NO; + } else { + dt = (now.tv_sec - lastUpdate_.tv_sec) + (now.tv_usec - lastUpdate_.tv_usec) / 1000000.0f; + dt = MAX(0,dt); + } + +#ifdef DEBUG + // If we are debugging our code, prevent big delta time + if( dt > 0.2f ) + dt = 1/60.0f; +#endif + + lastUpdate_ = now; +} + +#pragma mark Director - Memory Helper + +-(void) purgeCachedData +{ + [CCLabelBMFont purgeCachedData]; + [[CCTextureCache sharedTextureCache] removeUnusedTextures]; +} + +#pragma mark Director - Scene OpenGL Helper + +-(ccDirectorProjection) projection +{ + return projection_; +} + +-(float) getZEye +{ + return ( winSizeInPixels_.height / 1.1566f ); +} + +-(void) setProjection:(ccDirectorProjection)projection +{ + CCLOG(@"cocos2d: override me"); +} + +- (void) setAlphaBlending: (BOOL) on +{ + if (on) { + glEnable(GL_BLEND); + glBlendFunc(CC_BLEND_SRC, CC_BLEND_DST); + + } else + glDisable(GL_BLEND); +} + +- (void) setDepthTest: (BOOL) on +{ + if (on) { + ccglClearDepth(1.0f); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); +// glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + } else + glDisable( GL_DEPTH_TEST ); +} + +#pragma mark Director Integration with a UIKit view + +-(CC_GLVIEW*) openGLView +{ + return openGLView_; +} + +-(void) setOpenGLView:(CC_GLVIEW *)view +{ + NSAssert( view, @"OpenGLView must be non-nil"); + + if( view != openGLView_ ) { + [openGLView_ release]; + openGLView_ = [view retain]; + + // set size + winSizeInPixels_ = winSizeInPoints_ = CCNSSizeToCGSize( [view bounds].size ); + + [self setGLDefaultValues]; + } +} + +#pragma mark Director Scene Landscape + +-(CGPoint)convertToGL:(CGPoint)uiPoint +{ + CCLOG(@"CCDirector#convertToGL: OVERRIDE ME."); + return CGPointZero; +} + +-(CGPoint)convertToUI:(CGPoint)glPoint +{ + CCLOG(@"CCDirector#convertToUI: OVERRIDE ME."); + return CGPointZero; +} + +-(CGSize)winSize +{ + return winSizeInPoints_; +} + +-(CGSize)winSizeInPixels +{ + return winSizeInPixels_; +} + +-(CGSize)displaySizeInPixels +{ + return winSizeInPixels_; +} + +-(void) reshapeProjection:(CGSize)newWindowSize +{ + winSizeInPixels_ = winSizeInPoints_ = newWindowSize; + [self setProjection:projection_]; +} + +#pragma mark Director Scene Management + +- (void)runWithScene:(CCScene*) scene +{ + NSAssert( scene != nil, @"Argument must be non-nil"); + NSAssert( runningScene_ == nil, @"You can't run an scene if another Scene is running. Use replaceScene or pushScene instead"); + + [self pushScene:scene]; + [self startAnimation]; +} + +-(void) replaceScene: (CCScene*) scene +{ + NSAssert( scene != nil, @"Argument must be non-nil"); + + NSUInteger index = [scenesStack_ count]; + + sendCleanupToScene_ = YES; + [scenesStack_ replaceObjectAtIndex:index-1 withObject:scene]; + nextScene_ = scene; // nextScene_ is a weak ref +} + +- (void) pushScene: (CCScene*) scene +{ + NSAssert( scene != nil, @"Argument must be non-nil"); + + sendCleanupToScene_ = NO; + + [scenesStack_ addObject: scene]; + nextScene_ = scene; // nextScene_ is a weak ref +} + +-(void) popScene +{ + NSAssert( runningScene_ != nil, @"A running Scene is needed"); + + [scenesStack_ removeLastObject]; + NSUInteger c = [scenesStack_ count]; + + if( c == 0 ) + [self end]; + else { + sendCleanupToScene_ = YES; + nextScene_ = [scenesStack_ objectAtIndex:c-1]; + } +} + +-(void) end +{ + [runningScene_ onExit]; + [runningScene_ cleanup]; + [runningScene_ release]; + + runningScene_ = nil; + nextScene_ = nil; + + // remove all objects, but don't release it. + // runWithScene might be executed after 'end'. + [scenesStack_ removeAllObjects]; + + [self stopAnimation]; + +#if CC_DIRECTOR_FAST_FPS + [FPSLabel_ release]; + FPSLabel_ = nil; +#endif + + [projectionDelegate_ release]; + projectionDelegate_ = nil; + + // Purge bitmap cache + [CCLabelBMFont purgeCachedData]; + + // Purge all managers + [CCAnimationCache purgeSharedAnimationCache]; + [CCSpriteFrameCache purgeSharedSpriteFrameCache]; + [CCScheduler purgeSharedScheduler]; + [CCActionManager purgeSharedManager]; + [CCTextureCache purgeSharedTextureCache]; + + + // OpenGL view + + // Since the director doesn't attach the openglview to the window + // it shouldn't remove it from the window too. +// [openGLView_ removeFromSuperview]; + + [openGLView_ release]; + openGLView_ = nil; +} + +-(void) setNextScene +{ + Class transClass = [CCTransitionScene class]; + BOOL runningIsTransition = [runningScene_ isKindOfClass:transClass]; + BOOL newIsTransition = [nextScene_ isKindOfClass:transClass]; + + // If it is not a transition, call onExit/cleanup + if( ! newIsTransition ) { + [runningScene_ onExit]; + + // issue #709. the root node (scene) should receive the cleanup message too + // otherwise it might be leaked. + if( sendCleanupToScene_) + [runningScene_ cleanup]; + } + + [runningScene_ release]; + + runningScene_ = [nextScene_ retain]; + nextScene_ = nil; + + if( ! runningIsTransition ) { + [runningScene_ onEnter]; + [runningScene_ onEnterTransitionDidFinish]; + } +} + +-(void) pause +{ + if( isPaused_ ) + return; + + oldAnimationInterval_ = animationInterval_; + + // when paused, don't consume CPU + [self setAnimationInterval:1/4.0]; + isPaused_ = YES; +} + +-(void) resume +{ + if( ! isPaused_ ) + return; + + [self setAnimationInterval: oldAnimationInterval_]; + + if( gettimeofday( &lastUpdate_, NULL) != 0 ) { + CCLOG(@"cocos2d: Director: Error in gettimeofday"); + } + + isPaused_ = NO; + dt = 0; +} + +- (void)startAnimation +{ + CCLOG(@"cocos2d: Director#startAnimation. Override me"); +} + +- (void)stopAnimation +{ + CCLOG(@"cocos2d: Director#stopAnimation. Override me"); +} + +- (void)setAnimationInterval:(NSTimeInterval)interval +{ + CCLOG(@"cocos2d: Director#setAnimationInterval. Override me"); +} + +#if CC_DIRECTOR_FAST_FPS + +// display the FPS using a LabelAtlas +// updates the FPS every frame +-(void) showFPS +{ + frames_++; + accumDt_ += dt; + + if ( accumDt_ > CC_DIRECTOR_FPS_INTERVAL) { + frameRate_ = frames_/accumDt_; + frames_ = 0; + accumDt_ = 0; + +// sprintf(format,"%.1f",frameRate); +// [FPSLabel setCString:format]; + + NSString *str = [[NSString alloc] initWithFormat:@"%.1f", frameRate_]; + [FPSLabel_ setString:str]; + [str release]; + } + + [FPSLabel_ draw]; +} +#else +// display the FPS using a manually generated Texture (very slow) +// updates the FPS 3 times per second aprox. +-(void) showFPS +{ + frames_++; + accumDt_ += dt; + + if ( accumDt_ > CC_DIRECTOR_FPS_INTERVAL) { + frameRate_ = frames_/accumDt_; + frames_ = 0; + accumDt_ = 0; + } + + NSString *str = [NSString stringWithFormat:@"%.2f",frameRate_]; + CCTexture2D *texture = [[CCTexture2D alloc] initWithString:str dimensions:CGSizeMake(100,30) alignment:CCTextAlignmentLeft fontName:@"Arial" fontSize:24]; + + // 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_TEXTURE_COORD_ARRAY + // Unneeded states: GL_COLOR_ARRAY + glDisableClientState(GL_COLOR_ARRAY); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glColor4ub(224,224,244,200); + [texture drawAtPoint: ccp(5,2)]; + [texture release]; + + glBlendFunc(CC_BLEND_SRC, CC_BLEND_DST); + + // restore default GL state + glEnableClientState(GL_COLOR_ARRAY); +} +#endif + +- (void) showProfilers { +#if CC_ENABLE_PROFILERS + accumDtForProfiler_ += dt; + if (accumDtForProfiler_ > 1.0f) { + accumDtForProfiler_ = 0; + [[CCProfiler sharedProfiler] displayTimers]; + } +#endif // CC_ENABLE_PROFILERS +} + +@end + diff --git a/libs/cocos2d/CCDrawingPrimitives.h b/libs/cocos2d/CCDrawingPrimitives.h new file mode 100644 index 0000000..8d1dbe5 --- /dev/null +++ b/libs/cocos2d/CCDrawingPrimitives.h @@ -0,0 +1,92 @@ +/* + * 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. + */ + + +#ifndef __CC_DRAWING_PRIMITIVES_H +#define __CC_DRAWING_PRIMITIVES_H + +#import +#import + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +#import // for CGPoint +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +/** + @file + Drawing OpenGL ES primitives. + - ccDrawPoint + - ccDrawLine + - ccDrawPoly + - ccDrawCircle + - ccDrawQuadBezier + - ccDrawCubicBezier + + You can change the color, width and other property by calling the + glColor4ub(), glLineWidth(), glPointSize(). + + @warning These functions draws the Line, Point, Polygon, immediately. They aren't batched. If you are going to make a game that depends on these primitives, I suggest creating a batch. + */ + + +/** draws a point given x and y coordinate measured in points. */ +void ccDrawPoint( CGPoint point ); + +/** draws an array of points. + @since v0.7.2 + */ +void ccDrawPoints( const CGPoint *points, NSUInteger numberOfPoints ); + +/** draws a line given the origin and destination point measured in points. */ +void ccDrawLine( CGPoint origin, CGPoint destination ); + +/** draws a poligon given a pointer to CGPoint coordiantes and the number of vertices measured in points. + The polygon can be closed or open + */ +void ccDrawPoly( const CGPoint *vertices, NSUInteger numOfVertices, BOOL closePolygon ); + +/** draws a circle given the center, radius and number of segments measured in points */ +void ccDrawCircle( CGPoint center, float radius, float angle, NSUInteger segments, BOOL drawLineToCenter); + +/** draws a quad bezier path measured in points. + @since v0.8 + */ +void ccDrawQuadBezier(CGPoint origin, CGPoint control, CGPoint destination, NSUInteger segments); + +/** draws a cubic bezier path measured in points. + @since v0.8 + */ +void ccDrawCubicBezier(CGPoint origin, CGPoint control1, CGPoint control2, CGPoint destination, NSUInteger segments); + +#ifdef __cplusplus +} +#endif + +#endif // __CC_DRAWING_PRIMITIVES_H diff --git a/libs/cocos2d/CCDrawingPrimitives.m b/libs/cocos2d/CCDrawingPrimitives.m new file mode 100644 index 0000000..f7df2b6 --- /dev/null +++ b/libs/cocos2d/CCDrawingPrimitives.m @@ -0,0 +1,272 @@ +/* + * 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. + */ + +#import +#import +#import + +#import "CCDrawingPrimitives.h" +#import "ccTypes.h" +#import "ccMacros.h" +#import "Platforms/CCGL.h" + +void ccDrawPoint( CGPoint point ) +{ + ccVertex2F p = (ccVertex2F) {point.x * CC_CONTENT_SCALE_FACTOR(), point.y * CC_CONTENT_SCALE_FACTOR() }; + + // Default GL states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY + // Needed states: GL_VERTEX_ARRAY, + // Unneeded states: GL_TEXTURE_2D, GL_TEXTURE_COORD_ARRAY, GL_COLOR_ARRAY + glDisable(GL_TEXTURE_2D); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + glVertexPointer(2, GL_FLOAT, 0, &p); + glDrawArrays(GL_POINTS, 0, 1); + + // restore default state + glEnableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnable(GL_TEXTURE_2D); +} + +void ccDrawPoints( const CGPoint *points, NSUInteger numberOfPoints ) +{ + // Default GL states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY + // Needed states: GL_VERTEX_ARRAY, + // Unneeded states: GL_TEXTURE_2D, GL_TEXTURE_COORD_ARRAY, GL_COLOR_ARRAY + glDisable(GL_TEXTURE_2D); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + ccVertex2F newPoints[numberOfPoints]; + + // iPhone and 32-bit machines optimization + if( sizeof(CGPoint) == sizeof(ccVertex2F) ) { + + // points ? + if( CC_CONTENT_SCALE_FACTOR() != 1 ) { + for( NSUInteger i=0; i + +@class CCTexture2D; + +/** FBO class that grabs the the contents of the screen */ +@interface CCGrabber : NSObject +{ + GLuint fbo; + GLint oldFBO; +} + +-(void)grab:(CCTexture2D*)texture; +-(void)beforeRender:(CCTexture2D*)texture; +-(void)afterRender:(CCTexture2D*)texture; + +@end diff --git a/libs/cocos2d/CCGrabber.m b/libs/cocos2d/CCGrabber.m new file mode 100644 index 0000000..a259091 --- /dev/null +++ b/libs/cocos2d/CCGrabber.m @@ -0,0 +1,95 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2009 On-Core + * + * 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 "Platforms/CCGL.h" +#import "CCGrabber.h" +#import "ccMacros.h" +#import "CCTexture2D.h" +#import "Support/OpenGL_Internal.h" + +@implementation CCGrabber + +-(id) init +{ + if(( self = [super init] )) { + // generate FBO + ccglGenFramebuffers(1, &fbo); + } + return self; +} + +-(void)grab:(CCTexture2D*)texture +{ + glGetIntegerv(CC_GL_FRAMEBUFFER_BINDING, &oldFBO); + + // bind + ccglBindFramebuffer(CC_GL_FRAMEBUFFER, fbo); + + // associate texture with FBO + ccglFramebufferTexture2D(CC_GL_FRAMEBUFFER, CC_GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.name, 0); + + // check if it worked (probably worth doing :) ) + GLuint status = ccglCheckFramebufferStatus(CC_GL_FRAMEBUFFER); + if (status != CC_GL_FRAMEBUFFER_COMPLETE) + [NSException raise:@"Frame Grabber" format:@"Could not attach texture to framebuffer"]; + + ccglBindFramebuffer(CC_GL_FRAMEBUFFER, oldFBO); +} + +-(void)beforeRender:(CCTexture2D*)texture +{ + glGetIntegerv(CC_GL_FRAMEBUFFER_BINDING, &oldFBO); + ccglBindFramebuffer(CC_GL_FRAMEBUFFER, fbo); + + // BUG XXX: doesn't work with RGB565. + + + glClearColor(0,0,0,0); + + // BUG #631: To fix #631, uncomment the lines with #631 + // Warning: But it CCGrabber won't work with 2 effects at the same time +// glClearColor(0.0f,0.0f,0.0f,1.0f); // #631 + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + +// glColorMask(TRUE, TRUE, TRUE, FALSE); // #631 + +} + +-(void)afterRender:(CCTexture2D*)texture +{ + ccglBindFramebuffer(CC_GL_FRAMEBUFFER, oldFBO); +// glColorMask(TRUE, TRUE, TRUE, TRUE); // #631 +} + +- (void) dealloc +{ + CCLOGINFO(@"cocos2d: deallocing %@", self); + ccglDeleteFramebuffers(1, &fbo); + [super dealloc]; +} + +@end diff --git a/libs/cocos2d/CCGrid.h b/libs/cocos2d/CCGrid.h new file mode 100644 index 0000000..e5e77e8 --- /dev/null +++ b/libs/cocos2d/CCGrid.h @@ -0,0 +1,121 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2009 On-Core + * + * 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 + +#import "CCNode.h" +#import "CCCamera.h" +#import "ccTypes.h" + +@class CCTexture2D; +@class CCGrabber; + +/** Base class for other + */ +@interface CCGridBase : NSObject +{ + BOOL active_; + int reuseGrid_; + ccGridSize gridSize_; + CCTexture2D *texture_; + CGPoint step_; + CCGrabber *grabber_; + BOOL isTextureFlipped_; +} + +/** wheter or not the grid is active */ +@property (nonatomic,readwrite) BOOL active; +/** number of times that the grid will be reused */ +@property (nonatomic,readwrite) int reuseGrid; +/** size of the grid */ +@property (nonatomic,readonly) ccGridSize gridSize; +/** pixels between the grids */ +@property (nonatomic,readwrite) CGPoint step; +/** texture used */ +@property (nonatomic, retain) CCTexture2D *texture; +/** grabber used */ +@property (nonatomic, retain) CCGrabber *grabber; +/** is texture flipped */ +@property (nonatomic, readwrite) BOOL isTextureFlipped; + ++(id) gridWithSize:(ccGridSize)gridSize texture:(CCTexture2D*)texture flippedTexture:(BOOL)flipped; ++(id) gridWithSize:(ccGridSize)gridSize; + +-(id) initWithSize:(ccGridSize)gridSize texture:(CCTexture2D*)texture flippedTexture:(BOOL)flipped; +-(id)initWithSize:(ccGridSize)gridSize; +-(void)beforeDraw; +-(void)afterDraw:(CCNode*)target; +-(void)blit; +-(void)reuse; + +-(void)calculateVertexPoints; + +@end + +//////////////////////////////////////////////////////////// + +/** + CCGrid3D is a 3D grid implementation. Each vertex has 3 dimensions: x,y,z + */ +@interface CCGrid3D : CCGridBase +{ + GLvoid *texCoordinates; + GLvoid *vertices; + GLvoid *originalVertices; + GLushort *indices; +} + +/** returns the vertex at a given position */ +-(ccVertex3F)vertex:(ccGridSize)pos; +/** returns the original (non-transformed) vertex at a given position */ +-(ccVertex3F)originalVertex:(ccGridSize)pos; +/** sets a new vertex at a given position */ +-(void)setVertex:(ccGridSize)pos vertex:(ccVertex3F)vertex; + +@end + +//////////////////////////////////////////////////////////// + +/** + CCTiledGrid3D is a 3D grid implementation. It differs from Grid3D in that + the tiles can be separated from the grid. +*/ +@interface CCTiledGrid3D : CCGridBase +{ + GLvoid *texCoordinates; + GLvoid *vertices; + GLvoid *originalVertices; + GLushort *indices; +} + +/** returns the tile at the given position */ +-(ccQuad3)tile:(ccGridSize)pos; +/** returns the original tile (untransformed) at the given position */ +-(ccQuad3)originalTile:(ccGridSize)pos; +/** sets a new tile */ +-(void)setTile:(ccGridSize)pos coords:(ccQuad3)coords; + +@end diff --git a/libs/cocos2d/CCGrid.m b/libs/cocos2d/CCGrid.m new file mode 100644 index 0000000..c2ed19d --- /dev/null +++ b/libs/cocos2d/CCGrid.m @@ -0,0 +1,571 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2009 On-Core + * + * 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 + +#import "ccMacros.h" +#import "CCGrid.h" +#import "CCTexture2D.h" +#import "CCDirector.h" +#import "CCGrabber.h" + +#import "Platforms/CCGL.h" +#import "Support/CGPointExtension.h" +#import "Support/ccUtils.h" + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +#import "Platforms/iOS/CCDirectorIOS.h" +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED + +#pragma mark - +#pragma mark CCGridBase + +@implementation CCGridBase + +@synthesize reuseGrid = reuseGrid_; +@synthesize texture = texture_; +@synthesize grabber = grabber_; +@synthesize gridSize = gridSize_; +@synthesize step = step_; + ++(id) gridWithSize:(ccGridSize)gridSize texture:(CCTexture2D*)texture flippedTexture:(BOOL)flipped +{ + return [[[self alloc] initWithSize:gridSize texture:texture flippedTexture:flipped] autorelease]; +} + ++(id) gridWithSize:(ccGridSize)gridSize +{ + return [[(CCGridBase*)[self alloc] initWithSize:gridSize] autorelease]; +} + +-(id) initWithSize:(ccGridSize)gridSize texture:(CCTexture2D*)texture flippedTexture:(BOOL)flipped +{ + if( (self=[super init]) ) { + + active_ = NO; + reuseGrid_ = 0; + gridSize_ = gridSize; + + self.texture = texture; + isTextureFlipped_ = flipped; + + CGSize texSize = [texture_ contentSizeInPixels]; + step_.x = texSize.width / gridSize_.x; + step_.y = texSize.height / gridSize_.y; + + grabber_ = [[CCGrabber alloc] init]; + [grabber_ grab:texture_]; + + [self calculateVertexPoints]; + } + return self; +} + +-(id)initWithSize:(ccGridSize)gSize +{ + CCDirector *director = [CCDirector sharedDirector]; + CGSize s = [director winSizeInPixels]; + + unsigned long POTWide = ccNextPOT(s.width); + unsigned long POTHigh = ccNextPOT(s.height); + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + EAGLView *glview = [[CCDirector sharedDirector] openGLView]; + NSString *pixelFormat = [glview pixelFormat]; + + CCTexture2DPixelFormat format = [pixelFormat isEqualToString: kEAGLColorFormatRGB565] ? kCCTexture2DPixelFormat_RGB565 : kCCTexture2DPixelFormat_RGBA8888; +#else + CCTexture2DPixelFormat format = kCCTexture2DPixelFormat_RGBA8888; +#endif + + void *data = calloc((int)(POTWide * POTHigh * 4), 1); + if( ! data ) { + CCLOG(@"cocos2d: CCGrid: not enough memory"); + [self release]; + return nil; + } + + CCTexture2D *texture = [[CCTexture2D alloc] initWithData:data pixelFormat:format pixelsWide:POTWide pixelsHigh:POTHigh contentSize:s]; + free( data ); + + if( ! texture ) { + CCLOG(@"cocos2d: CCGrid: error creating texture"); + [self release]; + return nil; + } + + self = [self initWithSize:gSize texture:texture flippedTexture:NO]; + + [texture release]; + + return self; +} +- (NSString*) description +{ + return [NSString stringWithFormat:@"<%@ = %08X | Dimensions = %ix%i>", [self class], self, gridSize_.x, gridSize_.y]; +} + +- (void) dealloc +{ + CCLOGINFO(@"cocos2d: deallocing %@", self); + + [self setActive: NO]; + + [texture_ release]; + [grabber_ release]; + [super dealloc]; +} + +// properties +-(BOOL) active +{ + return active_; +} + +-(void) setActive:(BOOL)active +{ + active_ = active; + if( ! active ) { + CCDirector *director = [CCDirector sharedDirector]; + ccDirectorProjection proj = [director projection]; + [director setProjection:proj]; + } +} + +-(BOOL) isTextureFlipped +{ + return isTextureFlipped_; +} + +-(void) setIsTextureFlipped:(BOOL)flipped +{ + if( isTextureFlipped_ != flipped ) { + isTextureFlipped_ = flipped; + [self calculateVertexPoints]; + } +} + +// This routine can be merged with Director +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +-(void)applyLandscape +{ + CCDirector *director = [CCDirector sharedDirector]; + + CGSize winSize = [director displaySizeInPixels]; + float w = winSize.width / 2; + float h = winSize.height / 2; + + ccDeviceOrientation orientation = [director deviceOrientation]; + + switch (orientation) { + case CCDeviceOrientationLandscapeLeft: + glTranslatef(w,h,0); + glRotatef(-90,0,0,1); + glTranslatef(-h,-w,0); + break; + case CCDeviceOrientationLandscapeRight: + glTranslatef(w,h,0); + glRotatef(90,0,0,1); + glTranslatef(-h,-w,0); + break; + case CCDeviceOrientationPortraitUpsideDown: + glTranslatef(w,h,0); + glRotatef(180,0,0,1); + glTranslatef(-w,-h,0); + break; + default: + break; + } +} +#endif + +-(void)set2DProjection +{ + CGSize winSize = [[CCDirector sharedDirector] winSizeInPixels]; + + glLoadIdentity(); + glViewport(0, 0, winSize.width, winSize.height); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + ccglOrtho(0, winSize.width, 0, winSize.height, -1024, 1024); + glMatrixMode(GL_MODELVIEW); +} + +// This routine can be merged with Director +-(void)set3DProjection +{ + CCDirector *director = [CCDirector sharedDirector]; + + CGSize winSize = [director displaySizeInPixels]; + + glViewport(0, 0, winSize.width, winSize.height); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(60, (GLfloat)winSize.width/winSize.height, 0.5f, 1500.0f); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt( winSize.width/2, winSize.height/2, [director getZEye], + winSize.width/2, winSize.height/2, 0, + 0.0f, 1.0f, 0.0f + ); +} + +-(void)beforeDraw +{ + [self set2DProjection]; + [grabber_ beforeRender:texture_]; +} + +-(void)afterDraw:(CCNode *)target +{ + [grabber_ afterRender:texture_]; + + [self set3DProjection]; +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + [self applyLandscape]; +#endif + + if( target.camera.dirty ) { + + CGPoint offset = [target anchorPointInPixels]; + + // + // XXX: Camera should be applied in the AnchorPoint + // + ccglTranslate(offset.x, offset.y, 0); + [target.camera locate]; + ccglTranslate(-offset.x, -offset.y, 0); + } + + glBindTexture(GL_TEXTURE_2D, texture_.name); + + [self blit]; +} + +-(void)blit +{ + [NSException raise:@"GridBase" format:@"Abstract class needs implementation"]; +} + +-(void)reuse +{ + [NSException raise:@"GridBase" format:@"Abstract class needs implementation"]; +} + +-(void)calculateVertexPoints +{ + [NSException raise:@"GridBase" format:@"Abstract class needs implementation"]; +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark CCGrid3D +@implementation CCGrid3D + +-(void)dealloc +{ + free(texCoordinates); + free(vertices); + free(indices); + free(originalVertices); + [super dealloc]; +} + +-(void)blit +{ + NSInteger n = gridSize_.x * gridSize_.y; + + // 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_TEXTURE_COORD_ARRAY + // Unneeded states: GL_COLOR_ARRAY + glDisableClientState(GL_COLOR_ARRAY); + + glVertexPointer(3, GL_FLOAT, 0, vertices); + glTexCoordPointer(2, GL_FLOAT, 0, texCoordinates); + glDrawElements(GL_TRIANGLES, (GLsizei) n*6, GL_UNSIGNED_SHORT, indices); + + // restore GL default state + glEnableClientState(GL_COLOR_ARRAY); +} + +-(void)calculateVertexPoints +{ + float width = (float)texture_.pixelsWide; + float height = (float)texture_.pixelsHigh; + float imageH = texture_.contentSizeInPixels.height; + + int x, y, i; + + vertices = malloc((gridSize_.x+1)*(gridSize_.y+1)*sizeof(ccVertex3F)); + originalVertices = malloc((gridSize_.x+1)*(gridSize_.y+1)*sizeof(ccVertex3F)); + texCoordinates = malloc((gridSize_.x+1)*(gridSize_.y+1)*sizeof(CGPoint)); + indices = malloc(gridSize_.x*gridSize_.y*sizeof(GLushort)*6); + + float *vertArray = (float*)vertices; + float *texArray = (float*)texCoordinates; + GLushort *idxArray = (GLushort *)indices; + + for( x = 0; x < gridSize_.x; x++ ) + { + for( y = 0; y < gridSize_.y; y++ ) + { + NSInteger idx = (y * gridSize_.x) + x; + + float x1 = x * step_.x; + float x2 = x1 + step_.x; + float y1 = y * step_.y; + float y2 = y1 + step_.y; + + GLushort a = x * (gridSize_.y+1) + y; + GLushort b = (x+1) * (gridSize_.y+1) + y; + GLushort c = (x+1) * (gridSize_.y+1) + (y+1); + GLushort d = x * (gridSize_.y+1) + (y+1); + + GLushort tempidx[6] = { a, b, d, b, c, d }; + + memcpy(&idxArray[6*idx], tempidx, 6*sizeof(GLushort)); + + int l1[4] = { a*3, b*3, c*3, d*3 }; + ccVertex3F e = {x1,y1,0}; + ccVertex3F f = {x2,y1,0}; + ccVertex3F g = {x2,y2,0}; + ccVertex3F h = {x1,y2,0}; + + ccVertex3F l2[4] = { e, f, g, h }; + + int tex1[4] = { a*2, b*2, c*2, d*2 }; + CGPoint tex2[4] = { ccp(x1, y1), ccp(x2, y1), ccp(x2, y2), ccp(x1, y2) }; + + for( i = 0; i < 4; i++ ) + { + vertArray[ l1[i] ] = l2[i].x; + vertArray[ l1[i] + 1 ] = l2[i].y; + vertArray[ l1[i] + 2 ] = l2[i].z; + + texArray[ tex1[i] ] = tex2[i].x / width; + if( isTextureFlipped_ ) + texArray[ tex1[i] + 1 ] = (imageH - tex2[i].y) / height; + else + texArray[ tex1[i] + 1 ] = tex2[i].y / height; + } + } + } + + memcpy(originalVertices, vertices, (gridSize_.x+1)*(gridSize_.y+1)*sizeof(ccVertex3F)); +} + +-(ccVertex3F)vertex:(ccGridSize)pos +{ + NSInteger index = (pos.x * (gridSize_.y+1) + pos.y) * 3; + float *vertArray = (float *)vertices; + + ccVertex3F vert = { vertArray[index], vertArray[index+1], vertArray[index+2] }; + + return vert; +} + +-(ccVertex3F)originalVertex:(ccGridSize)pos +{ + NSInteger index = (pos.x * (gridSize_.y+1) + pos.y) * 3; + float *vertArray = (float *)originalVertices; + + ccVertex3F vert = { vertArray[index], vertArray[index+1], vertArray[index+2] }; + + return vert; +} + +-(void)setVertex:(ccGridSize)pos vertex:(ccVertex3F)vertex +{ + NSInteger index = (pos.x * (gridSize_.y+1) + pos.y) * 3; + float *vertArray = (float *)vertices; + vertArray[index] = vertex.x; + vertArray[index+1] = vertex.y; + vertArray[index+2] = vertex.z; +} + +-(void)reuse +{ + if ( reuseGrid_ > 0 ) + { + memcpy(originalVertices, vertices, (gridSize_.x+1)*(gridSize_.y+1)*sizeof(ccVertex3F)); + reuseGrid_--; + } +} + +@end + +//////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark CCTiledGrid3D + +@implementation CCTiledGrid3D + +-(void)dealloc +{ + free(texCoordinates); + free(vertices); + free(indices); + free(originalVertices); + [super dealloc]; +} + +-(void)blit +{ + NSInteger n = gridSize_.x * gridSize_.y; + + // 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_TEXTURE_COORD_ARRAY + // Unneeded states: GL_COLOR_ARRAY + glDisableClientState(GL_COLOR_ARRAY); + + glVertexPointer(3, GL_FLOAT, 0, vertices); + glTexCoordPointer(2, GL_FLOAT, 0, texCoordinates); + glDrawElements(GL_TRIANGLES, (GLsizei) n*6, GL_UNSIGNED_SHORT, indices); + + // restore default GL state + glEnableClientState(GL_COLOR_ARRAY); +} + +-(void)calculateVertexPoints +{ + float width = (float)texture_.pixelsWide; + float height = (float)texture_.pixelsHigh; + float imageH = texture_.contentSizeInPixels.height; + + NSInteger numQuads = gridSize_.x * gridSize_.y; + + vertices = malloc(numQuads*12*sizeof(GLfloat)); + originalVertices = malloc(numQuads*12*sizeof(GLfloat)); + texCoordinates = malloc(numQuads*8*sizeof(GLfloat)); + indices = malloc(numQuads*6*sizeof(GLushort)); + + float *vertArray = (float*)vertices; + float *texArray = (float*)texCoordinates; + GLushort *idxArray = (GLushort *)indices; + + int x, y; + + for( x = 0; x < gridSize_.x; x++ ) + { + for( y = 0; y < gridSize_.y; y++ ) + { + float x1 = x * step_.x; + float x2 = x1 + step_.x; + float y1 = y * step_.y; + float y2 = y1 + step_.y; + + *vertArray++ = x1; + *vertArray++ = y1; + *vertArray++ = 0; + *vertArray++ = x2; + *vertArray++ = y1; + *vertArray++ = 0; + *vertArray++ = x1; + *vertArray++ = y2; + *vertArray++ = 0; + *vertArray++ = x2; + *vertArray++ = y2; + *vertArray++ = 0; + + float newY1 = y1; + float newY2 = y2; + + if( isTextureFlipped_ ) { + newY1 = imageH - y1; + newY2 = imageH - y2; + } + + *texArray++ = x1 / width; + *texArray++ = newY1 / height; + *texArray++ = x2 / width; + *texArray++ = newY1 / height; + *texArray++ = x1 / width; + *texArray++ = newY2 / height; + *texArray++ = x2 / width; + *texArray++ = newY2 / height; + } + } + + for( x = 0; x < numQuads; x++) + { + idxArray[x*6+0] = x*4+0; + idxArray[x*6+1] = x*4+1; + idxArray[x*6+2] = x*4+2; + + idxArray[x*6+3] = x*4+1; + idxArray[x*6+4] = x*4+2; + idxArray[x*6+5] = x*4+3; + } + + memcpy(originalVertices, vertices, numQuads*12*sizeof(GLfloat)); +} + +-(void)setTile:(ccGridSize)pos coords:(ccQuad3)coords +{ + NSInteger idx = (gridSize_.y * pos.x + pos.y) * 4 * 3; + float *vertArray = (float*)vertices; + memcpy(&vertArray[idx], &coords, sizeof(ccQuad3)); +} + +-(ccQuad3)originalTile:(ccGridSize)pos +{ + NSInteger idx = (gridSize_.y * pos.x + pos.y) * 4 * 3; + float *vertArray = (float*)originalVertices; + + ccQuad3 ret; + memcpy(&ret, &vertArray[idx], sizeof(ccQuad3)); + + return ret; +} + +-(ccQuad3)tile:(ccGridSize)pos +{ + NSInteger idx = (gridSize_.y * pos.x + pos.y) * 4 * 3; + float *vertArray = (float*)vertices; + + ccQuad3 ret; + memcpy(&ret, &vertArray[idx], sizeof(ccQuad3)); + + return ret; +} + +-(void)reuse +{ + if ( reuseGrid_ > 0 ) + { + NSInteger numQuads = gridSize_.x * gridSize_.y; + + memcpy(originalVertices, vertices, numQuads*12*sizeof(GLfloat)); + reuseGrid_--; + } +} + +@end diff --git a/libs/cocos2d/CCLabelAtlas.h b/libs/cocos2d/CCLabelAtlas.h new file mode 100644 index 0000000..18d9ce4 --- /dev/null +++ b/libs/cocos2d/CCLabelAtlas.h @@ -0,0 +1,62 @@ +/* + * 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. + * + */ + + +#import "CCAtlasNode.h" +#import "CCTextureAtlas.h" + +/** CCLabelAtlas is a subclass of CCAtlasNode. + + It can be as a replacement of CCLabel since it is MUCH faster. + + CCLabelAtlas versus CCLabel: + - CCLabelAtlas is MUCH faster than CCLabel + - CCLabelAtlas "characters" have a fixed height and width + - CCLabelAtlas "characters" can be anything you want since they are taken from an image file + + A more flexible class is CCBitmapFontAtlas. It supports variable width characters and it also has a nice editor. + */ +@interface CCLabelAtlas : CCAtlasNode +{ + // string to render + NSString *string_; + + // the first char in the charmap + char mapStartChar; +} + + +/** creates the CCLabelAtlas with a string, a char map file(the atlas), the width and height of each element in points and the starting char of the atlas */ ++(id) labelWithString:(NSString*) string charMapFile: (NSString*) charmapfile itemWidth:(int)w itemHeight:(int)h startCharMap:(char)c; + +/** creates the CCLabelAtlas with a string, a char map file(the atlas), the width and height of each element in points and the starting char of the atlas. + @deprecated Will be removed in 1.0.1. Use "labelWithString:" instead + */ ++(id) labelAtlasWithString:(NSString*) string charMapFile: (NSString*) charmapfile itemWidth:(int)w itemHeight:(int)h startCharMap:(char)c DEPRECATED_ATTRIBUTE; + +/** initializes the CCLabelAtlas with a string, a char map file(the atlas), the width and height in points of each element and the starting char of the atlas */ +-(id) initWithString:(NSString*) string charMapFile: (NSString*) charmapfile itemWidth:(int)w itemHeight:(int)h startCharMap:(char)c; +@end diff --git a/libs/cocos2d/CCLabelAtlas.m b/libs/cocos2d/CCLabelAtlas.m new file mode 100644 index 0000000..ef953f5 --- /dev/null +++ b/libs/cocos2d/CCLabelAtlas.m @@ -0,0 +1,191 @@ +/* + * 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. + * + */ + + +#import "ccConfig.h" +#import "ccMacros.h" +#import "CCDrawingPrimitives.h" +#import "CCLabelAtlas.h" +#import "Support/CGPointExtension.h" + + + +@implementation CCLabelAtlas + +#pragma mark CCLabelAtlas - Creation & Init ++(id) labelWithString:(NSString*)string charMapFile:(NSString*)charmapfile itemWidth:(int)w itemHeight:(int)h startCharMap:(char)c +{ + return [[[self alloc] initWithString:string charMapFile:charmapfile itemWidth:w itemHeight:h startCharMap:c] autorelease]; +} + +// XXX DEPRECATED. Remove it in 1.0.1 ++(id) labelAtlasWithString:(NSString*) string charMapFile: (NSString*) charmapfile itemWidth:(int)w itemHeight:(int)h startCharMap:(char)c +{ + return [self labelWithString:string charMapFile:charmapfile itemWidth:w itemHeight:h startCharMap:c]; +} + + +-(id) initWithString:(NSString*) theString charMapFile: (NSString*) charmapfile itemWidth:(int)w itemHeight:(int)h startCharMap:(char)c +{ + + if ((self=[super initWithTileFile:charmapfile tileWidth:w tileHeight:h itemsToRender:[theString length] ]) ) { + + mapStartChar = c; + [self setString: theString]; + } + + return self; +} + +-(void) dealloc +{ + [string_ release]; + + [super dealloc]; +} + +#pragma mark CCLabelAtlas - Atlas generation + +-(void) updateAtlasValues +{ + NSInteger n = [string_ length]; + + ccV3F_C4B_T2F_Quad quad; + + const char *s = [string_ UTF8String]; + + CCTexture2D *texture = [textureAtlas_ texture]; + float textureWide = [texture pixelsWide]; + float textureHigh = [texture pixelsHigh]; + + for( NSUInteger i=0; i textureAtlas_.capacity ) + [textureAtlas_ resizeCapacity:len]; + + [string_ release]; + string_ = [newString copy]; + [self updateAtlasValues]; + + CGSize s; + s.width = len * itemWidth_; + s.height = itemHeight_; + [self setContentSizeInPixels:s]; +} + +-(NSString*) string +{ + return string_; +} + +#pragma mark CCLabelAtlas - draw + +// XXX: overriding draw from AtlasNode +- (void) 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_TEXTURE_COORD_ARRAY + // Unneeded states: GL_COLOR_ARRAY + glDisableClientState(GL_COLOR_ARRAY); + + glColor4ub( color_.r, color_.g, color_.b, opacity_); + + BOOL newBlend = blendFunc_.src != CC_BLEND_SRC || blendFunc_.dst != CC_BLEND_DST; + if( newBlend ) + glBlendFunc( blendFunc_.src, blendFunc_.dst ); + + [textureAtlas_ drawNumberOfQuads:string_.length fromIndex:0]; + + if( newBlend ) + glBlendFunc(CC_BLEND_SRC, CC_BLEND_DST); + + // is this chepear than saving/restoring color state ? + // XXX: There is no need to restore the color to (255,255,255,255). Objects should use the color + // XXX: that they need +// glColor4ub( 255, 255, 255, 255); + + // Restore Default GL state. Enable GL_COLOR_ARRAY + glEnableClientState(GL_COLOR_ARRAY); + + +#if CC_LABELATLAS_DEBUG_DRAW + CGSize s = [self contentSize]; + CGPoint vertices[4]={ + ccp(0,0),ccp(s.width,0), + ccp(s.width,s.height),ccp(0,s.height), + }; + ccDrawPoly(vertices, 4, YES); +#endif // CC_LABELATLAS_DEBUG_DRAW + +} +@end diff --git a/libs/cocos2d/CCLabelBMFont.h b/libs/cocos2d/CCLabelBMFont.h new file mode 100644 index 0000000..0fd2fce --- /dev/null +++ b/libs/cocos2d/CCLabelBMFont.h @@ -0,0 +1,190 @@ +/* + * 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. + * + * Portions of this code are based and inspired on: + * http://www.71squared.co.uk/2009/04/iphone-game-programming-tutorial-4-bitmap-font-class + * by Michael Daley + + * Use any of these editors to generate bitmap font atlas: + * http://www.n4te.com/hiero/hiero.jnlp + * http://slick.cokeandcode.com/demos/hiero.jnlp + * http://www.angelcode.com/products/bmfont/ + * + */ + +#import "CCSpriteBatchNode.h" +#import "Support/uthash.h" + +struct _KerningHashElement; + +/** @struct ccBMFontDef + BMFont definition + */ +typedef struct _BMFontDef { + //! ID of the character + unsigned int charID; + //! origin and size of the font + CGRect rect; + //! The X amount the image should be offset when drawing the image (in pixels) + int xOffset; + //! The Y amount the image should be offset when drawing the image (in pixels) + int yOffset; + //! The amount to move the current position after drawing the character (in pixels) + int xAdvance; +} ccBMFontDef; + +/** @struct ccBMFontPadding + BMFont padding + @since v0.8.2 + */ +typedef struct _BMFontPadding { + /// padding left + int left; + /// padding top + int top; + /// padding right + int right; + /// padding bottom + int bottom; +} ccBMFontPadding; + +enum { + // how many characters are supported + kCCBMFontMaxChars = 2048, //256, +}; + +/** CCBMFontConfiguration has parsed configuration of the the .fnt file + @since v0.8 + */ +@interface CCBMFontConfiguration : NSObject +{ +// XXX: Creating a public interface so that the bitmapFontArray[] is accesible +@public + // The characters building up the font + ccBMFontDef BMFontArray_[kCCBMFontMaxChars]; + + // FNTConfig: Common Height + NSUInteger commonHeight_; + + // Padding + ccBMFontPadding padding_; + + // atlas name + NSString *atlasName_; + + // values for kerning + struct _KerningHashElement *kerningDictionary_; +} + +/** allocates a CCBMFontConfiguration with a FNT file */ ++(id) configurationWithFNTFile:(NSString*)FNTfile; +/** initializes a CCBMFontConfiguration with a FNT file */ +-(id) initWithFNTfile:(NSString*)FNTfile; +@end + + +/** CCLabelBMFont is a subclass of CCSpriteBatchNode + + Features: + - Treats each character like a CCSprite. This means that each individual character can be: + - rotated + - scaled + - translated + - tinted + - chage the opacity + - It can be used as part of a menu item. + - anchorPoint can be used to align the "label" + - Supports AngelCode text format + + Limitations: + - All inner characters are using an anchorPoint of (0.5f, 0.5f) and it is not recommend to change it + because it might affect the rendering + + CCLabelBMFont implements the protocol CCLabelProtocol, like CCLabel and CCLabelAtlas. + CCLabelBMFont has the flexibility of CCLabel, the speed of CCLabelAtlas and all the features of CCSprite. + If in doubt, use CCLabelBMFont instead of CCLabelAtlas / CCLabel. + + Supported editors: + - http://www.n4te.com/hiero/hiero.jnlp + - http://slick.cokeandcode.com/demos/hiero.jnlp + - http://www.angelcode.com/products/bmfont/ + + @since v0.8 + */ + +@interface CCLabelBMFont : CCSpriteBatchNode +{ + // string to render + NSString *string_; + + CCBMFontConfiguration *configuration_; + + // texture RGBA + GLubyte opacity_; + ccColor3B color_; + BOOL opacityModifyRGB_; +} + +/** Purges the cached data. + Removes from memory the cached configurations and the atlas name dictionary. + @since v0.99.3 + */ ++(void) purgeCachedData; + +/** conforms to CCRGBAProtocol protocol */ +@property (nonatomic,readwrite) GLubyte opacity; +/** conforms to CCRGBAProtocol protocol */ +@property (nonatomic,readwrite) ccColor3B color; + + +/** creates a BMFont label with an initial string and the FNT file */ ++(id) labelWithString:(NSString*)string fntFile:(NSString*)fntFile; + +/** creates a BMFont label with an initial string and the FNT file + @deprecated Will be removed in 1.0.1. Use "labelWithString" instead. + */ ++(id) bitmapFontAtlasWithString:(NSString*)string fntFile:(NSString*)fntFile DEPRECATED_ATTRIBUTE; + +/** init a BMFont label with an initial string and the FNT file */ +-(id) initWithString:(NSString*)string fntFile:(NSString*)fntFile; + +/** updates the font chars based on the string to render */ +-(void) createFontChars; +@end + +/** Free function that parses a FNT file a place it on the cache +*/ +CCBMFontConfiguration * FNTConfigLoadFile( NSString *file ); +/** Purges the FNT config cache + */ +void FNTConfigRemoveCache( void ); + + + +/** CCBitmapFontAtlas + @deprecated Use CCLabelBMFont instead. Will be removed 1.0.1 + */ +DEPRECATED_ATTRIBUTE @interface CCBitmapFontAtlas : CCLabelBMFont +@end + diff --git a/libs/cocos2d/CCLabelBMFont.m b/libs/cocos2d/CCLabelBMFont.m new file mode 100644 index 0000000..78d7876 --- /dev/null +++ b/libs/cocos2d/CCLabelBMFont.m @@ -0,0 +1,673 @@ +/* + * 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. + * + * Portions of this code are based and inspired on: + * http://www.71squared.co.uk/2009/04/iphone-game-programming-tutorial-4-bitmap-font-class + * by Michael Daley + * + * + * Use any of these editors to generate bitmap font atlas: + * http://www.n4te.com/hiero/hiero.jnlp + * http://slick.cokeandcode.com/demos/hiero.jnlp + * http://www.angelcode.com/products/bmfont/ + */ + +#import "ccConfig.h" +#import "CCLabelBMFont.h" +#import "CCSprite.h" +#import "CCDrawingPrimitives.h" +#import "CCConfiguration.h" +#import "Support/CCFileUtils.h" +#import "Support/CGPointExtension.h" +#import "Support/uthash.h" + +#pragma mark - +#pragma mark FNTConfig Cache - free functions + +NSMutableDictionary *configurations = nil; +CCBMFontConfiguration* FNTConfigLoadFile( NSString *fntFile) +{ + CCBMFontConfiguration *ret = nil; + + if( configurations == nil ) + configurations = [[NSMutableDictionary dictionaryWithCapacity:3] retain]; + + ret = [configurations objectForKey:fntFile]; + if( ret == nil ) { + ret = [CCBMFontConfiguration configurationWithFNTFile:fntFile]; + [configurations setObject:ret forKey:fntFile]; + } + + return ret; +} + +void FNTConfigRemoveCache( void ) +{ + [configurations removeAllObjects]; +} + +#pragma mark - Hash Element + +// Equal function for targetSet. +typedef struct _KerningHashElement +{ + int key; // key for the hash. 16-bit for 1st element, 16-bit for 2nd element + int amount; + UT_hash_handle hh; +} tKerningHashElement; + +#pragma mark - +#pragma mark BitmapFontConfiguration + + +@interface CCBMFontConfiguration (Private) +-(void) parseConfigFile:(NSString*)controlFile; +-(void) parseCharacterDefinition:(NSString*)line charDef:(ccBMFontDef*)characterDefinition; +-(void) parseInfoArguments:(NSString*)line; +-(void) parseCommonArguments:(NSString*)line; +-(void) parseImageFileName:(NSString*)line fntFile:(NSString*)fntFile; +-(void) parseKerningCapacity:(NSString*)line; +-(void) parseKerningEntry:(NSString*)line; +-(void) purgeKerningDictionary; +@end + +@implementation CCBMFontConfiguration + ++(id) configurationWithFNTFile:(NSString*)FNTfile +{ + return [[[self alloc] initWithFNTfile:FNTfile] autorelease]; +} + +-(id) initWithFNTfile:(NSString*)fntFile +{ + if((self=[super init])) { + + kerningDictionary_ = NULL; + + [self parseConfigFile:fntFile]; + } + return self; +} + +- (void) dealloc +{ + CCLOGINFO( @"cocos2d: deallocing %@", self); + [self purgeKerningDictionary]; + [atlasName_ release]; + [super dealloc]; +} + +- (NSString*) description +{ + return [NSString stringWithFormat:@"<%@ = %08X | Kernings:%d | Image = %@>", [self class], self, + HASH_COUNT(kerningDictionary_), + atlasName_]; +} + + +-(void) purgeKerningDictionary +{ + tKerningHashElement *current; + + while(kerningDictionary_) { + current = kerningDictionary_; + HASH_DEL(kerningDictionary_,current); + free(current); + } +} + +- (void)parseConfigFile:(NSString*)fntFile +{ + NSString *fullpath = [CCFileUtils fullPathFromRelativePath:fntFile]; + NSError *error; + NSString *contents = [NSString stringWithContentsOfFile:fullpath encoding:NSUTF8StringEncoding error:&error]; + + NSAssert1( contents, @"cocos2d: Error parsing FNTfile: %@", error); + + + // Move all lines in the string, which are denoted by \n, into an array + NSArray *lines = [[NSArray alloc] initWithArray:[contents componentsSeparatedByString:@"\n"]]; + + // Create an enumerator which we can use to move through the lines read from the control file + NSEnumerator *nse = [lines objectEnumerator]; + + // Create a holder for each line we are going to work with + NSString *line; + + // Loop through all the lines in the lines array processing each one + while( (line = [nse nextObject]) ) { + // parse spacing / padding + if([line hasPrefix:@"info face"]) { + // XXX: info parsing is incomplete + // Not needed for the Hiero editors, but needed for the AngelCode editor +// [self parseInfoArguments:line]; + } + // Check to see if the start of the line is something we are interested in + else if([line hasPrefix:@"common lineHeight"]) { + [self parseCommonArguments:line]; + } + else if([line hasPrefix:@"page id"]) { + [self parseImageFileName:line fntFile:fntFile]; + } + else if([line hasPrefix:@"chars c"]) { + // Ignore this line + } + else if([line hasPrefix:@"char"]) { + // Parse the current line and create a new CharDef + ccBMFontDef characterDefinition; + [self parseCharacterDefinition:line charDef:&characterDefinition]; + + // Add the CharDef returned to the charArray + BMFontArray_[ characterDefinition.charID ] = characterDefinition; + } + else if([line hasPrefix:@"kernings count"]) { + [self parseKerningCapacity:line]; + } + else if([line hasPrefix:@"kerning first"]) { + [self parseKerningEntry:line]; + } + } + // Finished with lines so release it + [lines release]; +} + +-(void) parseImageFileName:(NSString*)line fntFile:(NSString*)fntFile +{ + NSString *propertyValue = nil; + + // Break the values for this line up using = + NSArray *values = [line componentsSeparatedByString:@"="]; + + // Get the enumerator for the array of components which has been created + NSEnumerator *nse = [values objectEnumerator]; + + // We need to move past the first entry in the array before we start assigning values + [nse nextObject]; + + // page ID. Sanity check + propertyValue = [nse nextObject]; + NSAssert( [propertyValue intValue] == 0, @"XXX: BitmapFontAtlas only supports 1 page"); + + // file + propertyValue = [nse nextObject]; + NSArray *array = [propertyValue componentsSeparatedByString:@"\""]; + propertyValue = [array objectAtIndex:1]; + NSAssert(propertyValue,@"BitmapFontAtlas file could not be found"); + + // Supports subdirectories + NSString *dir = [fntFile stringByDeletingLastPathComponent]; + atlasName_ = [dir stringByAppendingPathComponent:propertyValue]; + + [atlasName_ retain]; +} + +-(void) parseInfoArguments:(NSString*)line +{ + // + // possible lines to parse: + // info face="Script" size=32 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=1,4,3,2 spacing=0,0 outline=0 + // info face="Cracked" size=36 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 + // + NSArray *values = [line componentsSeparatedByString:@"="]; + NSEnumerator *nse = [values objectEnumerator]; + NSString *propertyValue = nil; + + // We need to move past the first entry in the array before we start assigning values + [nse nextObject]; + + // face (ignore) + [nse nextObject]; + + // size (ignore) + [nse nextObject]; + + // bold (ignore) + [nse nextObject]; + + // italic (ignore) + [nse nextObject]; + + // charset (ignore) + [nse nextObject]; + + // unicode (ignore) + [nse nextObject]; + + // strechH (ignore) + [nse nextObject]; + + // smooth (ignore) + [nse nextObject]; + + // aa (ignore) + [nse nextObject]; + + // padding (ignore) + propertyValue = [nse nextObject]; + { + + NSArray *paddingValues = [propertyValue componentsSeparatedByString:@","]; + NSEnumerator *paddingEnum = [paddingValues objectEnumerator]; + // padding top + propertyValue = [paddingEnum nextObject]; + padding_.top = [propertyValue intValue]; + + // padding right + propertyValue = [paddingEnum nextObject]; + padding_.right = [propertyValue intValue]; + + // padding bottom + propertyValue = [paddingEnum nextObject]; + padding_.bottom = [propertyValue intValue]; + + // padding left + propertyValue = [paddingEnum nextObject]; + padding_.left = [propertyValue intValue]; + + CCLOG(@"cocos2d: padding: %d,%d,%d,%d", padding_.left, padding_.top, padding_.right, padding_.bottom); + } + + // spacing (ignore) + [nse nextObject]; +} + +-(void) parseCommonArguments:(NSString*)line +{ + // + // line to parse: + // common lineHeight=104 base=26 scaleW=1024 scaleH=512 pages=1 packed=0 + // + NSArray *values = [line componentsSeparatedByString:@"="]; + NSEnumerator *nse = [values objectEnumerator]; + NSString *propertyValue = nil; + + // We need to move past the first entry in the array before we start assigning values + [nse nextObject]; + + // Character ID + propertyValue = [nse nextObject]; + commonHeight_ = [propertyValue intValue]; + + // base (ignore) + [nse nextObject]; + + + // scaleW. sanity check + propertyValue = [nse nextObject]; + NSAssert( [propertyValue intValue] <= [[CCConfiguration sharedConfiguration] maxTextureSize], @"CCLabelBMFont: page can't be larger than supported"); + + // scaleH. sanity check + propertyValue = [nse nextObject]; + NSAssert( [propertyValue intValue] <= [[CCConfiguration sharedConfiguration] maxTextureSize], @"CCLabelBMFont: page can't be larger than supported"); + + // pages. sanity check + propertyValue = [nse nextObject]; + NSAssert( [propertyValue intValue] == 1, @"CCBitfontAtlas: only supports 1 page"); + + // packed (ignore) What does this mean ?? +} +- (void)parseCharacterDefinition:(NSString*)line charDef:(ccBMFontDef*)characterDefinition +{ + // Break the values for this line up using = + NSArray *values = [line componentsSeparatedByString:@"="]; + NSEnumerator *nse = [values objectEnumerator]; + NSString *propertyValue; + + // We need to move past the first entry in the array before we start assigning values + [nse nextObject]; + + // Character ID + propertyValue = [nse nextObject]; + propertyValue = [propertyValue substringToIndex: [propertyValue rangeOfString: @" "].location]; + characterDefinition->charID = [propertyValue intValue]; + NSAssert(characterDefinition->charID < kCCBMFontMaxChars, @"BitmpaFontAtlas: CharID bigger than supported"); + + // Character x + propertyValue = [nse nextObject]; + characterDefinition->rect.origin.x = [propertyValue intValue]; + // Character y + propertyValue = [nse nextObject]; + characterDefinition->rect.origin.y = [propertyValue intValue]; + // Character width + propertyValue = [nse nextObject]; + characterDefinition->rect.size.width = [propertyValue intValue]; + // Character height + propertyValue = [nse nextObject]; + characterDefinition->rect.size.height = [propertyValue intValue]; + // Character xoffset + propertyValue = [nse nextObject]; + characterDefinition->xOffset = [propertyValue intValue]; + // Character yoffset + propertyValue = [nse nextObject]; + characterDefinition->yOffset = [propertyValue intValue]; + // Character xadvance + propertyValue = [nse nextObject]; + characterDefinition->xAdvance = [propertyValue intValue]; +} + +-(void) parseKerningCapacity:(NSString*) line +{ + // When using uthash there is not need to parse the capacity. + +// NSAssert(!kerningDictionary, @"dictionary already initialized"); +// +// // Break the values for this line up using = +// NSArray *values = [line componentsSeparatedByString:@"="]; +// NSEnumerator *nse = [values objectEnumerator]; +// NSString *propertyValue; +// +// // We need to move past the first entry in the array before we start assigning values +// [nse nextObject]; +// +// // count +// propertyValue = [nse nextObject]; +// int capacity = [propertyValue intValue]; +// +// if( capacity != -1 ) +// kerningDictionary = ccHashSetNew(capacity, targetSetEql); +} + +-(void) parseKerningEntry:(NSString*) line +{ + NSArray *values = [line componentsSeparatedByString:@"="]; + NSEnumerator *nse = [values objectEnumerator]; + NSString *propertyValue; + + // We need to move past the first entry in the array before we start assigning values + [nse nextObject]; + + // first + propertyValue = [nse nextObject]; + int first = [propertyValue intValue]; + + // second + propertyValue = [nse nextObject]; + int second = [propertyValue intValue]; + + // second + propertyValue = [nse nextObject]; + int amount = [propertyValue intValue]; + + tKerningHashElement *element = calloc( sizeof( *element ), 1 ); + element->amount = amount; + element->key = (first<<16) | (second&0xffff); + HASH_ADD_INT(kerningDictionary_,key, element); +} + +@end + +#pragma mark - +#pragma mark CCLabelBMFont + +@interface CCLabelBMFont (Private) +-(NSString*) atlasNameFromFntFile:(NSString*)fntFile; + +-(int) kerningAmountForFirst:(unichar)first second:(unichar)second; + +@end + +@implementation CCLabelBMFont + +@synthesize opacity = opacity_, color = color_; + +#pragma mark BitmapFontAtlas - Purge Cache ++(void) purgeCachedData +{ + FNTConfigRemoveCache(); +} + +#pragma mark BitmapFontAtlas - Creation & Init + ++(id) labelWithString:(NSString *)string fntFile:(NSString *)fntFile +{ + return [[[self alloc] initWithString:string fntFile:fntFile] autorelease]; +} + +// XXX - deprecated - Will be removed in 1.0.1 ++(id) bitmapFontAtlasWithString:(NSString*)string fntFile:(NSString*)fntFile +{ + return [self labelWithString:string fntFile:fntFile]; +} + +-(id) initWithString:(NSString*)theString fntFile:(NSString*)fntFile +{ + + [configuration_ release]; // allow re-init + + configuration_ = FNTConfigLoadFile(fntFile); + [configuration_ retain]; + + NSAssert( configuration_, @"Error creating config for BitmapFontAtlas"); + + + if ((self=[super initWithFile:configuration_->atlasName_ capacity:[theString length]])) { + + opacity_ = 255; + color_ = ccWHITE; + + contentSize_ = CGSizeZero; + + opacityModifyRGB_ = [[textureAtlas_ texture] hasPremultipliedAlpha]; + + anchorPoint_ = ccp(0.5f, 0.5f); + + [self setString:theString]; + } + + return self; +} + +-(void) dealloc +{ + [string_ release]; + [configuration_ release]; + [super dealloc]; +} + +#pragma mark BitmapFontAtlas - Atlas generation + +-(int) kerningAmountForFirst:(unichar)first second:(unichar)second +{ + int ret = 0; + unsigned int key = (first<<16) | (second & 0xffff); + + if( configuration_->kerningDictionary_ ) { + tKerningHashElement *element = NULL; + HASH_FIND_INT(configuration_->kerningDictionary_, &key, element); + if(element) + ret = element->amount; + } + + return ret; +} + +-(void) createFontChars +{ + NSInteger nextFontPositionX = 0; + NSInteger nextFontPositionY = 0; + unichar prev = -1; + NSInteger kerningAmount = 0; + + CGSize tmpSize = CGSizeZero; + + NSInteger longestLine = 0; + NSUInteger totalHeight = 0; + + NSUInteger quantityOfLines = 1; + + NSUInteger stringLen = [string_ length]; + if( ! stringLen ) + return; + + // quantity of lines NEEDS to be calculated before parsing the lines, + // since the Y position needs to be calcualted before hand + for(NSUInteger i=0; i < stringLen-1;i++) { + unichar c = [string_ characterAtIndex:i]; + if( c=='\n') + quantityOfLines++; + } + + totalHeight = configuration_->commonHeight_ * quantityOfLines; + nextFontPositionY = -(configuration_->commonHeight_ - configuration_->commonHeight_*quantityOfLines); + + for(NSUInteger i=0; icommonHeight_; + continue; + } + + kerningAmount = [self kerningAmountForFirst:prev second:c]; + + ccBMFontDef fontDef = configuration_->BMFontArray_[c]; + + CGRect rect = fontDef.rect; + + CCSprite *fontChar; + + fontChar = (CCSprite*) [self getChildByTag:i]; + if( ! fontChar ) { + fontChar = [[CCSprite alloc] initWithBatchNode:self rectInPixels:rect]; + [self addChild:fontChar z:0 tag:i]; + [fontChar release]; + } + else { + // reusing fonts + [fontChar setTextureRectInPixels:rect rotated:NO untrimmedSize:rect.size]; + + // restore to default in case they were modified + fontChar.visible = YES; + fontChar.opacity = 255; + } + + float yOffset = configuration_->commonHeight_ - fontDef.yOffset; + fontChar.positionInPixels = ccp( (float)nextFontPositionX + fontDef.xOffset + fontDef.rect.size.width*0.5f + kerningAmount, + (float)nextFontPositionY + yOffset - rect.size.height*0.5f ); + + // update kerning + nextFontPositionX += configuration_->BMFontArray_[c].xAdvance + kerningAmount; + prev = c; + + // Apply label properties + [fontChar setOpacityModifyRGB:opacityModifyRGB_]; + // Color MUST be set before opacity, since opacity might change color if OpacityModifyRGB is on + [fontChar setColor:color_]; + + // only apply opacity if it is different than 255 ) + // to prevent modifying the color too (issue #610) + if( opacity_ != 255 ) + [fontChar setOpacity: opacity_]; + + if (longestLine < nextFontPositionX) + longestLine = nextFontPositionX; + } + + tmpSize.width = longestLine; + tmpSize.height = totalHeight; + + [self setContentSizeInPixels:tmpSize]; +} + +#pragma mark BitmapFontAtlas - CCLabelProtocol protocol +- (void) setString:(NSString*) newString +{ + [string_ release]; + string_ = [newString copy]; + + CCNode *child; + CCARRAY_FOREACH(children_, child) + child.visible = NO; + + [self createFontChars]; +} + +-(NSString*) string +{ + return string_; +} + +-(void) setCString:(char*)label +{ + [self setString:[NSString stringWithUTF8String:label]]; +} + +#pragma mark BitmapFontAtlas - CCRGBAProtocol protocol + +-(void) setColor:(ccColor3B)color +{ + color_ = color; + + CCSprite *child; + CCARRAY_FOREACH(children_, child) + [child setColor:color_]; +} + +-(void) setOpacity:(GLubyte)opacity +{ + opacity_ = opacity; + + id child; + CCARRAY_FOREACH(children_, child) + [child setOpacity:opacity_]; +} +-(void) setOpacityModifyRGB:(BOOL)modify +{ + opacityModifyRGB_ = modify; + + id child; + CCARRAY_FOREACH(children_, child) + [child setOpacityModifyRGB:modify]; +} + +-(BOOL) doesOpacityModifyRGB +{ + return opacityModifyRGB_; +} + +#pragma mark BitmapFontAtlas - AnchorPoint +-(void) setAnchorPoint:(CGPoint)point +{ + if( ! CGPointEqualToPoint(point, anchorPoint_) ) { + [super setAnchorPoint:point]; + [self createFontChars]; + } +} + +#pragma mark BitmapFontAtlas - Debug draw +#if CC_LABELBMFONT_DEBUG_DRAW +-(void) draw +{ + [super draw]; + CGSize s = [self contentSize]; + CGPoint vertices[4]={ + ccp(0,0),ccp(s.width,0), + ccp(s.width,s.height),ccp(0,s.height), + }; + ccDrawPoly(vertices, 4, YES); +} +#endif // CC_LABELBMFONT_DEBUG_DRAW +@end diff --git a/libs/cocos2d/CCLabelTTF.h b/libs/cocos2d/CCLabelTTF.h new file mode 100644 index 0000000..326e93f --- /dev/null +++ b/libs/cocos2d/CCLabelTTF.h @@ -0,0 +1,78 @@ +/* + * 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. + * + */ + + +#import "CCTexture2D.h" +#import "CCSprite.h" +#import "Platforms/CCNS.h" + + +/** CCLabel is a subclass of CCTextureNode that knows how to render text labels + * + * All features from CCTextureNode are valid in CCLabel + * + * CCLabel objects are slow. Consider using CCLabelAtlas or CCBitmapFontAtlas instead. + */ + +@interface CCLabelTTF : CCSprite +{ + CGSize dimensions_; + CCTextAlignment alignment_; + NSString * fontName_; + CGFloat fontSize_; + CCLineBreakMode lineBreakMode_; + NSString *string_; +} + +/** creates a CCLabel from a fontname, alignment, dimension in points, line break mode, and font size in points. + Supported lineBreakModes: + - iOS: all UILineBreakMode supported modes + - Mac: Only NSLineBreakByWordWrapping is supported. + @since v1.0 + */ ++ (id) labelWithString:(NSString*)string dimensions:(CGSize)dimensions alignment:(CCTextAlignment)alignment lineBreakMode:(CCLineBreakMode)lineBreakMode fontName:(NSString*)name fontSize:(CGFloat)size; +/** creates a CCLabel from a fontname, alignment, dimension in points and font size in points*/ ++ (id) labelWithString:(NSString*)string dimensions:(CGSize)dimensions alignment:(CCTextAlignment)alignment fontName:(NSString*)name fontSize:(CGFloat)size; +/** creates a CCLabel from a fontname and font size in points*/ ++ (id) labelWithString:(NSString*)string fontName:(NSString*)name fontSize:(CGFloat)size; +/** initializes the CCLabel with a font name, alignment, dimension in points, line brea mode and font size in points. + Supported lineBreakModes: + - iOS: all UILineBreakMode supported modes + - Mac: Only NSLineBreakByWordWrapping is supported. + @since v1.0 + */ +- (id) initWithString:(NSString*)str dimensions:(CGSize)dimensions alignment:(CCTextAlignment)alignment lineBreakMode:(CCLineBreakMode)lineBreakMode fontName:(NSString*)name fontSize:(CGFloat)size; +/** initializes the CCLabel with a font name, alignment, dimension in points and font size in points */ +- (id) initWithString:(NSString*)string dimensions:(CGSize)dimensions alignment:(CCTextAlignment)alignment fontName:(NSString*)name fontSize:(CGFloat)size; +/** initializes the CCLabel with a font name and font size in points */ +- (id) initWithString:(NSString*)string fontName:(NSString*)name fontSize:(CGFloat)size; + +/** changes the string to render + * @warning Changing the string is as expensive as creating a new CCLabel. To obtain better performance use CCLabelAtlas + */ +- (void) setString:(NSString*)str; + +@end diff --git a/libs/cocos2d/CCLabelTTF.m b/libs/cocos2d/CCLabelTTF.m new file mode 100644 index 0000000..4e4c954 --- /dev/null +++ b/libs/cocos2d/CCLabelTTF.m @@ -0,0 +1,138 @@ +/* + * 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. + * + */ + + +#import + +#import "CCLabelTTF.h" +#import "Support/CGPointExtension.h" +#import "ccMacros.h" + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +#import "Platforms/iOS/CCDirectorIOS.h" +#endif + +@implementation CCLabelTTF + +- (id) init +{ + NSAssert(NO, @"CCLabelTTF: Init not supported. Use initWithString"); + [self release]; + return nil; +} + ++ (id) labelWithString:(NSString*)string dimensions:(CGSize)dimensions alignment:(CCTextAlignment)alignment lineBreakMode:(CCLineBreakMode)lineBreakMode fontName:(NSString*)name fontSize:(CGFloat)size; +{ + return [[[self alloc] initWithString: string dimensions:dimensions alignment:alignment lineBreakMode:lineBreakMode fontName:name fontSize:size]autorelease]; +} + ++ (id) labelWithString:(NSString*)string dimensions:(CGSize)dimensions alignment:(CCTextAlignment)alignment fontName:(NSString*)name fontSize:(CGFloat)size +{ + return [[[self alloc] initWithString: string dimensions:dimensions alignment:alignment fontName:name fontSize:size]autorelease]; +} + ++ (id) labelWithString:(NSString*)string fontName:(NSString*)name fontSize:(CGFloat)size +{ + return [[[self alloc] initWithString: string fontName:name fontSize:size]autorelease]; +} + + +- (id) initWithString:(NSString*)str dimensions:(CGSize)dimensions alignment:(CCTextAlignment)alignment lineBreakMode:(CCLineBreakMode)lineBreakMode fontName:(NSString*)name fontSize:(CGFloat)size +{ + if( (self=[super init]) ) { + + dimensions_ = CGSizeMake( dimensions.width * CC_CONTENT_SCALE_FACTOR(), dimensions.height * CC_CONTENT_SCALE_FACTOR() ); + alignment_ = alignment; + fontName_ = [name retain]; + fontSize_ = size * CC_CONTENT_SCALE_FACTOR(); + lineBreakMode_ = lineBreakMode; + + [self setString:str]; + } + return self; +} + +- (id) initWithString:(NSString*)str dimensions:(CGSize)dimensions alignment:(CCTextAlignment)alignment fontName:(NSString*)name fontSize:(CGFloat)size +{ + return [self initWithString:str dimensions:dimensions alignment:alignment lineBreakMode:CCLineBreakModeWordWrap fontName:name fontSize:size]; +} + +- (id) initWithString:(NSString*)str fontName:(NSString*)name fontSize:(CGFloat)size +{ + if( (self=[super init]) ) { + + dimensions_ = CGSizeZero; + fontName_ = [name retain]; + fontSize_ = size * CC_CONTENT_SCALE_FACTOR(); + + [self setString:str]; + } + return self; +} + +- (void) setString:(NSString*)str +{ + [string_ release]; + string_ = [str copy]; + + CCTexture2D *tex; + if( CGSizeEqualToSize( dimensions_, CGSizeZero ) ) + tex = [[CCTexture2D alloc] initWithString:str + fontName:fontName_ + fontSize:fontSize_]; + else + tex = [[CCTexture2D alloc] initWithString:str + dimensions:dimensions_ + alignment:alignment_ + lineBreakMode:lineBreakMode_ + fontName:fontName_ + fontSize:fontSize_]; + + [self setTexture:tex]; + [tex release]; + + CGRect rect = CGRectZero; + rect.size = [texture_ contentSize]; + [self setTextureRect: rect]; +} + +-(NSString*) string +{ + return string_; +} + +- (void) dealloc +{ + [string_ release]; + [fontName_ release]; + [super dealloc]; +} + +- (NSString*) description +{ + return [NSString stringWithFormat:@"<%@ = %08X | FontName = %@, FontSize = %.1f>", [self class], self, fontName_, fontSize_]; +} +@end diff --git a/libs/cocos2d/CCLayer.h b/libs/cocos2d/CCLayer.h new file mode 100644 index 0000000..f4c9860 --- /dev/null +++ b/libs/cocos2d/CCLayer.h @@ -0,0 +1,293 @@ +/* + * 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. + * + */ + + + +#import +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +#import // Needed for UIAccelerometerDelegate +#import "Platforms/iOS/CCTouchDelegateProtocol.h" // Touches only supported on iOS +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) +#import "Platforms/Mac/CCEventDispatcher.h" +#endif + +#import "CCProtocols.h" +#import "CCNode.h" + +#pragma mark - +#pragma mark CCLayer + +/** CCLayer is a subclass of CCNode that implements the TouchEventsDelegate protocol. + + All features from CCNode are valid, plus the following new features: + - It can receive iPhone Touches + - It can receive Accelerometer input +*/ +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +@interface CCLayer : CCNode +{ + BOOL isTouchEnabled_; + BOOL isAccelerometerEnabled_; +} +/** If isTouchEnabled, this method is called onEnter. Override it to change the + way CCLayer receives touch events. + ( Default: [[TouchDispatcher sharedDispatcher] addStandardDelegate:self priority:0] ) + Example: + -(void) registerWithTouchDispatcher + { + [[TouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:INT_MIN+1 swallowsTouches:YES]; + } + + Valid only on iOS. Not valid on Mac. + + @since v0.8.0 + */ +-(void) registerWithTouchDispatcher; + +/** whether or not it will receive Touch events. + You can enable / disable touch events with this property. + Only the touches of this node will be affected. This "method" is not propagated to it's children. + + Valid on iOS and Mac OS X v10.6 and later. + + @since v0.8.1 + */ +@property(nonatomic,assign) BOOL isTouchEnabled; +/** whether or not it will receive Accelerometer events + You can enable / disable accelerometer events with this property. + + Valid only on iOS. Not valid on Mac. + + @since v0.8.1 + */ +@property(nonatomic,assign) BOOL isAccelerometerEnabled; + +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) + + +@interface CCLayer : CCNode +{ + BOOL isMouseEnabled_; + BOOL isKeyboardEnabled_; + BOOL isTouchEnabled_; +} + +/** whether or not it will receive mouse events. + + Valind only Mac. Not valid on iOS + */ +@property (nonatomic, readwrite) BOOL isMouseEnabled; + +/** whether or not it will receive keyboard events. + + Valind only Mac. Not valid on iOS + */ +@property (nonatomic, readwrite) BOOL isKeyboardEnabled; + +/** whether or not it will receive touch events. + + Valid on iOS and Mac OS X v10.6 and later. + */ +@property (nonatomic, readwrite) BOOL isTouchEnabled; + +/** priority of the mouse event delegate. + Default 0. + Override this method to set another priority. + + Valind only Mac. Not valid on iOS + */ +-(NSInteger) mouseDelegatePriority; + +/** priority of the keyboard event delegate. + Default 0. + Override this method to set another priority. + + Valind only Mac. Not valid on iOS + */ +-(NSInteger) keyboardDelegatePriority; + +/** priority of the touch event delegate. + Default 0. + Override this method to set another priority. + + Valind only Mac. Not valid on iOS + */ +-(NSInteger) touchDelegatePriority; + +#endif // mac + + +@end + +#pragma mark - +#pragma mark CCLayerColor + +/** CCLayerColor is a subclass of CCLayer that implements the CCRGBAProtocol protocol. + + All features from CCLayer are valid, plus the following new features: + - opacity + - RGB colors + */ +@interface CCLayerColor : CCLayer +{ + GLubyte opacity_; + ccColor3B color_; + ccVertex2F squareVertices_[4]; + ccColor4B squareColors_[4]; + + ccBlendFunc blendFunc_; +} + +/** creates a CCLayer with color, width and height in Points*/ ++ (id) layerWithColor: (ccColor4B)color width:(GLfloat)w height:(GLfloat)h; +/** creates a CCLayer with color. Width and height are the window size. */ ++ (id) layerWithColor: (ccColor4B)color; + +/** initializes a CCLayer with color, width and height in Points */ +- (id) initWithColor:(ccColor4B)color width:(GLfloat)w height:(GLfloat)h; +/** initializes a CCLayer with color. Width and height are the window size. */ +- (id) initWithColor:(ccColor4B)color; + +/** change width in Points */ +-(void) changeWidth: (GLfloat)w; +/** change height in Points */ +-(void) changeHeight: (GLfloat)h; +/** change width and height in Points + @since v0.8 + */ +-(void) changeWidth:(GLfloat)w height:(GLfloat)h; + +/** Opacity: conforms to CCRGBAProtocol protocol */ +@property (nonatomic,readonly) GLubyte opacity; +/** Opacity: conforms to CCRGBAProtocol protocol */ +@property (nonatomic,readonly) ccColor3B color; +/** BlendFunction. Conforms to CCBlendProtocol protocol */ +@property (nonatomic,readwrite) ccBlendFunc blendFunc; +@end + +/** CCColorLayer + It is the same as CCLayerColor. + + @deprecated Use CCLayerColor instead. This class will be removed in v1.0.1 + */ +DEPRECATED_ATTRIBUTE @interface CCColorLayer : CCLayerColor +@end + +#pragma mark - +#pragma mark CCLayerGradient + +/** CCLayerGradient is a subclass of CCLayerColor that draws gradients across +the background. + + All features from CCLayerColor are valid, plus the following new features: + - direction + - final color + - interpolation mode + + Color is interpolated between the startColor and endColor along the given + vector (starting at the origin, ending at the terminus). If no vector is + supplied, it defaults to (0, -1) -- a fade from top to bottom. + + If 'compressedInterpolation' is disabled, you will not see either the start or end color for + non-cardinal vectors; a smooth gradient implying both end points will be still + be drawn, however. + + If ' compressedInterpolation' is enabled (default mode) you will see both the start and end colors of the gradient. + + @since v0.99.5 + */ +@interface CCLayerGradient : CCLayerColor +{ + ccColor3B endColor_; + GLubyte startOpacity_; + GLubyte endOpacity_; + CGPoint vector_; + BOOL compressedInterpolation_; +} + +/** Creates a full-screen CCLayer with a gradient between start and end. */ ++ (id) layerWithColor: (ccColor4B) start fadingTo: (ccColor4B) end; +/** Creates a full-screen CCLayer with a gradient between start and end in the direction of v. */ ++ (id) layerWithColor: (ccColor4B) start fadingTo: (ccColor4B) end alongVector: (CGPoint) v; + +/** Initializes the CCLayer with a gradient between start and end. */ +- (id) initWithColor: (ccColor4B) start fadingTo: (ccColor4B) end; +/** Initializes the CCLayer with a gradient between start and end in the direction of v. */ +- (id) initWithColor: (ccColor4B) start fadingTo: (ccColor4B) end alongVector: (CGPoint) v; + +/** The starting color. */ +@property (nonatomic, readwrite) ccColor3B startColor; +/** The ending color. */ +@property (nonatomic, readwrite) ccColor3B endColor; +/** The starting opacity. */ +@property (nonatomic, readwrite) GLubyte startOpacity; +/** The ending color. */ +@property (nonatomic, readwrite) GLubyte endOpacity; +/** The vector along which to fade color. */ +@property (nonatomic, readwrite) CGPoint vector; +/** Whether or not the interpolation will be compressed in order to display all the colors of the gradient both in canonical and non canonical vectors + Default: YES + */ +@property (nonatomic, readwrite) BOOL compressedInterpolation; + +@end + +#pragma mark - +#pragma mark CCLayerMultiplex + +/** CCLayerMultiplex is a CCLayer with the ability to multiplex it's children. + Features: + - It supports one or more children + - Only one children will be active a time + */ +@interface CCLayerMultiplex : CCLayer +{ + unsigned int enabledLayer_; + NSMutableArray *layers_; +} + +/** creates a CCMultiplexLayer with one or more layers using a variable argument list. */ ++(id) layerWithLayers: (CCLayer*) layer, ... NS_REQUIRES_NIL_TERMINATION; +/** initializes a MultiplexLayer with one or more layers using a variable argument list. */ +-(id) initWithLayers: (CCLayer*) layer vaList:(va_list) params; +/** switches to a certain layer indexed by n. + The current (old) layer will be removed from it's parent with 'cleanup:YES'. + */ +-(void) switchTo: (unsigned int) n; +/** release the current layer and switches to another layer indexed by n. + The current (old) layer will be removed from it's parent with 'cleanup:YES'. + */ +-(void) switchToAndReleaseMe: (unsigned int) n; +@end + +/** CCMultiplexLayer + It is the same as CCLayerMultiplex. + + @deprecated Use CCLayerMultiplex instead. This class will be removed in v1.0.1 + */ +DEPRECATED_ATTRIBUTE @interface CCMultiplexLayer : CCLayerMultiplex +@end + diff --git a/libs/cocos2d/CCLayer.m b/libs/cocos2d/CCLayer.m new file mode 100644 index 0000000..68cc14f --- /dev/null +++ b/libs/cocos2d/CCLayer.m @@ -0,0 +1,619 @@ +/* + * 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. + * + */ + + +#import + +#import "Platforms/CCGL.h" + +#import "CCLayer.h" +#import "CCDirector.h" +#import "ccMacros.h" +#import "Support/CGPointExtension.h" + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +#import "Platforms/iOS/CCTouchDispatcher.h" +#import "Platforms/iOS/CCDirectorIOS.h" +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) +#import "Platforms/Mac/CCEventDispatcher.h" +#endif + +#pragma mark - +#pragma mark Layer + +@implementation CCLayer + +#pragma mark Layer - Init +-(id) init +{ + if( (self=[super init]) ) { + + CGSize s = [[CCDirector sharedDirector] winSize]; + anchorPoint_ = ccp(0.5f, 0.5f); + [self setContentSize:s]; + self.isRelativeAnchorPoint = NO; + + isTouchEnabled_ = NO; + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + isAccelerometerEnabled_ = NO; +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) + isMouseEnabled_ = NO; + isKeyboardEnabled_ = NO; +#endif + } + + return self; +} + +#pragma mark Layer - Touch and Accelerometer related + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +-(void) registerWithTouchDispatcher +{ + [[CCTouchDispatcher sharedDispatcher] addStandardDelegate:self priority:0]; +} + +-(BOOL) isAccelerometerEnabled +{ + return isAccelerometerEnabled_; +} + +-(void) setIsAccelerometerEnabled:(BOOL)enabled +{ + if( enabled != isAccelerometerEnabled_ ) { + isAccelerometerEnabled_ = enabled; + if( isRunning_ ) { + if( enabled ) + [[UIAccelerometer sharedAccelerometer] setDelegate:self]; + else + [[UIAccelerometer sharedAccelerometer] setDelegate:nil]; + } + } +} + +-(BOOL) isTouchEnabled +{ + return isTouchEnabled_; +} + +-(void) setIsTouchEnabled:(BOOL)enabled +{ + if( isTouchEnabled_ != enabled ) { + isTouchEnabled_ = enabled; + if( isRunning_ ) { + if( enabled ) + [self registerWithTouchDispatcher]; + else + [[CCTouchDispatcher sharedDispatcher] removeDelegate:self]; + } + } +} + +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) + +#pragma mark CCLayer - Mouse, Keyboard & Touch events + +-(NSInteger) mouseDelegatePriority +{ + return 0; +} + +-(BOOL) isMouseEnabled +{ + return isMouseEnabled_; +} + +-(void) setIsMouseEnabled:(BOOL)enabled +{ + if( isMouseEnabled_ != enabled ) { + isMouseEnabled_ = enabled; + + if( isRunning_ ) { + if( enabled ) + [[CCEventDispatcher sharedDispatcher] addMouseDelegate:self priority:[self mouseDelegatePriority]]; + else + [[CCEventDispatcher sharedDispatcher] removeMouseDelegate:self]; + } + } +} + +-(NSInteger) keyboardDelegatePriority +{ + return 0; +} + +-(BOOL) isKeyboardEnabled +{ + return isKeyboardEnabled_; +} + +-(void) setIsKeyboardEnabled:(BOOL)enabled +{ + if( isKeyboardEnabled_ != enabled ) { + isKeyboardEnabled_ = enabled; + + if( isRunning_ ) { + if( enabled ) + [[CCEventDispatcher sharedDispatcher] addKeyboardDelegate:self priority:[self keyboardDelegatePriority] ]; + else + [[CCEventDispatcher sharedDispatcher] removeKeyboardDelegate:self]; + } + } +} + +-(NSInteger) touchDelegatePriority +{ + return 0; +} + +-(BOOL) isTouchEnabled +{ + return isTouchEnabled_; +} + +-(void) setIsTouchEnabled:(BOOL)enabled +{ + if( isTouchEnabled_ != enabled ) { + isTouchEnabled_ = enabled; + if( isRunning_ ) { + if( enabled ) + [[CCEventDispatcher sharedDispatcher] addTouchDelegate:self priority:[self touchDelegatePriority]]; + else + [[CCEventDispatcher sharedDispatcher] removeTouchDelegate:self]; + } + } +} + + +#endif // Mac + + +#pragma mark Layer - Callbacks +-(void) onEnter +{ +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + // register 'parent' nodes first + // since events are propagated in reverse order + if (isTouchEnabled_) + [self registerWithTouchDispatcher]; + +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) + if( isMouseEnabled_ ) + [[CCEventDispatcher sharedDispatcher] addMouseDelegate:self priority:[self mouseDelegatePriority]]; + + if( isKeyboardEnabled_) + [[CCEventDispatcher sharedDispatcher] addKeyboardDelegate:self priority:[self keyboardDelegatePriority]]; + + if( isTouchEnabled_) + [[CCEventDispatcher sharedDispatcher] addTouchDelegate:self priority:[self touchDelegatePriority]]; + +#endif + + // then iterate over all the children + [super onEnter]; +} + +// issue #624. +// Can't register mouse, touches here because of #issue #1018, and #1021 +-(void) onEnterTransitionDidFinish +{ +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + if( isAccelerometerEnabled_ ) + [[UIAccelerometer sharedAccelerometer] setDelegate:self]; +#endif + + [super onEnterTransitionDidFinish]; +} + + +-(void) onExit +{ +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + if( isTouchEnabled_ ) + [[CCTouchDispatcher sharedDispatcher] removeDelegate:self]; + + if( isAccelerometerEnabled_ ) + [[UIAccelerometer sharedAccelerometer] setDelegate:nil]; + +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) + if( isMouseEnabled_ ) + [[CCEventDispatcher sharedDispatcher] removeMouseDelegate:self]; + + if( isKeyboardEnabled_ ) + [[CCEventDispatcher sharedDispatcher] removeKeyboardDelegate:self]; + + if( isTouchEnabled_ ) + [[CCEventDispatcher sharedDispatcher] removeTouchDelegate:self]; + +#endif + + [super onExit]; +} + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event +{ + NSAssert(NO, @"Layer#ccTouchBegan override me"); + return YES; +} +#endif +@end + +#pragma mark - +#pragma mark LayerColor + +@interface CCLayerColor (Private) +-(void) updateColor; +@end + +@implementation CCLayerColor + +// Opacity and RGB color protocol +@synthesize opacity = opacity_, color = color_; +@synthesize blendFunc = blendFunc_; + + ++ (id) layerWithColor:(ccColor4B)color width:(GLfloat)w height:(GLfloat) h +{ + return [[[self alloc] initWithColor:color width:w height:h] autorelease]; +} + ++ (id) layerWithColor:(ccColor4B)color +{ + return [[(CCLayerColor*)[self alloc] initWithColor:color] autorelease]; +} + +- (id) initWithColor:(ccColor4B)color width:(GLfloat)w height:(GLfloat) h +{ + if( (self=[super init]) ) { + + // default blend function + blendFunc_ = (ccBlendFunc) { CC_BLEND_SRC, CC_BLEND_DST }; + + color_.r = color.r; + color_.g = color.g; + color_.b = color.b; + opacity_ = color.a; + + for (NSUInteger i = 0; i +{ + tCCMenuState state_; + CCMenuItem *selectedItem_; + GLubyte opacity_; + ccColor3B color_; +} + +/** creates a CCMenu with it's items */ ++ (id) menuWithItems: (CCMenuItem*) item, ... NS_REQUIRES_NIL_TERMINATION; + +/** initializes a CCMenu with it's items */ +- (id) initWithItems: (CCMenuItem*) item vaList: (va_list) args; + +/** align items vertically */ +-(void) alignItemsVertically; +/** align items vertically with padding + @since v0.7.2 + */ +-(void) alignItemsVerticallyWithPadding:(float) padding; + +/** align items horizontally */ +-(void) alignItemsHorizontally; +/** align items horizontally with padding + @since v0.7.2 + */ +-(void) alignItemsHorizontallyWithPadding: (float) padding; + + +/** align items in rows of columns */ +-(void) alignItemsInColumns: (NSNumber *) columns, ... NS_REQUIRES_NIL_TERMINATION; +-(void) alignItemsInColumns: (NSNumber *) columns vaList: (va_list) args; + +/** align items in columns of rows */ +-(void) alignItemsInRows: (NSNumber *) rows, ... NS_REQUIRES_NIL_TERMINATION; +-(void) alignItemsInRows: (NSNumber *) rows vaList: (va_list) args; + + +/** conforms to CCRGBAProtocol protocol */ +@property (nonatomic,readonly) GLubyte opacity; +/** conforms to CCRGBAProtocol protocol */ +@property (nonatomic,readonly) ccColor3B color; + +@end diff --git a/libs/cocos2d/CCMenu.m b/libs/cocos2d/CCMenu.m new file mode 100644 index 0000000..a624154 --- /dev/null +++ b/libs/cocos2d/CCMenu.m @@ -0,0 +1,523 @@ +/* + * 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. + * + */ + + + +#import "CCMenu.h" +#import "CCDirector.h" +#import "Support/CGPointExtension.h" +#import "ccMacros.h" + +#import +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +#import "Platforms/iOS/CCDirectorIOS.h" +#import "Platforms/iOS/CCTouchDispatcher.h" +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) +#import "Platforms/Mac/MacGLView.h" +#import "Platforms/Mac/CCDirectorMac.h" +#endif + +enum { + kDefaultPadding = 5, +}; + +@implementation CCMenu + +@synthesize opacity = opacity_, color = color_; + +- (id) init +{ + NSAssert(NO, @"CCMenu: Init not supported."); + [self release]; + return nil; +} + ++(id) menuWithItems: (CCMenuItem*) item, ... +{ + va_list args; + va_start(args,item); + + id s = [[[self alloc] initWithItems: item vaList:args] autorelease]; + + va_end(args); + return s; +} + +-(id) initWithItems: (CCMenuItem*) item vaList: (va_list) args +{ + if( (self=[super init]) ) { + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + self.isTouchEnabled = YES; +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) + self.isMouseEnabled = YES; +#endif + + // menu in the center of the screen + CGSize s = [[CCDirector sharedDirector] winSize]; + + self.isRelativeAnchorPoint = NO; + anchorPoint_ = ccp(0.5f, 0.5f); + [self setContentSize:s]; + + // XXX: in v0.7, winSize should return the visible size + // XXX: so the bar calculation should be done there +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + CGRect r = [[UIApplication sharedApplication] statusBarFrame]; + ccDeviceOrientation orientation = [[CCDirector sharedDirector] deviceOrientation]; + if( orientation == CCDeviceOrientationLandscapeLeft || orientation == CCDeviceOrientationLandscapeRight ) + s.height -= r.size.width; + else + s.height -= r.size.height; +#endif + self.position = ccp(s.width/2, s.height/2); + + int z=0; + + if (item) { + [self addChild: item z:z]; + CCMenuItem *i = va_arg(args, CCMenuItem*); + while(i) { + z++; + [self addChild: i z:z]; + i = va_arg(args, CCMenuItem*); + } + } + // [self alignItemsVertically]; + + selectedItem_ = nil; + state_ = kCCMenuStateWaiting; + } + + return self; +} + +-(void) dealloc +{ + [super dealloc]; +} + +/* + * override add: + */ +-(void) addChild:(CCMenuItem*)child z:(NSInteger)z tag:(NSInteger) aTag +{ + NSAssert( [child isKindOfClass:[CCMenuItem class]], @"Menu only supports MenuItem objects as children"); + [super addChild:child z:z tag:aTag]; +} + +- (void) onExit +{ + if(state_ == kCCMenuStateTrackingTouch) + { + [selectedItem_ unselected]; + state_ = kCCMenuStateWaiting; + selectedItem_ = nil; + } + [super onExit]; +} + +#pragma mark Menu - Touches + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +-(void) registerWithTouchDispatcher +{ + [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:kCCMenuTouchPriority swallowsTouches:YES]; +} + +-(CCMenuItem *) itemForTouch: (UITouch *) touch +{ + CGPoint touchLocation = [touch locationInView: [touch view]]; + touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation]; + + CCMenuItem* item; + CCARRAY_FOREACH(children_, item){ + // ignore invisible and disabled items: issue #779, #866 + if ( [item visible] && [item isEnabled] ) { + + CGPoint local = [item convertToNodeSpace:touchLocation]; + CGRect r = [item rect]; + r.origin = CGPointZero; + + if( CGRectContainsPoint( r, local ) ) + return item; + } + } + return nil; +} + +-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event +{ + if( state_ != kCCMenuStateWaiting || !visible_ ) + return NO; + + selectedItem_ = [self itemForTouch:touch]; + [selectedItem_ selected]; + + if( selectedItem_ ) { + state_ = kCCMenuStateTrackingTouch; + return YES; + } + return NO; +} + +-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event +{ + NSAssert(state_ == kCCMenuStateTrackingTouch, @"[Menu ccTouchEnded] -- invalid state"); + + [selectedItem_ unselected]; + [selectedItem_ activate]; + + state_ = kCCMenuStateWaiting; +} + +-(void) ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event +{ + NSAssert(state_ == kCCMenuStateTrackingTouch, @"[Menu ccTouchCancelled] -- invalid state"); + + [selectedItem_ unselected]; + + state_ = kCCMenuStateWaiting; +} + +-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event +{ + NSAssert(state_ == kCCMenuStateTrackingTouch, @"[Menu ccTouchMoved] -- invalid state"); + + CCMenuItem *currentItem = [self itemForTouch:touch]; + + if (currentItem != selectedItem_) { + [selectedItem_ unselected]; + selectedItem_ = currentItem; + [selectedItem_ selected]; + } +} + +#pragma mark Menu - Mouse + +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) + +-(NSInteger) mouseDelegatePriority +{ + return kCCMenuMousePriority+1; +} + +-(CCMenuItem *) itemForMouseEvent: (NSEvent *) event +{ + CGPoint location = [(CCDirectorMac*)[CCDirector sharedDirector] convertEventToGL:event]; + + CCMenuItem* item; + CCARRAY_FOREACH(children_, item){ + // ignore invisible and disabled items: issue #779, #866 + if ( [item visible] && [item isEnabled] ) { + + CGPoint local = [item convertToNodeSpace:location]; + + CGRect r = [item rect]; + r.origin = CGPointZero; + + if( CGRectContainsPoint( r, local ) ) + return item; + } + } + return nil; +} + +-(BOOL) ccMouseUp:(NSEvent *)event +{ + if( ! visible_ ) + return NO; + + if(state_ == kCCMenuStateTrackingTouch) { + if( selectedItem_ ) { + [selectedItem_ unselected]; + [selectedItem_ activate]; + } + state_ = kCCMenuStateWaiting; + + return YES; + } + return NO; +} + +-(BOOL) ccMouseDown:(NSEvent *)event +{ + if( ! visible_ ) + return NO; + + selectedItem_ = [self itemForMouseEvent:event]; + [selectedItem_ selected]; + + if( selectedItem_ ) { + state_ = kCCMenuStateTrackingTouch; + return YES; + } + + return NO; +} + +-(BOOL) ccMouseDragged:(NSEvent *)event +{ + if( ! visible_ ) + return NO; + + if(state_ == kCCMenuStateTrackingTouch) { + CCMenuItem *currentItem = [self itemForMouseEvent:event]; + + if (currentItem != selectedItem_) { + [selectedItem_ unselected]; + selectedItem_ = currentItem; + [selectedItem_ selected]; + } + + return YES; + } + return NO; +} + +#endif // Mac Mouse support + +#pragma mark Menu - Alignment +-(void) alignItemsVertically +{ + return [self alignItemsVerticallyWithPadding:kDefaultPadding]; +} +-(void) alignItemsVerticallyWithPadding:(float)padding +{ + float height = -padding; + + CCMenuItem *item; + CCARRAY_FOREACH(children_, item) + height += item.contentSize.height * item.scaleY + padding; + + float y = height / 2.0f; + + CCARRAY_FOREACH(children_, item) { + CGSize itemSize = item.contentSize; + [item setPosition:ccp(0, y - itemSize.height * item.scaleY / 2.0f)]; + y -= itemSize.height * item.scaleY + padding; + } +} + +-(void) alignItemsHorizontally +{ + return [self alignItemsHorizontallyWithPadding:kDefaultPadding]; +} + +-(void) alignItemsHorizontallyWithPadding:(float)padding +{ + + float width = -padding; + CCMenuItem *item; + CCARRAY_FOREACH(children_, item) + width += item.contentSize.width * item.scaleX + padding; + + float x = -width / 2.0f; + + CCARRAY_FOREACH(children_, item){ + CGSize itemSize = item.contentSize; + [item setPosition:ccp(x + itemSize.width * item.scaleX / 2.0f, 0)]; + x += itemSize.width * item.scaleX + padding; + } +} + +-(void) alignItemsInColumns: (NSNumber *) columns, ... +{ + va_list args; + va_start(args, columns); + + [self alignItemsInColumns:columns vaList:args]; + + va_end(args); +} + +-(void) alignItemsInColumns: (NSNumber *) columns vaList: (va_list) args +{ + NSMutableArray *rows = [[NSMutableArray alloc] initWithObjects:columns, nil]; + columns = va_arg(args, NSNumber*); + while(columns) { + [rows addObject:columns]; + columns = va_arg(args, NSNumber*); + } + + int height = -5; + NSUInteger row = 0, rowHeight = 0, columnsOccupied = 0, rowColumns; + CCMenuItem *item; + CCARRAY_FOREACH(children_, item){ + NSAssert( row < [rows count], @"Too many menu items for the amount of rows/columns."); + + rowColumns = [(NSNumber *) [rows objectAtIndex:row] unsignedIntegerValue]; + NSAssert( rowColumns, @"Can't have zero columns on a row"); + + rowHeight = fmaxf(rowHeight, item.contentSize.height); + ++columnsOccupied; + + if(columnsOccupied >= rowColumns) { + height += rowHeight + 5; + + columnsOccupied = 0; + rowHeight = 0; + ++row; + } + } + NSAssert( !columnsOccupied, @"Too many rows/columns for available menu items." ); + + CGSize winSize = [[CCDirector sharedDirector] winSize]; + + row = 0; rowHeight = 0; rowColumns = 0; + float w, x, y = height / 2; + CCARRAY_FOREACH(children_, item) { + if(rowColumns == 0) { + rowColumns = [(NSNumber *) [rows objectAtIndex:row] unsignedIntegerValue]; + w = winSize.width / (1 + rowColumns); + x = w; + } + + CGSize itemSize = item.contentSize; + rowHeight = fmaxf(rowHeight, itemSize.height); + [item setPosition:ccp(x - winSize.width / 2, + y - itemSize.height / 2)]; + + x += w + 10; + ++columnsOccupied; + + if(columnsOccupied >= rowColumns) { + y -= rowHeight + 5; + + columnsOccupied = 0; + rowColumns = 0; + rowHeight = 0; + ++row; + } + } + + [rows release]; +} + +-(void) alignItemsInRows: (NSNumber *) rows, ... +{ + va_list args; + va_start(args, rows); + + [self alignItemsInRows:rows vaList:args]; + + va_end(args); +} + +-(void) alignItemsInRows: (NSNumber *) rows vaList: (va_list) args +{ + NSMutableArray *columns = [[NSMutableArray alloc] initWithObjects:rows, nil]; + rows = va_arg(args, NSNumber*); + while(rows) { + [columns addObject:rows]; + rows = va_arg(args, NSNumber*); + } + + NSMutableArray *columnWidths = [[NSMutableArray alloc] init]; + NSMutableArray *columnHeights = [[NSMutableArray alloc] init]; + + int width = -10, columnHeight = -5; + NSUInteger column = 0, columnWidth = 0, rowsOccupied = 0, columnRows; + CCMenuItem *item; + CCARRAY_FOREACH(children_, item){ + NSAssert( column < [columns count], @"Too many menu items for the amount of rows/columns."); + + columnRows = [(NSNumber *) [columns objectAtIndex:column] unsignedIntegerValue]; + NSAssert( columnRows, @"Can't have zero rows on a column"); + + CGSize itemSize = item.contentSize; + columnWidth = fmaxf(columnWidth, itemSize.width); + columnHeight += itemSize.height + 5; + ++rowsOccupied; + + if(rowsOccupied >= columnRows) { + [columnWidths addObject:[NSNumber numberWithUnsignedInteger:columnWidth]]; + [columnHeights addObject:[NSNumber numberWithUnsignedInteger:columnHeight]]; + width += columnWidth + 10; + + rowsOccupied = 0; + columnWidth = 0; + columnHeight = -5; + ++column; + } + } + NSAssert( !rowsOccupied, @"Too many rows/columns for available menu items."); + + CGSize winSize = [[CCDirector sharedDirector] winSize]; + + column = 0; columnWidth = 0; columnRows = 0; + float x = -width / 2, y; + + CCARRAY_FOREACH(children_, item){ + if(columnRows == 0) { + columnRows = [(NSNumber *) [columns objectAtIndex:column] unsignedIntegerValue]; + y = ([(NSNumber *) [columnHeights objectAtIndex:column] intValue] + winSize.height) / 2; + } + + CGSize itemSize = item.contentSize; + columnWidth = fmaxf(columnWidth, itemSize.width); + [item setPosition:ccp(x + [(NSNumber *) [columnWidths objectAtIndex:column] unsignedIntegerValue] / 2, + y - winSize.height / 2)]; + + y -= itemSize.height + 10; + ++rowsOccupied; + + if(rowsOccupied >= columnRows) { + x += columnWidth + 5; + + rowsOccupied = 0; + columnRows = 0; + columnWidth = 0; + ++column; + } + } + + [columns release]; + [columnWidths release]; + [columnHeights release]; +} + +#pragma mark Menu - Opacity Protocol + +/** Override synthesized setOpacity to recurse items */ +- (void) setOpacity:(GLubyte)newOpacity +{ + opacity_ = newOpacity; + + id item; + CCARRAY_FOREACH(children_, item) + [item setOpacity:opacity_]; +} + +-(void) setColor:(ccColor3B)color +{ + color_ = color; + + id item; + CCARRAY_FOREACH(children_, item) + [item setColor:color_]; +} +@end diff --git a/libs/cocos2d/CCMenuItem.h b/libs/cocos2d/CCMenuItem.h new file mode 100644 index 0000000..d78af7f --- /dev/null +++ b/libs/cocos2d/CCMenuItem.h @@ -0,0 +1,363 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2008-2011 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 "CCBlockSupport.h" + +#import "CCNode.h" +#import "CCProtocols.h" + +@class CCSprite; + +#define kItemSize 32 + +#pragma mark - +#pragma mark CCMenuItem +/** CCMenuItem base class + * + * Subclass CCMenuItem (or any subclass) to create your custom CCMenuItem objects. + */ +@interface CCMenuItem : CCNode +{ + NSInvocation *invocation; +#if NS_BLOCKS_AVAILABLE + // used for menu items using a block + void (^block_)(id sender); +#endif + + BOOL isEnabled_; + BOOL isSelected_; +} + +/** returns whether or not the item is selected +@since v0.8.2 +*/ +@property (nonatomic,readonly) BOOL isSelected; + +/** Creates a CCMenuItem with a target/selector */ ++(id) itemWithTarget:(id)target selector:(SEL)selector; + +/** Initializes a CCMenuItem with a target/selector */ +-(id) initWithTarget:(id)target selector:(SEL)selector; + +#if NS_BLOCKS_AVAILABLE +/** Creates a CCMenuItem with the specified block. + The block will be "copied". + */ ++(id) itemWithBlock:(void(^)(id sender))block; + +/** Initializes a CCMenuItem with the specified block. + The block will be "copied". +*/ +-(id) initWithBlock:(void(^)(id sender))block; +#endif + +/** Returns the outside box in points */ +-(CGRect) rect; + +/** Activate the item */ +-(void) activate; + +/** The item was selected (not activated), similar to "mouse-over" */ +-(void) selected; + +/** The item was unselected */ +-(void) unselected; + +/** Enable or disabled the CCMenuItem */ +-(void) setIsEnabled:(BOOL)enabled; +/** Returns whether or not the CCMenuItem is enabled */ +-(BOOL) isEnabled; +@end + +#pragma mark - +#pragma mark CCMenuItemLabel + +/** An abstract class for "label" CCMenuItemLabel items + Any CCNode that supports the CCLabelProtocol protocol can be added. + Supported nodes: + - CCLabelBMFont + - CCLabelAtlas + - CCLabelTTF + */ +@interface CCMenuItemLabel : CCMenuItem +{ + CCNode *label_; + ccColor3B colorBackup; + ccColor3B disabledColor_; + float originalScale_; +} + +/** the color that will be used to disable the item */ +@property (nonatomic,readwrite) ccColor3B disabledColor; + +/** Label that is rendered. It can be any CCNode that implements the CCLabelProtocol */ +@property (nonatomic,readwrite,assign) CCNode* label; + +/** creates a CCMenuItemLabel with a Label. Target and selector will be nill */ ++(id) itemWithLabel:(CCNode*)label; + +/** creates a CCMenuItemLabel with a Label, target and selector */ ++(id) itemWithLabel:(CCNode*)label target:(id)target selector:(SEL)selector; + +/** initializes a CCMenuItemLabel with a Label, target and selector */ +-(id) initWithLabel:(CCNode*)label target:(id)target selector:(SEL)selector; + +#if NS_BLOCKS_AVAILABLE +/** creates a CCMenuItemLabel with a Label and a block to execute. + The block will be "copied". + */ ++(id) itemWithLabel:(CCNode*)label block:(void(^)(id sender))block; + +/** initializes a CCMenuItemLabel with a Label and a block to execute. + The block will be "copied". + */ +-(id) initWithLabel:(CCNode*)label block:(void(^)(id sender))block; +#endif + +/** sets a new string to the inner label */ +-(void) setString:(NSString*)label; + +/** Enable or disabled the CCMenuItemFont + @warning setIsEnabled changes the RGB color of the font + */ +-(void) setIsEnabled: (BOOL)enabled; +@end + +#pragma mark - +#pragma mark CCMenuItemAtlasFont + +/** A CCMenuItemAtlasFont + Helper class that creates a MenuItemLabel class with a LabelAtlas + */ +@interface CCMenuItemAtlasFont : CCMenuItemLabel +{ +} + +/** creates a menu item from a string and atlas with a target/selector */ ++(id) itemFromString: (NSString*) value charMapFile:(NSString*) charMapFile itemWidth:(int)itemWidth itemHeight:(int)itemHeight startCharMap:(char)startCharMap; + +/** creates a menu item from a string and atlas. Use it with MenuItemToggle */ ++(id) itemFromString: (NSString*) value charMapFile:(NSString*) charMapFile itemWidth:(int)itemWidth itemHeight:(int)itemHeight startCharMap:(char)startCharMap target:(id) rec selector:(SEL) cb; + +/** initializes a menu item from a string and atlas with a target/selector */ +-(id) initFromString: (NSString*) value charMapFile:(NSString*) charMapFile itemWidth:(int)itemWidth itemHeight:(int)itemHeight startCharMap:(char)startCharMap target:(id) rec selector:(SEL) cb; + +#if NS_BLOCKS_AVAILABLE +/** creates a menu item from a string and atlas. Use it with MenuItemToggle. + The block will be "copied". + */ ++(id) itemFromString: (NSString*) value charMapFile:(NSString*) charMapFile itemWidth:(int)itemWidth itemHeight:(int)itemHeight startCharMap:(char)startCharMap block:(void(^)(id sender))block; + +/** initializes a menu item from a string and atlas with a block. + The block will be "copied". + */ +-(id) initFromString: (NSString*) value charMapFile:(NSString*) charMapFile itemWidth:(int)itemWidth itemHeight:(int)itemHeight startCharMap:(char)startCharMap block:(void(^)(id sender))block; +#endif + +@end + +#pragma mark - +#pragma mark CCMenuItemFont + +/** A CCMenuItemFont + Helper class that creates a CCMenuItemLabel class with a Label + */ +@interface CCMenuItemFont : CCMenuItemLabel +{ +} +/** set font size */ ++(void) setFontSize: (int) s; + +/** get font size */ ++(int) fontSize; + +/** set the font name */ ++(void) setFontName: (NSString*) n; + +/** get the font name */ ++(NSString*) fontName; + +/** creates a menu item from a string without target/selector. To be used with CCMenuItemToggle */ ++(id) itemFromString: (NSString*) value; + +/** creates a menu item from a string with a target/selector */ ++(id) itemFromString: (NSString*) value target:(id) r selector:(SEL) s; + +/** initializes a menu item from a string with a target/selector */ +-(id) initFromString: (NSString*) value target:(id) r selector:(SEL) s; + +#if NS_BLOCKS_AVAILABLE +/** creates a menu item from a string with the specified block. + The block will be "copied". + */ ++(id) itemFromString: (NSString*) value block:(void(^)(id sender))block; + +/** initializes a menu item from a string with the specified block. + The block will be "copied". + */ +-(id) initFromString: (NSString*) value block:(void(^)(id sender))block; +#endif +@end + +#pragma mark - +#pragma mark CCMenuItemSprite + +/** CCMenuItemSprite accepts CCNode objects as items. + The images has 3 different states: + - unselected image + - selected image + - disabled image + + @since v0.8.0 + */ +@interface CCMenuItemSprite : CCMenuItem +{ + CCNode *normalImage_, *selectedImage_, *disabledImage_; +} + +// weak references + +/** the image used when the item is not selected */ +@property (nonatomic,readwrite,assign) CCNode *normalImage; +/** the image used when the item is selected */ +@property (nonatomic,readwrite,assign) CCNode *selectedImage; +/** the image used when the item is disabled */ +@property (nonatomic,readwrite,assign) CCNode *disabledImage; + +/** creates a menu item with a normal and selected image*/ ++(id) itemFromNormalSprite:(CCNode*)normalSprite selectedSprite:(CCNode*)selectedSprite; +/** creates a menu item with a normal and selected image with target/selector */ ++(id) itemFromNormalSprite:(CCNode*)normalSprite selectedSprite:(CCNode*)selectedSprite target:(id)target selector:(SEL)selector; +/** creates a menu item with a normal,selected and disabled image with target/selector */ ++(id) itemFromNormalSprite:(CCNode*)normalSprite selectedSprite:(CCNode*)selectedSprite disabledSprite:(CCNode*)disabledSprite target:(id)target selector:(SEL)selector; +/** initializes a menu item with a normal, selected and disabled image with target/selector */ +-(id) initFromNormalSprite:(CCNode*)normalSprite selectedSprite:(CCNode*)selectedSprite disabledSprite:(CCNode*)disabledSprite target:(id)target selector:(SEL)selector; + +#if NS_BLOCKS_AVAILABLE +/** creates a menu item with a normal and selected image with a block. + The block will be "copied". + */ ++(id) itemFromNormalSprite:(CCNode*)normalSprite selectedSprite:(CCNode*)selectedSprite block:(void(^)(id sender))block; +/** creates a menu item with a normal,selected and disabled image with a block. + The block will be "copied". + */ ++(id) itemFromNormalSprite:(CCNode*)normalSprite selectedSprite:(CCNode*)selectedSprite disabledSprite:(CCNode*)disabledSprite block:(void(^)(id sender))block; +/** initializes a menu item with a normal, selected and disabled image with a block. + The block will be "copied". + */ +-(id) initFromNormalSprite:(CCNode*)normalSprite selectedSprite:(CCNode*)selectedSprite disabledSprite:(CCNode*)disabledSprite block:(void(^)(id sender))block; +#endif + +@end + +#pragma mark - +#pragma mark CCMenuItemImage + +/** CCMenuItemImage accepts images as items. + The images has 3 different states: + - unselected image + - selected image + - disabled image + + For best results try that all images are of the same size + */ +@interface CCMenuItemImage : CCMenuItemSprite +{ +} + +/** creates a menu item with a normal and selected image*/ ++(id) itemFromNormalImage: (NSString*)value selectedImage:(NSString*) value2; +/** creates a menu item with a normal and selected image with target/selector */ ++(id) itemFromNormalImage: (NSString*)value selectedImage:(NSString*) value2 target:(id) r selector:(SEL) s; +/** creates a menu item with a normal,selected and disabled image with target/selector */ ++(id) itemFromNormalImage: (NSString*)value selectedImage:(NSString*) value2 disabledImage:(NSString*) value3 target:(id) r selector:(SEL) s; +/** initializes a menu item with a normal, selected and disabled image with target/selector */ +-(id) initFromNormalImage: (NSString*) value selectedImage:(NSString*)value2 disabledImage:(NSString*) value3 target:(id) r selector:(SEL) s; +#if NS_BLOCKS_AVAILABLE +/** creates a menu item with a normal and selected image with a block. + The block will be "copied". + */ ++(id) itemFromNormalImage: (NSString*)value selectedImage:(NSString*) value2 block:(void(^)(id sender))block; +/** creates a menu item with a normal,selected and disabled image with a block. + The block will be "copied". +*/ ++(id) itemFromNormalImage: (NSString*)value selectedImage:(NSString*) value2 disabledImage:(NSString*) value3 block:(void(^)(id sender))block; +/** initializes a menu item with a normal, selected and disabled image with a block. + The block will be "copied". +*/ +-(id) initFromNormalImage: (NSString*) value selectedImage:(NSString*)value2 disabledImage:(NSString*) value3 block:(void(^)(id sender))block; +#endif +@end + +#pragma mark - +#pragma mark CCMenuItemToggle + +/** A CCMenuItemToggle + A simple container class that "toggles" it's inner items + The inner itmes can be any MenuItem + */ +@interface CCMenuItemToggle : CCMenuItem +{ + NSUInteger selectedIndex_; + NSMutableArray* subItems_; + GLubyte opacity_; + ccColor3B color_; +} + +/** conforms with CCRGBAProtocol protocol */ +@property (nonatomic,readonly) GLubyte opacity; +/** conforms with CCRGBAProtocol protocol */ +@property (nonatomic,readonly) ccColor3B color; + +/** returns the selected item */ +@property (nonatomic,readwrite) NSUInteger selectedIndex; +/** NSMutableArray that contains the subitems. You can add/remove items in runtime, and you can replace the array with a new one. + @since v0.7.2 + */ +@property (nonatomic,readwrite,retain) NSMutableArray *subItems; + +/** creates a menu item from a list of items with a target/selector */ ++(id) itemWithTarget:(id)t selector:(SEL)s items:(CCMenuItem*) item, ... NS_REQUIRES_NIL_TERMINATION; + +/** initializes a menu item from a list of items with a target selector */ +-(id) initWithTarget:(id)t selector:(SEL)s items:(CCMenuItem*) item vaList:(va_list) args; + +#if NS_BLOCKS_AVAILABLE +/** creates a menu item from a list of items and executes the given block when the item is selected. + The block will be "copied". + */ ++(id) itemWithBlock:(void(^)(id sender))block items:(CCMenuItem*)item, ... NS_REQUIRES_NIL_TERMINATION; + +/** initializes a menu item from a list of items with a block. + The block will be "copied". + */ +-(id) initWithBlock:(void (^)(id))block items:(CCMenuItem*)item vaList:(va_list)args; +#endif + +/** return the selected item */ +-(CCMenuItem*) selectedItem; +@end + diff --git a/libs/cocos2d/CCMenuItem.m b/libs/cocos2d/CCMenuItem.m new file mode 100644 index 0000000..3f8ab2e --- /dev/null +++ b/libs/cocos2d/CCMenuItem.m @@ -0,0 +1,763 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2008-2011 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 "CCMenuItem.h" +#import "CCLabelTTF.h" +#import "CCLabelAtlas.h" +#import "CCActionInterval.h" +#import "CCSprite.h" +#import "Support/CGPointExtension.h" + +static int _fontSize = kItemSize; +static NSString *_fontName = @"Marker Felt"; +static BOOL _fontNameRelease = NO; + +enum { + kCurrentItem = 0xc0c05001, +}; + +enum { + kZoomActionTag = 0xc0c05002, +}; + + + +#pragma mark - +#pragma mark CCMenuItem + +@implementation CCMenuItem + +@synthesize isSelected=isSelected_; +-(id) init +{ + NSAssert(NO, @"MenuItemInit: Init not supported."); + [self release]; + return nil; +} + ++(id) itemWithTarget:(id) r selector:(SEL) s +{ + return [[[self alloc] initWithTarget:r selector:s] autorelease]; +} + +-(id) initWithTarget:(id) rec selector:(SEL) cb +{ + if((self=[super init]) ) { + + anchorPoint_ = ccp(0.5f, 0.5f); + NSMethodSignature * sig = nil; + + if( rec && cb ) { + sig = [rec methodSignatureForSelector:cb]; + + invocation = nil; + invocation = [NSInvocation invocationWithMethodSignature:sig]; + [invocation setTarget:rec]; + [invocation setSelector:cb]; +#if NS_BLOCKS_AVAILABLE + if ([sig numberOfArguments] == 3) +#endif + [invocation setArgument:&self atIndex:2]; + + [invocation retain]; + } + + isEnabled_ = YES; + isSelected_ = NO; + } + + return self; +} + +#if NS_BLOCKS_AVAILABLE + ++(id) itemWithBlock:(void(^)(id sender))block { + return [[[self alloc] initWithBlock:block] autorelease]; +} + +-(id) initWithBlock:(void(^)(id sender))block { + block_ = [block copy]; + return [self initWithTarget:block_ selector:@selector(ccCallbackBlockWithSender:)]; +} + +#endif // NS_BLOCKS_AVAILABLE + +-(void) dealloc +{ + [invocation release]; + +#if NS_BLOCKS_AVAILABLE + [block_ release]; +#endif + + [super dealloc]; +} + +-(void) selected +{ + isSelected_ = YES; +} + +-(void) unselected +{ + isSelected_ = NO; +} + +-(void) activate +{ + if(isEnabled_) + [invocation invoke]; +} + +-(void) setIsEnabled: (BOOL)enabled +{ + isEnabled_ = enabled; +} + +-(BOOL) isEnabled +{ + return isEnabled_; +} + +-(CGRect) rect +{ + return CGRectMake( position_.x - contentSize_.width*anchorPoint_.x, + position_.y - contentSize_.height*anchorPoint_.y, + contentSize_.width, contentSize_.height); +} + +@end + + +#pragma mark - +#pragma mark CCMenuItemLabel + +@implementation CCMenuItemLabel + +@synthesize disabledColor = disabledColor_; + ++(id) itemWithLabel:(CCNode*)label target:(id)target selector:(SEL)selector +{ + return [[[self alloc] initWithLabel:label target:target selector:selector] autorelease]; +} + ++(id) itemWithLabel:(CCNode*)label +{ + return [[[self alloc] initWithLabel:label target:nil selector:NULL] autorelease]; +} + +-(id) initWithLabel:(CCNode*)label target:(id)target selector:(SEL)selector +{ + if( (self=[super initWithTarget:target selector:selector]) ) { + originalScale_ = 1; + colorBackup = ccWHITE; + disabledColor_ = ccc3( 126,126,126); + self.label = label; + + } + return self; +} + +#if NS_BLOCKS_AVAILABLE + ++(id) itemWithLabel:(CCNode*)label block:(void(^)(id sender))block { + return [[[self alloc] initWithLabel:label block:block] autorelease]; +} + +-(id) initWithLabel:(CCNode*)label block:(void(^)(id sender))block { + block_ = [block copy]; + return [self initWithLabel:label target:block_ selector:@selector(ccCallbackBlockWithSender:)]; +} + +#endif // NS_BLOCKS_AVAILABLE + +-(CCNode*) label +{ + return label_; +} +-(void) setLabel:(CCNode*) label +{ + if( label != label_ ) { + [self removeChild:label_ cleanup:YES]; + [self addChild:label]; + + label_ = label; + label_.anchorPoint = ccp(0,0); + + [self setContentSize:[label_ contentSize]]; + } +} + +-(void) setString:(NSString *)string +{ + [label_ setString:string]; + [self setContentSize: [label_ contentSize]]; +} + +-(void) activate { + if(isEnabled_) { + [self stopAllActions]; + + self.scale = originalScale_; + + [super activate]; + } +} + +-(void) selected +{ + // subclass to change the default action + if(isEnabled_) { + [super selected]; + + CCAction *action = [self getActionByTag:kZoomActionTag]; + if( action ) + [self stopAction:action]; + else + originalScale_ = self.scale; + + CCAction *zoomAction = [CCScaleTo actionWithDuration:0.1f scale:originalScale_ * 1.2f]; + zoomAction.tag = kZoomActionTag; + [self runAction:zoomAction]; + } +} + +-(void) unselected +{ + // subclass to change the default action + if(isEnabled_) { + [super unselected]; + [self stopActionByTag:kZoomActionTag]; + CCAction *zoomAction = [CCScaleTo actionWithDuration:0.1f scale:originalScale_]; + zoomAction.tag = kZoomActionTag; + [self runAction:zoomAction]; + } +} + +-(void) setIsEnabled: (BOOL)enabled +{ + if( isEnabled_ != enabled ) { + if(enabled == NO) { + colorBackup = [label_ color]; + [label_ setColor: disabledColor_]; + } + else + [label_ setColor:colorBackup]; + } + + [super setIsEnabled:enabled]; +} + +- (void) setOpacity: (GLubyte)opacity +{ + [label_ setOpacity:opacity]; +} +-(GLubyte) opacity +{ + return [label_ opacity]; +} +-(void) setColor:(ccColor3B)color +{ + [label_ setColor:color]; +} +-(ccColor3B) color +{ + return [label_ color]; +} +@end + +#pragma mark - +#pragma mark CCMenuItemAtlasFont + +@implementation CCMenuItemAtlasFont + ++(id) itemFromString: (NSString*) value charMapFile:(NSString*) charMapFile itemWidth:(int)itemWidth itemHeight:(int)itemHeight startCharMap:(char)startCharMap +{ + return [CCMenuItemAtlasFont itemFromString:value charMapFile:charMapFile itemWidth:itemWidth itemHeight:itemHeight startCharMap:startCharMap target:nil selector:nil]; +} + ++(id) itemFromString: (NSString*) value charMapFile:(NSString*) charMapFile itemWidth:(int)itemWidth itemHeight:(int)itemHeight startCharMap:(char)startCharMap target:(id) rec selector:(SEL) cb +{ + return [[[self alloc] initFromString:value charMapFile:charMapFile itemWidth:itemWidth itemHeight:itemHeight startCharMap:startCharMap target:rec selector:cb] autorelease]; +} + +-(id) initFromString: (NSString*) value charMapFile:(NSString*) charMapFile itemWidth:(int)itemWidth itemHeight:(int)itemHeight startCharMap:(char)startCharMap target:(id) rec selector:(SEL) cb +{ + NSAssert( [value length] != 0, @"value length must be greater than 0"); + + CCLabelAtlas *label = [[CCLabelAtlas alloc] initWithString:value charMapFile:charMapFile itemWidth:itemWidth itemHeight:itemHeight startCharMap:startCharMap]; + [label autorelease]; + + if((self=[super initWithLabel:label target:rec selector:cb]) ) { + // do something ? + } + + return self; +} + +#if NS_BLOCKS_AVAILABLE ++(id) itemFromString:(NSString*)value charMapFile:(NSString*)charMapFile itemWidth:(int)itemWidth itemHeight:(int)itemHeight startCharMap:(char)startCharMap block:(void(^)(id sender))block { + return [[[self alloc] initFromString:value charMapFile:charMapFile itemWidth:itemWidth itemHeight:itemHeight startCharMap:startCharMap block:block] autorelease]; +} + +-(id) initFromString:(NSString*)value charMapFile:(NSString*)charMapFile itemWidth:(int)itemWidth itemHeight:(int)itemHeight startCharMap:(char)startCharMap block:(void(^)(id sender))block { + block_ = [block copy]; + return [self initFromString:value charMapFile:charMapFile itemWidth:itemWidth itemHeight:itemHeight startCharMap:startCharMap target:block_ selector:@selector(ccCallbackBlockWithSender:)]; +} +#endif // NS_BLOCKS_AVAILABLE + +-(void) dealloc +{ + [super dealloc]; +} +@end + + +#pragma mark - +#pragma mark CCMenuItemFont + +@implementation CCMenuItemFont + ++(void) setFontSize: (int) s +{ + _fontSize = s; +} + ++(int) fontSize +{ + return _fontSize; +} + ++(void) setFontName: (NSString*) n +{ + if( _fontNameRelease ) + [_fontName release]; + + _fontName = [n retain]; + _fontNameRelease = YES; +} + ++(NSString*) fontName +{ + return _fontName; +} + ++(id) itemFromString: (NSString*) value target:(id) r selector:(SEL) s +{ + return [[[self alloc] initFromString: value target:r selector:s] autorelease]; +} + ++(id) itemFromString: (NSString*) value +{ + return [[[self alloc] initFromString: value target:nil selector:nil] autorelease]; +} + +-(id) initFromString: (NSString*) value target:(id) rec selector:(SEL) cb +{ + NSAssert( [value length] != 0, @"Value length must be greater than 0"); + + CCLabelTTF *label = [CCLabelTTF labelWithString:value fontName:_fontName fontSize:_fontSize]; + + if((self=[super initWithLabel:label target:rec selector:cb]) ) { + // do something ? + } + + return self; +} + +#if NS_BLOCKS_AVAILABLE ++(id) itemFromString: (NSString*) value block:(void(^)(id sender))block { + return [[[self alloc] initFromString:value block:block] autorelease]; +} + +-(id) initFromString: (NSString*) value block:(void(^)(id sender))block { + block_ = [block copy]; + return [self initFromString:value target:block_ selector:@selector(ccCallbackBlockWithSender:)]; +} +#endif // NS_BLOCKS_AVAILABLE + +@end + +#pragma mark - +#pragma mark CCMenuItemSprite +@implementation CCMenuItemSprite + +@synthesize normalImage=normalImage_, selectedImage=selectedImage_, disabledImage=disabledImage_; + ++(id) itemFromNormalSprite:(CCNode*)normalSprite selectedSprite:(CCNode*)selectedSprite +{ + return [self itemFromNormalSprite:normalSprite selectedSprite:selectedSprite disabledSprite:nil target:nil selector:nil]; +} ++(id) itemFromNormalSprite:(CCNode*)normalSprite selectedSprite:(CCNode*)selectedSprite target:(id)target selector:(SEL)selector +{ + return [self itemFromNormalSprite:normalSprite selectedSprite:selectedSprite disabledSprite:nil target:target selector:selector]; +} ++(id) itemFromNormalSprite:(CCNode*)normalSprite selectedSprite:(CCNode*)selectedSprite disabledSprite:(CCNode*)disabledSprite target:(id)target selector:(SEL)selector +{ + return [[[self alloc] initFromNormalSprite:normalSprite selectedSprite:selectedSprite disabledSprite:disabledSprite target:target selector:selector] autorelease]; +} +-(id) initFromNormalSprite:(CCNode*)normalSprite selectedSprite:(CCNode*)selectedSprite disabledSprite:(CCNode*)disabledSprite target:(id)target selector:(SEL)selector +{ + if( (self=[super initWithTarget:target selector:selector]) ) { + + self.normalImage = normalSprite; + self.selectedImage = selectedSprite; + self.disabledImage = disabledSprite; + + [self setContentSize: [normalImage_ contentSize]]; + } + return self; +} + +#if NS_BLOCKS_AVAILABLE ++(id) itemFromNormalSprite:(CCNode*)normalSprite selectedSprite:(CCNode*)selectedSprite block:(void(^)(id sender))block { + return [self itemFromNormalSprite:normalSprite selectedSprite:selectedSprite disabledSprite:nil block:block]; +} + ++(id) itemFromNormalSprite:(CCNode*)normalSprite selectedSprite:(CCNode*)selectedSprite disabledSprite:(CCNode*)disabledSprite block:(void(^)(id sender))block { + return [[[self alloc] initFromNormalSprite:normalSprite selectedSprite:selectedSprite disabledSprite:disabledSprite block:block] autorelease]; +} + +-(id) initFromNormalSprite:(CCNode*)normalSprite selectedSprite:(CCNode*)selectedSprite disabledSprite:(CCNode*)disabledSprite block:(void(^)(id sender))block { + block_ = [block copy]; + return [self initFromNormalSprite:normalSprite selectedSprite:selectedSprite disabledSprite:disabledSprite target:block_ selector:@selector(ccCallbackBlockWithSender:)]; +} +#endif // NS_BLOCKS_AVAILABLE + + +-(void) setNormalImage:(CCNode *)image +{ + if( image != normalImage_ ) { + image.anchorPoint = ccp(0,0); + image.visible = YES; + + [self removeChild:normalImage_ cleanup:YES]; + [self addChild:image]; + + normalImage_ = image; + } +} + +-(void) setSelectedImage:(CCNode *)image +{ + if( image != selectedImage_ ) { + image.anchorPoint = ccp(0,0); + image.visible = NO; + + [self removeChild:selectedImage_ cleanup:YES]; + [self addChild:image]; + + selectedImage_ = image; + } +} + +-(void) setDisabledImage:(CCNode *)image +{ + if( image != disabledImage_ ) { + image.anchorPoint = ccp(0,0); + image.visible = NO; + + [self removeChild:disabledImage_ cleanup:YES]; + [self addChild:image]; + + disabledImage_ = image; + } +} + +#pragma mark CCMenuItemImage - CCRGBAProtocol protocol +- (void) setOpacity: (GLubyte)opacity +{ + [normalImage_ setOpacity:opacity]; + [selectedImage_ setOpacity:opacity]; + [disabledImage_ setOpacity:opacity]; +} + +-(void) setColor:(ccColor3B)color +{ + [normalImage_ setColor:color]; + [selectedImage_ setColor:color]; + [disabledImage_ setColor:color]; +} + +-(GLubyte) opacity +{ + return [normalImage_ opacity]; +} + +-(ccColor3B) color +{ + return [normalImage_ color]; +} + +-(void) selected +{ + [super selected]; + + if( selectedImage_ ) { + [normalImage_ setVisible:NO]; + [selectedImage_ setVisible:YES]; + [disabledImage_ setVisible:NO]; + + } else { // there is not selected image + + [normalImage_ setVisible:YES]; + [selectedImage_ setVisible:NO]; + [disabledImage_ setVisible:NO]; + } +} + +-(void) unselected +{ + [super unselected]; + [normalImage_ setVisible:YES]; + [selectedImage_ setVisible:NO]; + [disabledImage_ setVisible:NO]; +} + +-(void) setIsEnabled:(BOOL)enabled +{ + [super setIsEnabled:enabled]; + + if( enabled ) { + [normalImage_ setVisible:YES]; + [selectedImage_ setVisible:NO]; + [disabledImage_ setVisible:NO]; + + } else { + if( disabledImage_ ) { + [normalImage_ setVisible:NO]; + [selectedImage_ setVisible:NO]; + [disabledImage_ setVisible:YES]; + } else { + [normalImage_ setVisible:YES]; + [selectedImage_ setVisible:NO]; + [disabledImage_ setVisible:NO]; + } + } +} + +@end + +#pragma mark - +#pragma mark CCMenuItemImage + +@implementation CCMenuItemImage + ++(id) itemFromNormalImage: (NSString*)value selectedImage:(NSString*) value2 +{ + return [self itemFromNormalImage:value selectedImage:value2 disabledImage: nil target:nil selector:nil]; +} + ++(id) itemFromNormalImage: (NSString*)value selectedImage:(NSString*) value2 target:(id) t selector:(SEL) s +{ + return [self itemFromNormalImage:value selectedImage:value2 disabledImage: nil target:t selector:s]; +} + ++(id) itemFromNormalImage: (NSString*)value selectedImage:(NSString*) value2 disabledImage: (NSString*) value3 +{ + return [[[self alloc] initFromNormalImage:value selectedImage:value2 disabledImage:value3 target:nil selector:nil] autorelease]; +} + ++(id) itemFromNormalImage: (NSString*)value selectedImage:(NSString*) value2 disabledImage: (NSString*) value3 target:(id) t selector:(SEL) s +{ + return [[[self alloc] initFromNormalImage:value selectedImage:value2 disabledImage:value3 target:t selector:s] autorelease]; +} + +-(id) initFromNormalImage: (NSString*) normalI selectedImage:(NSString*)selectedI disabledImage: (NSString*) disabledI target:(id)t selector:(SEL)sel +{ + CCNode *normalImage = [CCSprite spriteWithFile:normalI]; + CCNode *selectedImage = nil; + CCNode *disabledImage = nil; + + if( selectedI ) + selectedImage = [CCSprite spriteWithFile:selectedI]; + if(disabledI) + disabledImage = [CCSprite spriteWithFile:disabledI]; + + return [self initFromNormalSprite:normalImage selectedSprite:selectedImage disabledSprite:disabledImage target:t selector:sel]; +} + +#if NS_BLOCKS_AVAILABLE + ++(id) itemFromNormalImage: (NSString*)value selectedImage:(NSString*) value2 block:(void(^)(id sender))block { + return [self itemFromNormalImage:value selectedImage:value2 disabledImage:nil block:block]; +} + ++(id) itemFromNormalImage: (NSString*)value selectedImage:(NSString*) value2 disabledImage:(NSString*) value3 block:(void(^)(id sender))block { + return [[[self alloc] initFromNormalImage:value selectedImage:value2 disabledImage:value3 block:block] autorelease]; +} + +-(id) initFromNormalImage: (NSString*) value selectedImage:(NSString*)value2 disabledImage:(NSString*) value3 block:(void(^)(id sender))block { + block_ = [block copy]; + return [self initFromNormalImage:value selectedImage:value2 disabledImage:value3 target:block_ selector:@selector(ccCallbackBlockWithSender:)]; +} + +#endif // NS_BLOCKS_AVAILABLE + +@end + +#pragma mark - +#pragma mark CCMenuItemToggle + +// +// MenuItemToggle +// +@implementation CCMenuItemToggle + +@synthesize subItems = subItems_; +@synthesize opacity = opacity_, color = color_; + ++(id) itemWithTarget: (id)t selector: (SEL)sel items: (CCMenuItem*) item, ... +{ + va_list args; + va_start(args, item); + + id s = [[[self alloc] initWithTarget: t selector:sel items: item vaList:args] autorelease]; + + va_end(args); + return s; +} + +-(id) initWithTarget: (id)t selector: (SEL)sel items:(CCMenuItem*) item vaList: (va_list) args +{ + if( (self=[super initWithTarget:t selector:sel]) ) { + + self.subItems = [NSMutableArray arrayWithCapacity:2]; + + int z = 0; + CCMenuItem *i = item; + while(i) { + z++; + [subItems_ addObject:i]; + i = va_arg(args, CCMenuItem*); + } + + selectedIndex_ = NSUIntegerMax; + [self setSelectedIndex:0]; + } + + return self; +} + +#if NS_BLOCKS_AVAILABLE + ++(id) itemWithBlock:(void(^)(id sender))block items:(CCMenuItem*)item, ... { + va_list args; + va_start(args, item); + + id s = [[[self alloc] initWithBlock:block items:item vaList:args] autorelease]; + + va_end(args); + return s; +} + +-(id) initWithBlock:(void (^)(id))block items:(CCMenuItem*)item vaList:(va_list)args { + block_ = [block copy]; + return [self initWithTarget:block_ selector:@selector(ccCallbackBlockWithSender:) items:item vaList:args]; +} + +#endif // NS_BLOCKS_AVAILABLE + +-(void) dealloc +{ + [subItems_ release]; + [super dealloc]; +} + +-(void)setSelectedIndex:(NSUInteger)index +{ + if( index != selectedIndex_ ) { + selectedIndex_=index; + [self removeChildByTag:kCurrentItem cleanup:NO]; + + CCMenuItem *item = [subItems_ objectAtIndex:selectedIndex_]; + [self addChild:item z:0 tag:kCurrentItem]; + + CGSize s = [item contentSize]; + [self setContentSize: s]; + item.position = ccp( s.width/2, s.height/2 ); + } +} + +-(NSUInteger) selectedIndex +{ + return selectedIndex_; +} + + +-(void) selected +{ + [super selected]; + [[subItems_ objectAtIndex:selectedIndex_] selected]; +} + +-(void) unselected +{ + [super unselected]; + [[subItems_ objectAtIndex:selectedIndex_] unselected]; +} + +-(void) activate +{ + // update index + if( isEnabled_ ) { + NSUInteger newIndex = (selectedIndex_ + 1) % [subItems_ count]; + [self setSelectedIndex:newIndex]; + + } + + [super activate]; +} + +-(void) setIsEnabled: (BOOL)enabled +{ + [super setIsEnabled:enabled]; + for(CCMenuItem* item in subItems_) + [item setIsEnabled:enabled]; +} + +-(CCMenuItem*) selectedItem +{ + return [subItems_ objectAtIndex:selectedIndex_]; +} + +#pragma mark CCMenuItemToggle - CCRGBAProtocol protocol + +- (void) setOpacity: (GLubyte)opacity +{ + opacity_ = opacity; + for(CCMenuItem* item in subItems_) + [item setOpacity:opacity]; +} + +- (void) setColor:(ccColor3B)color +{ + color_ = color; + for(CCMenuItem* item in subItems_) + [item setColor:color]; +} + +@end diff --git a/libs/cocos2d/CCMotionStreak.h b/libs/cocos2d/CCMotionStreak.h new file mode 100644 index 0000000..e017124 --- /dev/null +++ b/libs/cocos2d/CCMotionStreak.h @@ -0,0 +1,67 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2008, 2009 Jason Booth + * + * 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 +#import "CCNode.h" +#import "CCRibbon.h" + +/** + * CCMotionStreak manages a Ribbon based on it's motion in absolute space. + * You construct it with a fadeTime, minimum segment size, texture path, texture + * length and color. The fadeTime controls how long it takes each vertex in + * the streak to fade out, the minimum segment size it how many pixels the + * streak will move before adding a new ribbon segement, and the texture + * length is the how many pixels the texture is stretched across. The texture + * is vertically aligned along the streak segemnts. + * + * Limitations: + * CCMotionStreak, by default, will use the GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA blending function. + * This blending function might not be the correct one for certain textures. + * But you can change it by using: + * [obj setBlendFunc: (ccBlendfunc) {new_src_blend_func, new_dst_blend_func}]; + * + * @since v0.8.1 + */ +@interface CCMotionStreak : CCNode +{ + CCRibbon* ribbon_; + float segThreshold_; + float width_; + CGPoint lastLocation_; +} + +/** Ribbon used by MotionStreak (weak reference) */ +@property (nonatomic,readonly) CCRibbon *ribbon; + +/** creates the a MotionStreak. The image will be loaded using the TextureMgr. */ ++(id)streakWithFade:(float)fade minSeg:(float)seg image:(NSString*)path width:(float)width length:(float)length color:(ccColor4B)color; + +/** initializes a MotionStreak. The file will be loaded using the TextureMgr. */ +-(id)initWithFade:(float)fade minSeg:(float)seg image:(NSString*)path width:(float)width length:(float)length color:(ccColor4B)color; + +/** polling function */ +-(void)update:(ccTime)delta; + +@end diff --git a/libs/cocos2d/CCMotionStreak.m b/libs/cocos2d/CCMotionStreak.m new file mode 100644 index 0000000..42737b9 --- /dev/null +++ b/libs/cocos2d/CCMotionStreak.m @@ -0,0 +1,104 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2008, 2009 Jason Booth + * + * 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. + * + * + ********************************************************* + * + * Motion Streak manages a Ribbon based on it's motion in absolute space. + * You construct it with a fadeTime, minimum segment size, texture path, texture + * length and color. The fadeTime controls how long it takes each vertex in + * the streak to fade out, the minimum segment size it how many pixels the + * streak will move before adding a new ribbon segement, and the texture + * length is the how many pixels the texture is stretched across. The texture + * is vertically aligned along the streak segemnts. + */ + +#import "CCMotionStreak.h" +#import "Support/CGPointExtension.h" + +@implementation CCMotionStreak + +@synthesize ribbon = ribbon_; + ++(id)streakWithFade:(float)fade minSeg:(float)seg image:(NSString*)path width:(float)width length:(float)length color:(ccColor4B)color +{ + return [[[self alloc] initWithFade:(float)fade minSeg:seg image:path width:width length:length color:color] autorelease]; +} + +-(id)initWithFade:(float)fade minSeg:(float)seg image:(NSString*)path width:(float)width length:(float)length color:(ccColor4B)color +{ + if( (self=[super init])) { + segThreshold_ = seg; + width_ = width; + lastLocation_ = CGPointZero; + ribbon_ = [CCRibbon ribbonWithWidth:width_ image:path length:length color:color fade:fade]; + [self addChild:ribbon_]; + + // update ribbon position. Use schedule:interval and not scheduleUpdated. issue #1075 + [self schedule:@selector(update:) interval:0]; + } + return self; +} + +-(void)update:(ccTime)delta +{ + CGPoint location = [self convertToWorldSpace:CGPointZero]; + [ribbon_ setPosition:ccp(-1*location.x, -1*location.y)]; + float len = ccpLength(ccpSub(lastLocation_, location)); + if (len > segThreshold_) + { + [ribbon_ addPointAt:location width:width_]; + lastLocation_ = location; + } + [ribbon_ update:delta]; +} + + +-(void)dealloc +{ + [super dealloc]; +} + +#pragma mark MotionStreak - CocosNodeTexture protocol + +-(void) setTexture:(CCTexture2D*) texture +{ + [ribbon_ setTexture: texture]; +} + +-(CCTexture2D*) texture +{ + return [ribbon_ texture]; +} + +-(ccBlendFunc) blendFunc +{ + return [ribbon_ blendFunc]; +} + +-(void) setBlendFunc:(ccBlendFunc)blendFunc +{ + [ribbon_ setBlendFunc:blendFunc]; +} + +@end diff --git a/libs/cocos2d/CCNode.h b/libs/cocos2d/CCNode.h new file mode 100644 index 0000000..64acdc5 --- /dev/null +++ b/libs/cocos2d/CCNode.h @@ -0,0 +1,529 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2009 Valentin Milea + * + * 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. + */ + +#import + +#import "Platforms/CCGL.h" +#import "CCAction.h" +#import "ccTypes.h" +#import "CCTexture2D.h" +#import "CCProtocols.h" +#import "ccConfig.h" +#import "Support/CCArray.h" + +enum { + kCCNodeTagInvalid = -1, +}; + +@class CCCamera; +@class CCGridBase; + +/** CCNode is the main element. Anything thats gets drawn or contains things that get drawn is a CCNode. + The most popular CCNodes are: CCScene, CCLayer, CCSprite, CCMenu. + + The main features of a CCNode are: + - They can contain other CCNode nodes (addChild, getChildByTag, removeChild, etc) + - They can schedule periodic callback (schedule, unschedule, etc) + - They can execute actions (runAction, stopAction, etc) + + Some CCNode nodes provide extra functionality for them or their children. + + Subclassing a CCNode usually means (one/all) of: + - overriding init to initialize resources and schedule callbacks + - create callbacks to handle the advancement of time + - overriding draw to render the node + + Features of CCNode: + - position + - scale (x, y) + - rotation (in degrees, clockwise) + - CCCamera (an interface to gluLookAt ) + - CCGridBase (to do mesh transformations) + - anchor point + - size + - visible + - z-order + - openGL z position + + Default values: + - rotation: 0 + - position: (x=0,y=0) + - scale: (x=1,y=1) + - contentSize: (x=0,y=0) + - anchorPoint: (x=0,y=0) + + Limitations: + - A CCNode is a "void" object. It doesn't have a texture + + Order in transformations with grid disabled + -# The node will be translated (position) + -# The node will be rotated (rotation) + -# The node will be scaled (scale) + -# The node will be moved according to the camera values (camera) + + Order in transformations with grid enabled + -# The node will be translated (position) + -# The node will be rotated (rotation) + -# The node will be scaled (scale) + -# The grid will capture the screen + -# The node will be moved according to the camera values (camera) + -# The grid will render the captured screen + + Camera: + - Each node has a camera. By default it points to the center of the CCNode. + */ +@interface CCNode : NSObject +{ + // rotation angle + float rotation_; + + // scaling factors + float scaleX_, scaleY_; + + // position of the node + CGPoint position_; + CGPoint positionInPixels_; + + // skew angles + float skewX_, skewY_; + + // is visible + BOOL visible_; + + // anchor point in pixels + CGPoint anchorPointInPixels_; + // anchor point normalized + CGPoint anchorPoint_; + // If YES the transformtions will be relative to (-transform.x, -transform.y). + // Sprites, Labels and any other "small" object uses it. + // Scenes, Layers and other "whole screen" object don't use it. + BOOL isRelativeAnchorPoint_; + + // untransformed size of the node + CGSize contentSize_; + CGSize contentSizeInPixels_; + + // transform + CGAffineTransform transform_, inverse_; +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + GLfloat transformGL_[16]; +#endif + + // openGL real Z vertex + float vertexZ_; + + // a Camera + CCCamera *camera_; + + // a Grid + CCGridBase *grid_; + + // z-order value + NSInteger zOrder_; + + // array of children + CCArray *children_; + + // weakref to parent + CCNode *parent_; + + // a tag. any number you want to assign to the node + NSInteger tag_; + + // user data field + void *userData_; + + // Is running + BOOL isRunning_; + + // To reduce memory, place BOOLs that are not properties here: + BOOL isTransformDirty_:1; + BOOL isInverseDirty_:1; +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + BOOL isTransformGLDirty_:1; +#endif +} + +/** The z order of the node relative to it's "brothers": children of the same parent */ +@property(nonatomic,readonly) NSInteger zOrder; +/** The real openGL Z vertex. + Differences between openGL Z vertex and cocos2d Z order: + - OpenGL Z modifies the Z vertex, and not the Z order in the relation between parent-children + - OpenGL Z might require to set 2D projection + - cocos2d Z order works OK if all the nodes uses the same openGL Z vertex. eg: vertexZ = 0 + @warning: Use it at your own risk since it might break the cocos2d parent-children z order + @since v0.8 + */ +@property (nonatomic,readwrite) float vertexZ; + +/** The X skew angle of the node in degrees. + This angle describes the shear distortion in the X direction. + Thus, it is the angle between the Y axis and the left edge of the shape + The default skewX angle is 0. Positive values distort the node in a CW direction. + */ +@property(nonatomic,readwrite,assign) float skewX; + +/** The Y skew angle of the node in degrees. + This angle describes the shear distortion in the Y direction. + Thus, it is the angle between the X axis and the bottom edge of the shape + The default skewY angle is 0. Positive values distort the node in a CCW direction. + */ +@property(nonatomic,readwrite,assign) float skewY; +/** The rotation (angle) of the node in degrees. 0 is the default rotation angle. Positive values rotate node CW. */ +@property(nonatomic,readwrite,assign) float rotation; +/** The scale factor of the node. 1.0 is the default scale factor. It modifies the X and Y scale at the same time. */ +@property(nonatomic,readwrite,assign) float scale; +/** The scale factor of the node. 1.0 is the default scale factor. It only modifies the X scale factor. */ +@property(nonatomic,readwrite,assign) float scaleX; +/** The scale factor of the node. 1.0 is the default scale factor. It only modifies the Y scale factor. */ +@property(nonatomic,readwrite,assign) float scaleY; +/** Position (x,y) of the node in points. (0,0) is the left-bottom corner. */ +@property(nonatomic,readwrite,assign) CGPoint position; +/** Position (x,y) of the node in points. (0,0) is the left-bottom corner. */ +@property(nonatomic,readwrite,assign) CGPoint positionInPixels; +/** A CCCamera object that lets you move the node using a gluLookAt +*/ +@property(nonatomic,readonly) CCCamera* camera; +/** Array of children */ +@property(nonatomic,readonly) CCArray *children; +/** A CCGrid object that is used when applying effects */ +@property(nonatomic,readwrite,retain) CCGridBase* grid; +/** Whether of not the node is visible. Default is YES */ +@property(nonatomic,readwrite,assign) BOOL visible; +/** anchorPoint is the point around which all transformations and positioning manipulations take place. + It's like a pin in the node where it is "attached" to its parent. + The anchorPoint is normalized, like a percentage. (0,0) means the bottom-left corner and (1,1) means the top-right corner. + But you can use values higher than (1,1) and lower than (0,0) too. + The default anchorPoint is (0,0). It starts in the bottom-left corner. CCSprite and other subclasses have a different default anchorPoint. + @since v0.8 + */ +@property(nonatomic,readwrite) CGPoint anchorPoint; +/** The anchorPoint in absolute pixels. + Since v0.8 you can only read it. If you wish to modify it, use anchorPoint instead + */ +@property(nonatomic,readonly) CGPoint anchorPointInPixels; + +/** The untransformed size of the node in Points + The contentSize remains the same no matter the node is scaled or rotated. + All nodes has a size. Layer and Scene has the same size of the screen. + @since v0.8 + */ +@property (nonatomic,readwrite) CGSize contentSize; + +/** The untransformed size of the node in Pixels + The contentSize remains the same no matter the node is scaled or rotated. + All nodes has a size. Layer and Scene has the same size of the screen. + @since v0.8 + */ +@property (nonatomic,readwrite) CGSize contentSizeInPixels; + +/** whether or not the node is running */ +@property(nonatomic,readonly) BOOL isRunning; +/** A weak reference to the parent */ +@property(nonatomic,readwrite,assign) CCNode* parent; +/** If YES the transformtions will be relative to it's anchor point. + * Sprites, Labels and any other sizeble object use it have it enabled by default. + * Scenes, Layers and other "whole screen" object don't use it, have it disabled by default. + */ +@property(nonatomic,readwrite,assign) BOOL isRelativeAnchorPoint; +/** A tag used to identify the node easily */ +@property(nonatomic,readwrite,assign) NSInteger tag; +/** A custom user data pointer */ +@property(nonatomic,readwrite,assign) void *userData; + +// initializators +/** allocates and initializes a node. + The node will be created as "autorelease". + */ ++(id) node; +/** initializes the node */ +-(id) init; + + +// scene managment + +/** callback that is called every time the CCNode enters the 'stage'. + If the CCNode enters the 'stage' with a transition, this callback is called when the transition starts. + During onEnter you can't a "sister/brother" node. + */ +-(void) onEnter; +/** callback that is called when the CCNode enters in the 'stage'. + If the CCNode enters the 'stage' with a transition, this callback is called when the transition finishes. + @since v0.8 + */ +-(void) onEnterTransitionDidFinish; +/** callback that is called every time the CCNode leaves the 'stage'. + If the CCNode leaves the 'stage' with a transition, this callback is called when the transition finishes. + During onExit you can't access a sibling node. + */ +-(void) onExit; + + +// composition: ADD + +/** Adds a child to the container with z-order as 0. + If the child is added to a 'running' node, then 'onEnter' and 'onEnterTransitionDidFinish' will be called immediately. + @since v0.7.1 + */ +-(void) addChild: (CCNode*)node; + +/** Adds a child to the container with a z-order. + If the child is added to a 'running' node, then 'onEnter' and 'onEnterTransitionDidFinish' will be called immediately. + @since v0.7.1 + */ +-(void) addChild: (CCNode*)node z:(NSInteger)z; + +/** Adds a child to the container with z order and tag. + If the child is added to a 'running' node, then 'onEnter' and 'onEnterTransitionDidFinish' will be called immediately. + @since v0.7.1 + */ +-(void) addChild: (CCNode*)node z:(NSInteger)z tag:(NSInteger)tag; + +// composition: REMOVE + +/** Remove itself from its parent node. If cleanup is YES, then also remove all actions and callbacks. + If the node orphan, then nothing happens. + @since v0.99.3 + */ +-(void) removeFromParentAndCleanup:(BOOL)cleanup; + +/** Removes a child from the container. It will also cleanup all running actions depending on the cleanup parameter. + @since v0.7.1 + */ +-(void) removeChild: (CCNode*)node cleanup:(BOOL)cleanup; + +/** Removes a child from the container by tag value. It will also cleanup all running actions depending on the cleanup parameter + @since v0.7.1 + */ +-(void) removeChildByTag:(NSInteger) tag cleanup:(BOOL)cleanup; + +/** Removes all children from the container and do a cleanup all running actions depending on the cleanup parameter. + @since v0.7.1 + */ +-(void) removeAllChildrenWithCleanup:(BOOL)cleanup; + +// composition: GET +/** Gets a child from the container given its tag + @return returns a CCNode object + @since v0.7.1 + */ +-(CCNode*) getChildByTag:(NSInteger) tag; + +/** Reorders a child according to a new z value. + * The child MUST be already added. + */ +-(void) reorderChild:(CCNode*)child z:(NSInteger)zOrder; + +/** Stops all running actions and schedulers + @since v0.8 + */ +-(void) cleanup; + +// draw + +/** Override this method to draw your own node. + The following GL states will be enabled by default: + - glEnableClientState(GL_VERTEX_ARRAY); + - glEnableClientState(GL_COLOR_ARRAY); + - glEnableClientState(GL_TEXTURE_COORD_ARRAY); + - glEnable(GL_TEXTURE_2D); + + AND YOU SHOULD NOT DISABLE THEM AFTER DRAWING YOUR NODE + + But if you enable any other GL state, you should disable it after drawing your node. + */ +-(void) draw; +/** recursive method that visit its children and draw them */ +-(void) visit; + +// transformations + +/** performs OpenGL view-matrix transformation based on position, scale, rotation and other attributes. */ +-(void) transform; + +/** performs OpenGL view-matrix transformation of it's ancestors. + Generally the ancestors are already transformed, but in certain cases (eg: attaching a FBO) + it's necessary to transform the ancestors again. + @since v0.7.2 + */ +-(void) transformAncestors; + +/** returns a "local" axis aligned bounding box of the node in points. + The returned box is relative only to its parent. + The returned box is in Points. + + @since v0.8.2 + */ +- (CGRect) boundingBox; + +/** returns a "local" axis aligned bounding box of the node in pixels. + The returned box is relative only to its parent. + The returned box is in Points. + + @since v0.99.5 + */ +- (CGRect) boundingBoxInPixels; + + +// actions + +/** Executes an action, and returns the action that is executed. + The node becomes the action's target. + @warning Starting from v0.8 actions don't retain their target anymore. + @since v0.7.1 + @return An Action pointer + */ +-(CCAction*) runAction: (CCAction*) action; +/** Removes all actions from the running action list */ +-(void) stopAllActions; +/** Removes an action from the running action list */ +-(void) stopAction: (CCAction*) action; +/** Removes an action from the running action list given its tag + @since v0.7.1 +*/ +-(void) stopActionByTag:(NSInteger) tag; +/** Gets an action from the running action list given its tag + @since v0.7.1 + @return the Action the with the given tag + */ +-(CCAction*) getActionByTag:(NSInteger) tag; +/** Returns the numbers of actions that are running plus the ones that are schedule to run (actions in actionsToAdd and actions arrays). + * Composable actions are counted as 1 action. Example: + * If you are running 1 Sequence of 7 actions, it will return 1. + * If you are running 7 Sequences of 2 actions, it will return 7. + */ +-(NSUInteger) numberOfRunningActions; + +// timers + +/** check whether a selector is scheduled. */ +//-(BOOL) isScheduled: (SEL) selector; + +/** schedules the "update" method. It will use the order number 0. This method will be called every frame. + Scheduled methods with a lower order value will be called before the ones that have a higher order value. + Only one "udpate" method could be scheduled per node. + + @since v0.99.3 + */ +-(void) scheduleUpdate; + +/** schedules the "update" selector with a custom priority. This selector will be called every frame. + Scheduled selectors with a lower priority will be called before the ones that have a higher value. + Only one "udpate" selector could be scheduled per node (You can't have 2 'update' selectors). + + @since v0.99.3 + */ +-(void) scheduleUpdateWithPriority:(NSInteger)priority; + +/* unschedules the "update" method. + + @since v0.99.3 + */ +-(void) unscheduleUpdate; + + +/** schedules a selector. + The scheduled selector will be ticked every frame + */ +-(void) schedule: (SEL) s; +/** schedules a custom selector with an interval time in seconds. + If time is 0 it will be ticked every frame. + If time is 0, it is recommended to use 'scheduleUpdate' instead. + + If the selector is already scheduled, then the interval parameter will be updated without scheduling it again. + */ +-(void) schedule: (SEL) s interval:(ccTime)seconds; +/** unschedules a custom selector.*/ +-(void) unschedule: (SEL) s; + +/** unschedule all scheduled selectors: custom selectors, and the 'update' selector. + Actions are not affected by this method. +@since v0.99.3 + */ +-(void) unscheduleAllSelectors; + +/** resumes all scheduled selectors and actions. + Called internally by onEnter + */ +-(void) resumeSchedulerAndActions; +/** pauses all scheduled selectors and actions. + Called internally by onExit + */ +-(void) pauseSchedulerAndActions; + + +// transformation methods + +/** Returns the matrix that transform the node's (local) space coordinates into the parent's space coordinates. + The matrix is in Pixels. + @since v0.7.1 + */ +- (CGAffineTransform)nodeToParentTransform; +/** Returns the matrix that transform parent's space coordinates to the node's (local) space coordinates. + The matrix is in Pixels. + @since v0.7.1 + */ +- (CGAffineTransform)parentToNodeTransform; +/** Retrusn the world affine transform matrix. The matrix is in Pixels. + @since v0.7.1 + */ +- (CGAffineTransform)nodeToWorldTransform; +/** Returns the inverse world affine transform matrix. The matrix is in Pixels. + @since v0.7.1 + */ +- (CGAffineTransform)worldToNodeTransform; +/** Converts a Point to node (local) space coordinates. The result is in Points. + @since v0.7.1 + */ +- (CGPoint)convertToNodeSpace:(CGPoint)worldPoint; +/** Converts a Point to world space coordinates. The result is in Points. + @since v0.7.1 + */ +- (CGPoint)convertToWorldSpace:(CGPoint)nodePoint; +/** Converts a Point to node (local) space coordinates. The result is in Points. + treating the returned/received node point as anchor relative. + @since v0.7.1 + */ +- (CGPoint)convertToNodeSpaceAR:(CGPoint)worldPoint; +/** Converts a local Point to world space coordinates.The result is in Points. + treating the returned/received node point as anchor relative. + @since v0.7.1 + */ +- (CGPoint)convertToWorldSpaceAR:(CGPoint)nodePoint; + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +/** Converts a UITouch to node (local) space coordinates. The result is in Points. + @since v0.7.1 + */ +- (CGPoint)convertTouchToNodeSpace:(UITouch *)touch; +/** Converts a UITouch to node (local) space coordinates. The result is in Points. + This method is AR (Anchor Relative).. + @since v0.7.1 + */ +- (CGPoint)convertTouchToNodeSpaceAR:(UITouch *)touch; +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED +@end diff --git a/libs/cocos2d/CCNode.m b/libs/cocos2d/CCNode.m new file mode 100644 index 0000000..569cb22 --- /dev/null +++ b/libs/cocos2d/CCNode.m @@ -0,0 +1,921 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2009 Valentin Milea + * + * 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. + */ + +#import "CCNode.h" +#import "CCGrid.h" +#import "CCDirector.h" +#import "CCActionManager.h" +#import "CCCamera.h" +#import "CCScheduler.h" +#import "ccConfig.h" +#import "ccMacros.h" +#import "Support/CGPointExtension.h" +#import "Support/ccCArray.h" +#import "Support/TransformUtils.h" +#import "ccMacros.h" + +#import +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +#import "Platforms/iOS/CCDirectorIOS.h" +#endif + + +#if CC_COCOSNODE_RENDER_SUBPIXEL +#define RENDER_IN_SUBPIXEL +#else +#define RENDER_IN_SUBPIXEL (NSInteger) +#endif + +@interface CCNode () +// lazy allocs +-(void) childrenAlloc; +// helper that reorder a child +-(void) insertChild:(CCNode*)child z:(NSInteger)z; +// used internally to alter the zOrder variable. DON'T call this method manually +-(void) _setZOrder:(NSInteger) z; +-(void) detachChild:(CCNode *)child cleanup:(BOOL)doCleanup; +@end + +@implementation CCNode + +@synthesize children = children_; +@synthesize visible = visible_; +@synthesize parent = parent_; +@synthesize grid = grid_; +@synthesize zOrder = zOrder_; +@synthesize tag = tag_; +@synthesize vertexZ = vertexZ_; +@synthesize isRunning = isRunning_; +@synthesize userData = userData_; + +#pragma mark CCNode - Transform related properties + +@synthesize rotation = rotation_, scaleX = scaleX_, scaleY = scaleY_; +@synthesize skewX = skewX_, skewY = skewY_; +@synthesize position = position_, positionInPixels = positionInPixels_; +@synthesize anchorPoint = anchorPoint_, anchorPointInPixels = anchorPointInPixels_; +@synthesize contentSize = contentSize_, contentSizeInPixels = contentSizeInPixels_; +@synthesize isRelativeAnchorPoint = isRelativeAnchorPoint_; + +// getters synthesized, setters explicit + +-(void) setSkewX:(float)newSkewX +{ + skewX_ = newSkewX; + isTransformDirty_ = isInverseDirty_ = YES; +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + isTransformGLDirty_ = YES; +#endif +} + +-(void) setSkewY:(float)newSkewY +{ + skewY_ = newSkewY; + isTransformDirty_ = isInverseDirty_ = YES; +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + isTransformGLDirty_ = YES; +#endif +} + +-(void) setRotation: (float)newRotation +{ + rotation_ = newRotation; + isTransformDirty_ = isInverseDirty_ = YES; +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + isTransformGLDirty_ = YES; +#endif +} + +-(void) setScaleX: (float)newScaleX +{ + scaleX_ = newScaleX; + isTransformDirty_ = isInverseDirty_ = YES; +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + isTransformGLDirty_ = YES; +#endif +} + +-(void) setScaleY: (float)newScaleY +{ + scaleY_ = newScaleY; + isTransformDirty_ = isInverseDirty_ = YES; +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + isTransformGLDirty_ = YES; +#endif +} + +-(void) setPosition: (CGPoint)newPosition +{ + position_ = newPosition; + if( CC_CONTENT_SCALE_FACTOR() == 1 ) + positionInPixels_ = position_; + else + positionInPixels_ = ccpMult( newPosition, CC_CONTENT_SCALE_FACTOR() ); + + isTransformDirty_ = isInverseDirty_ = YES; +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + isTransformGLDirty_ = YES; +#endif +} + +-(void) setPositionInPixels:(CGPoint)newPosition +{ + positionInPixels_ = newPosition; + + if( CC_CONTENT_SCALE_FACTOR() == 1 ) + position_ = positionInPixels_; + else + position_ = ccpMult( newPosition, 1/CC_CONTENT_SCALE_FACTOR() ); + + isTransformDirty_ = isInverseDirty_ = YES; +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + isTransformGLDirty_ = YES; +#endif +} + +-(void) setIsRelativeAnchorPoint: (BOOL)newValue +{ + isRelativeAnchorPoint_ = newValue; + isTransformDirty_ = isInverseDirty_ = YES; +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + isTransformGLDirty_ = YES; +#endif +} + +-(void) setAnchorPoint:(CGPoint)point +{ + if( ! CGPointEqualToPoint(point, anchorPoint_) ) { + anchorPoint_ = point; + anchorPointInPixels_ = ccp( contentSizeInPixels_.width * anchorPoint_.x, contentSizeInPixels_.height * anchorPoint_.y ); + isTransformDirty_ = isInverseDirty_ = YES; +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + isTransformGLDirty_ = YES; +#endif + } +} + +-(void) setContentSize:(CGSize)size +{ + if( ! CGSizeEqualToSize(size, contentSize_) ) { + contentSize_ = size; + + if( CC_CONTENT_SCALE_FACTOR() == 1 ) + contentSizeInPixels_ = contentSize_; + else + contentSizeInPixels_ = CGSizeMake( size.width * CC_CONTENT_SCALE_FACTOR(), size.height * CC_CONTENT_SCALE_FACTOR() ); + + anchorPointInPixels_ = ccp( contentSizeInPixels_.width * anchorPoint_.x, contentSizeInPixels_.height * anchorPoint_.y ); + isTransformDirty_ = isInverseDirty_ = YES; +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + isTransformGLDirty_ = YES; +#endif + } +} + +-(void) setContentSizeInPixels:(CGSize)size +{ + if( ! CGSizeEqualToSize(size, contentSizeInPixels_) ) { + contentSizeInPixels_ = size; + + if( CC_CONTENT_SCALE_FACTOR() == 1 ) + contentSize_ = contentSizeInPixels_; + else + contentSize_ = CGSizeMake( size.width / CC_CONTENT_SCALE_FACTOR(), size.height / CC_CONTENT_SCALE_FACTOR() ); + + anchorPointInPixels_ = ccp( contentSizeInPixels_.width * anchorPoint_.x, contentSizeInPixels_.height * anchorPoint_.y ); + isTransformDirty_ = isInverseDirty_ = YES; +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + isTransformGLDirty_ = YES; +#endif + } +} + +- (CGRect) boundingBox +{ + CGRect ret = [self boundingBoxInPixels]; + return CC_RECT_PIXELS_TO_POINTS( ret ); +} + +- (CGRect) boundingBoxInPixels +{ + CGRect rect = CGRectMake(0, 0, contentSizeInPixels_.width, contentSizeInPixels_.height); + return CGRectApplyAffineTransform(rect, [self nodeToParentTransform]); +} + +-(void) setVertexZ:(float)vertexZ +{ + vertexZ_ = vertexZ * CC_CONTENT_SCALE_FACTOR(); +} + +-(float) scale +{ + NSAssert( scaleX_ == scaleY_, @"CCNode#scale. ScaleX != ScaleY. Don't know which one to return"); + return scaleX_; +} + +-(void) setScale:(float) s +{ + scaleX_ = scaleY_ = s; + isTransformDirty_ = isInverseDirty_ = YES; +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + isTransformGLDirty_ = YES; +#endif +} + +#pragma mark CCNode - Init & cleanup + ++(id) node +{ + return [[[self alloc] init] autorelease]; +} + +-(id) init +{ + if ((self=[super init]) ) { + + isRunning_ = NO; + + skewX_ = skewY_ = 0.0f; + rotation_ = 0.0f; + scaleX_ = scaleY_ = 1.0f; + positionInPixels_ = position_ = CGPointZero; + anchorPointInPixels_ = anchorPoint_ = CGPointZero; + contentSizeInPixels_ = contentSize_ = CGSizeZero; + + + // "whole screen" objects. like Scenes and Layers, should set isRelativeAnchorPoint to NO + isRelativeAnchorPoint_ = YES; + + isTransformDirty_ = isInverseDirty_ = YES; +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + isTransformGLDirty_ = YES; +#endif + + vertexZ_ = 0; + + grid_ = nil; + + visible_ = YES; + + tag_ = kCCNodeTagInvalid; + + zOrder_ = 0; + + // lazy alloc + camera_ = nil; + + // children (lazy allocs) + children_ = nil; + + // userData is always inited as nil + userData_ = nil; + + //initialize parent to nil + parent_ = nil; + } + + return self; +} + +- (void)cleanup +{ + // actions + [self stopAllActions]; + [self unscheduleAllSelectors]; + + // timers + [children_ makeObjectsPerformSelector:@selector(cleanup)]; +} + +- (NSString*) description +{ + return [NSString stringWithFormat:@"<%@ = %08X | Tag = %i>", [self class], self, tag_]; +} + +- (void) dealloc +{ + CCLOGINFO( @"cocos2d: deallocing %@", self); + + // attributes + [camera_ release]; + + [grid_ release]; + + // children + CCNode *child; + CCARRAY_FOREACH(children_, child) + child.parent = nil; + + [children_ release]; + + [super dealloc]; +} + +#pragma mark CCNode Composition + +-(void) childrenAlloc +{ + children_ = [[CCArray alloc] initWithCapacity:4]; +} + +// camera: lazy alloc +-(CCCamera*) camera +{ + if( ! camera_ ) { + camera_ = [[CCCamera alloc] init]; + + // by default, center camera at the Sprite's anchor point + // [camera_ setCenterX:anchorPointInPixels_.x centerY:anchorPointInPixels_.y centerZ:0]; + // [camera_ setEyeX:anchorPointInPixels_.x eyeY:anchorPointInPixels_.y eyeZ:1]; + + // [camera_ setCenterX:0 centerY:0 centerZ:0]; + // [camera_ setEyeX:0 eyeY:0 eyeZ:1]; + + } + + return camera_; +} + +-(CCNode*) getChildByTag:(NSInteger) aTag +{ + NSAssert( aTag != kCCNodeTagInvalid, @"Invalid tag"); + + CCNode *node; + CCARRAY_FOREACH(children_, node){ + if( node.tag == aTag ) + return node; + } + // not found + return nil; +} + +/* "add" logic MUST only be on this method + * If a class want's to extend the 'addChild' behaviour it only needs + * to override this method + */ +-(void) addChild: (CCNode*) child z:(NSInteger)z tag:(NSInteger) aTag +{ + NSAssert( child != nil, @"Argument must be non-nil"); + NSAssert( child.parent == nil, @"child already added. It can't be added again"); + + if( ! children_ ) + [self childrenAlloc]; + + [self insertChild:child z:z]; + + child.tag = aTag; + + [child setParent: self]; + + if( isRunning_ ) { + [child onEnter]; + [child onEnterTransitionDidFinish]; + } +} + +-(void) addChild: (CCNode*) child z:(NSInteger)z +{ + NSAssert( child != nil, @"Argument must be non-nil"); + [self addChild:child z:z tag:child.tag]; +} + +-(void) addChild: (CCNode*) child +{ + NSAssert( child != nil, @"Argument must be non-nil"); + [self addChild:child z:child.zOrder tag:child.tag]; +} + +-(void) removeFromParentAndCleanup:(BOOL)cleanup +{ + [parent_ removeChild:self cleanup:cleanup]; +} + +/* "remove" logic MUST only be on this method + * If a class want's to extend the 'removeChild' behavior it only needs + * to override this method + */ +-(void) removeChild: (CCNode*)child cleanup:(BOOL)cleanup +{ + // explicit nil handling + if (child == nil) + return; + + if ( [children_ containsObject:child] ) + [self detachChild:child cleanup:cleanup]; +} + +-(void) removeChildByTag:(NSInteger)aTag cleanup:(BOOL)cleanup +{ + NSAssert( aTag != kCCNodeTagInvalid, @"Invalid tag"); + + CCNode *child = [self getChildByTag:aTag]; + + if (child == nil) + CCLOG(@"cocos2d: removeChildByTag: child not found!"); + else + [self removeChild:child cleanup:cleanup]; +} + +-(void) removeAllChildrenWithCleanup:(BOOL)cleanup +{ + // not using detachChild improves speed here + CCNode *c; + CCARRAY_FOREACH(children_, c) + { + // IMPORTANT: + // -1st do onExit + // -2nd cleanup + if (isRunning_) + [c onExit]; + + if (cleanup) + [c cleanup]; + + // set parent nil at the end (issue #476) + [c setParent:nil]; + } + + [children_ removeAllObjects]; +} + +-(void) detachChild:(CCNode *)child cleanup:(BOOL)doCleanup +{ + // IMPORTANT: + // -1st do onExit + // -2nd cleanup + if (isRunning_) + [child onExit]; + + // If you don't do cleanup, the child's actions will not get removed and the + // its scheduledSelectors_ dict will not get released! + if (doCleanup) + [child cleanup]; + + // set parent nil at the end (issue #476) + [child setParent:nil]; + + [children_ removeObject:child]; +} + +// used internally to alter the zOrder variable. DON'T call this method manually +-(void) _setZOrder:(NSInteger) z +{ + zOrder_ = z; +} + +// helper used by reorderChild & add +-(void) insertChild:(CCNode*)child z:(NSInteger)z +{ + NSUInteger index=0; + CCNode *a = [children_ lastObject]; + + // quick comparison to improve performance + if (!a || a.zOrder <= z) + [children_ addObject:child]; + + else + { + CCARRAY_FOREACH(children_, a) { + if ( a.zOrder > z ) { + [children_ insertObject:child atIndex:index]; + break; + } + index++; + } + } + + [child _setZOrder:z]; +} + +-(void) reorderChild:(CCNode*) child z:(NSInteger)z +{ + NSAssert( child != nil, @"Child must be non-nil"); + + [child retain]; + [children_ removeObject:child]; + + [self insertChild:child z:z]; + + [child release]; +} + +#pragma mark CCNode Draw + +-(void) draw +{ + // override me + // Only use this function to draw your staff. + // DON'T draw your stuff outside this method +} + +-(void) visit +{ + // quick return if not visible + if (!visible_) + return; + + glPushMatrix(); + + if ( grid_ && grid_.active) { + [grid_ beforeDraw]; + [self transformAncestors]; + } + + [self transform]; + + if(children_) { + ccArray *arrayData = children_->data; + NSUInteger i = 0; + + // draw children zOrder < 0 + for( ; i < arrayData->num; i++ ) { + CCNode *child = arrayData->arr[i]; + if ( [child zOrder] < 0 ) + [child visit]; + else + break; + } + + // self draw + [self draw]; + + // draw children zOrder >= 0 + for( ; i < arrayData->num; i++ ) { + CCNode *child = arrayData->arr[i]; + [child visit]; + } + + } else + [self draw]; + + if ( grid_ && grid_.active) + [grid_ afterDraw:self]; + + glPopMatrix(); +} + +#pragma mark CCNode - Transformations + +-(void) transformAncestors +{ + if( parent_ ) { + [parent_ transformAncestors]; + [parent_ transform]; + } +} + +-(void) transform +{ + // transformations + +#if CC_NODE_TRANSFORM_USING_AFFINE_MATRIX + // BEGIN alternative -- using cached transform + // + if( isTransformGLDirty_ ) { + CGAffineTransform t = [self nodeToParentTransform]; + CGAffineToGL(&t, transformGL_); + isTransformGLDirty_ = NO; + } + + glMultMatrixf(transformGL_); + if( vertexZ_ ) + glTranslatef(0, 0, vertexZ_); + + // XXX: Expensive calls. Camera should be integrated into the cached affine matrix + if ( camera_ && !(grid_ && grid_.active) ) + { + BOOL translate = (anchorPointInPixels_.x != 0.0f || anchorPointInPixels_.y != 0.0f); + + if( translate ) + ccglTranslate(RENDER_IN_SUBPIXEL(anchorPointInPixels_.x), RENDER_IN_SUBPIXEL(anchorPointInPixels_.y), 0); + + [camera_ locate]; + + if( translate ) + ccglTranslate(RENDER_IN_SUBPIXEL(-anchorPointInPixels_.x), RENDER_IN_SUBPIXEL(-anchorPointInPixels_.y), 0); + } + + + // END alternative + +#else + // BEGIN original implementation + // + // translate + if ( isRelativeAnchorPoint_ && (anchorPointInPixels_.x != 0 || anchorPointInPixels_.y != 0 ) ) + glTranslatef( RENDER_IN_SUBPIXEL(-anchorPointInPixels_.x), RENDER_IN_SUBPIXEL(-anchorPointInPixels_.y), 0); + + if (anchorPointInPixels_.x != 0 || anchorPointInPixels_.y != 0) + glTranslatef( RENDER_IN_SUBPIXEL(positionInPixels_.x + anchorPointInPixels_.x), RENDER_IN_SUBPIXEL(positionInPixels_.y + anchorPointInPixels_.y), vertexZ_); + else if ( positionInPixels_.x !=0 || positionInPixels_.y !=0 || vertexZ_ != 0) + glTranslatef( RENDER_IN_SUBPIXEL(positionInPixels_.x), RENDER_IN_SUBPIXEL(positionInPixels_.y), vertexZ_ ); + + // rotate + if (rotation_ != 0.0f ) + glRotatef( -rotation_, 0.0f, 0.0f, 1.0f ); + + // skew + if ( (skewX_ != 0.0f) || (skewY_ != 0.0f) ) { + CGAffineTransform skewMatrix = CGAffineTransformMake( 1.0f, tanf(CC_DEGREES_TO_RADIANS(skewY_)), tanf(CC_DEGREES_TO_RADIANS(skewX_)), 1.0f, 0.0f, 0.0f ); + GLfloat glMatrix[16]; + CGAffineToGL(&skewMatrix, glMatrix); + glMultMatrixf(glMatrix); + } + + // scale + if (scaleX_ != 1.0f || scaleY_ != 1.0f) + glScalef( scaleX_, scaleY_, 1.0f ); + + if ( camera_ && !(grid_ && grid_.active) ) + [camera_ locate]; + + // restore and re-position point + if (anchorPointInPixels_.x != 0.0f || anchorPointInPixels_.y != 0.0f) + glTranslatef(RENDER_IN_SUBPIXEL(-anchorPointInPixels_.x), RENDER_IN_SUBPIXEL(-anchorPointInPixels_.y), 0); + + // + // END original implementation +#endif + +} + +#pragma mark CCNode SceneManagement + +-(void) onEnter +{ + [children_ makeObjectsPerformSelector:@selector(onEnter)]; + [self resumeSchedulerAndActions]; + + isRunning_ = YES; +} + +-(void) onEnterTransitionDidFinish +{ + [children_ makeObjectsPerformSelector:@selector(onEnterTransitionDidFinish)]; +} + +-(void) onExit +{ + [self pauseSchedulerAndActions]; + isRunning_ = NO; + + [children_ makeObjectsPerformSelector:@selector(onExit)]; +} + +#pragma mark CCNode Actions + +-(CCAction*) runAction:(CCAction*) action +{ + NSAssert( action != nil, @"Argument must be non-nil"); + + [[CCActionManager sharedManager] addAction:action target:self paused:!isRunning_]; + return action; +} + +-(void) stopAllActions +{ + [[CCActionManager sharedManager] removeAllActionsFromTarget:self]; +} + +-(void) stopAction: (CCAction*) action +{ + [[CCActionManager sharedManager] removeAction:action]; +} + +-(void) stopActionByTag:(NSInteger)aTag +{ + NSAssert( aTag != kCCActionTagInvalid, @"Invalid tag"); + [[CCActionManager sharedManager] removeActionByTag:aTag target:self]; +} + +-(CCAction*) getActionByTag:(NSInteger) aTag +{ + NSAssert( aTag != kCCActionTagInvalid, @"Invalid tag"); + return [[CCActionManager sharedManager] getActionByTag:aTag target:self]; +} + +-(NSUInteger) numberOfRunningActions +{ + return [[CCActionManager sharedManager] numberOfRunningActionsInTarget:self]; +} + +#pragma mark CCNode - Scheduler + +-(void) scheduleUpdate +{ + [self scheduleUpdateWithPriority:0]; +} + +-(void) scheduleUpdateWithPriority:(NSInteger)priority +{ + [[CCScheduler sharedScheduler] scheduleUpdateForTarget:self priority:priority paused:!isRunning_]; +} + +-(void) unscheduleUpdate +{ + [[CCScheduler sharedScheduler] unscheduleUpdateForTarget:self]; +} + +-(void) schedule:(SEL)selector +{ + [self schedule:selector interval:0]; +} + +-(void) schedule:(SEL)selector interval:(ccTime)interval +{ + NSAssert( selector != nil, @"Argument must be non-nil"); + NSAssert( interval >=0, @"Arguemnt must be positive"); + + [[CCScheduler sharedScheduler] scheduleSelector:selector forTarget:self interval:interval paused:!isRunning_]; +} + +-(void) unschedule:(SEL)selector +{ + // explicit nil handling + if (selector == nil) + return; + + [[CCScheduler sharedScheduler] unscheduleSelector:selector forTarget:self]; +} + +-(void) unscheduleAllSelectors +{ + [[CCScheduler sharedScheduler] unscheduleAllSelectorsForTarget:self]; +} +- (void) resumeSchedulerAndActions +{ + [[CCScheduler sharedScheduler] resumeTarget:self]; + [[CCActionManager sharedManager] resumeTarget:self]; +} + +- (void) pauseSchedulerAndActions +{ + [[CCScheduler sharedScheduler] pauseTarget:self]; + [[CCActionManager sharedManager] pauseTarget:self]; +} + +#pragma mark CCNode Transform + +- (CGAffineTransform)nodeToParentTransform +{ + if ( isTransformDirty_ ) { + + transform_ = CGAffineTransformIdentity; + + if ( !isRelativeAnchorPoint_ && !CGPointEqualToPoint(anchorPointInPixels_, CGPointZero) ) + transform_ = CGAffineTransformTranslate(transform_, anchorPointInPixels_.x, anchorPointInPixels_.y); + + if( ! CGPointEqualToPoint(positionInPixels_, CGPointZero) ) + transform_ = CGAffineTransformTranslate(transform_, positionInPixels_.x, positionInPixels_.y); + + if( rotation_ != 0 ) + transform_ = CGAffineTransformRotate(transform_, -CC_DEGREES_TO_RADIANS(rotation_)); + + if( skewX_ != 0 || skewY_ != 0 ) { + // create a skewed coordinate system + CGAffineTransform skew = CGAffineTransformMake(1.0f, tanf(CC_DEGREES_TO_RADIANS(skewY_)), tanf(CC_DEGREES_TO_RADIANS(skewX_)), 1.0f, 0.0f, 0.0f); + // apply the skew to the transform + transform_ = CGAffineTransformConcat(skew, transform_); + } + + if( ! (scaleX_ == 1 && scaleY_ == 1) ) + transform_ = CGAffineTransformScale(transform_, scaleX_, scaleY_); + + if( ! CGPointEqualToPoint(anchorPointInPixels_, CGPointZero) ) + transform_ = CGAffineTransformTranslate(transform_, -anchorPointInPixels_.x, -anchorPointInPixels_.y); + + isTransformDirty_ = NO; + } + + return transform_; +} + +- (CGAffineTransform)parentToNodeTransform +{ + if ( isInverseDirty_ ) { + inverse_ = CGAffineTransformInvert([self nodeToParentTransform]); + isInverseDirty_ = NO; + } + + return inverse_; +} + +- (CGAffineTransform)nodeToWorldTransform +{ + CGAffineTransform t = [self nodeToParentTransform]; + + for (CCNode *p = parent_; p != nil; p = p.parent) + t = CGAffineTransformConcat(t, [p nodeToParentTransform]); + + return t; +} + +- (CGAffineTransform)worldToNodeTransform +{ + return CGAffineTransformInvert([self nodeToWorldTransform]); +} + +- (CGPoint)convertToNodeSpace:(CGPoint)worldPoint +{ + CGPoint ret; + if( CC_CONTENT_SCALE_FACTOR() == 1 ) + ret = CGPointApplyAffineTransform(worldPoint, [self worldToNodeTransform]); + else { + ret = ccpMult( worldPoint, CC_CONTENT_SCALE_FACTOR() ); + ret = CGPointApplyAffineTransform(ret, [self worldToNodeTransform]); + ret = ccpMult( ret, 1/CC_CONTENT_SCALE_FACTOR() ); + } + + return ret; +} + +- (CGPoint)convertToWorldSpace:(CGPoint)nodePoint +{ + CGPoint ret; + if( CC_CONTENT_SCALE_FACTOR() == 1 ) + ret = CGPointApplyAffineTransform(nodePoint, [self nodeToWorldTransform]); + else { + ret = ccpMult( nodePoint, CC_CONTENT_SCALE_FACTOR() ); + ret = CGPointApplyAffineTransform(ret, [self nodeToWorldTransform]); + ret = ccpMult( ret, 1/CC_CONTENT_SCALE_FACTOR() ); + } + + return ret; +} + +- (CGPoint)convertToNodeSpaceAR:(CGPoint)worldPoint +{ + CGPoint nodePoint = [self convertToNodeSpace:worldPoint]; + CGPoint anchorInPoints; + if( CC_CONTENT_SCALE_FACTOR() == 1 ) + anchorInPoints = anchorPointInPixels_; + else + anchorInPoints = ccpMult( anchorPointInPixels_, 1/CC_CONTENT_SCALE_FACTOR() ); + + return ccpSub(nodePoint, anchorInPoints); +} + +- (CGPoint)convertToWorldSpaceAR:(CGPoint)nodePoint +{ + CGPoint anchorInPoints; + if( CC_CONTENT_SCALE_FACTOR() == 1 ) + anchorInPoints = anchorPointInPixels_; + else + anchorInPoints = ccpMult( anchorPointInPixels_, 1/CC_CONTENT_SCALE_FACTOR() ); + + nodePoint = ccpAdd(nodePoint, anchorInPoints); + return [self convertToWorldSpace:nodePoint]; +} + +- (CGPoint)convertToWindowSpace:(CGPoint)nodePoint +{ + CGPoint worldPoint = [self convertToWorldSpace:nodePoint]; + return [[CCDirector sharedDirector] convertToUI:worldPoint]; +} + +// convenience methods which take a UITouch instead of CGPoint + +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED + +- (CGPoint)convertTouchToNodeSpace:(UITouch *)touch +{ + CGPoint point = [touch locationInView: [touch view]]; + point = [[CCDirector sharedDirector] convertToGL: point]; + return [self convertToNodeSpace:point]; +} + +- (CGPoint)convertTouchToNodeSpaceAR:(UITouch *)touch +{ + CGPoint point = [touch locationInView: [touch view]]; + point = [[CCDirector sharedDirector] convertToGL: point]; + return [self convertToNodeSpaceAR:point]; +} + +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED + + +@end diff --git a/libs/cocos2d/CCParallaxNode.h b/libs/cocos2d/CCParallaxNode.h new file mode 100644 index 0000000..5728eb1 --- /dev/null +++ b/libs/cocos2d/CCParallaxNode.h @@ -0,0 +1,50 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * 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 "CCNode.h" +#import "Support/ccCArray.h" + +/** CCParallaxNode: A node that simulates a parallax scroller + + The children will be moved faster / slower than the parent according the the parallax ratio. + + */ +@interface CCParallaxNode : CCNode +{ + ccArray *parallaxArray_; + CGPoint lastPosition; +} + +/** array that holds the offset / ratio of the children */ +@property (nonatomic,readwrite) ccArray * parallaxArray; + +/** Adds a child to the container with a z-order, a parallax ratio and a position offset + It returns self, so you can chain several addChilds. + @since v0.8 + */ +-(void) addChild: (CCNode*)node z:(NSInteger)z parallaxRatio:(CGPoint)c positionOffset:(CGPoint)positionOffset; + +@end diff --git a/libs/cocos2d/CCParallaxNode.m b/libs/cocos2d/CCParallaxNode.m new file mode 100644 index 0000000..9d39cc8 --- /dev/null +++ b/libs/cocos2d/CCParallaxNode.m @@ -0,0 +1,161 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * 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 "CCParallaxNode.h" +#import "Support/CGPointExtension.h" +#import "Support/ccCArray.h" + +@interface CGPointObject : NSObject +{ + CGPoint ratio_; + CGPoint offset_; + CCNode *child_; // weak ref +} +@property (readwrite) CGPoint ratio; +@property (readwrite) CGPoint offset; +@property (readwrite,assign) CCNode *child; ++(id) pointWithCGPoint:(CGPoint)point offset:(CGPoint)offset; +-(id) initWithCGPoint:(CGPoint)point offset:(CGPoint)offset; +@end +@implementation CGPointObject +@synthesize ratio = ratio_; +@synthesize offset = offset_; +@synthesize child=child_; + ++(id) pointWithCGPoint:(CGPoint)ratio offset:(CGPoint)offset +{ + return [[[self alloc] initWithCGPoint:ratio offset:offset] autorelease]; +} +-(id) initWithCGPoint:(CGPoint)ratio offset:(CGPoint)offset +{ + if( (self=[super init])) { + ratio_ = ratio; + offset_ = offset; + } + return self; +} +@end + +@implementation CCParallaxNode + +@synthesize parallaxArray = parallaxArray_; + +-(id) init +{ + if( (self=[super init]) ) { + parallaxArray_ = ccArrayNew(5); + lastPosition = CGPointMake(-100,-100); + } + return self; +} + +- (void) dealloc +{ + if( parallaxArray_ ) { + ccArrayFree(parallaxArray_); + parallaxArray_ = nil; + } + [super dealloc]; +} + +-(void) addChild:(CCNode*)child z:(NSInteger)z tag:(NSInteger)tag +{ + NSAssert(NO,@"ParallaxNode: use addChild:z:parallaxRatio:positionOffset instead"); +} + +-(void) addChild: (CCNode*) child z:(NSInteger)z parallaxRatio:(CGPoint)ratio positionOffset:(CGPoint)offset +{ + NSAssert( child != nil, @"Argument must be non-nil"); + CGPointObject *obj = [CGPointObject pointWithCGPoint:ratio offset:offset]; + obj.child = child; + ccArrayAppendObjectWithResize(parallaxArray_, obj); + + CGPoint pos = self.position; + pos.x = pos.x * ratio.x + offset.x; + pos.y = pos.y * ratio.y + offset.y; + child.position = pos; + + [super addChild: child z:z tag:child.tag]; +} + +-(void) removeChild:(CCNode*)node cleanup:(BOOL)cleanup +{ + for( unsigned int i=0;i < parallaxArray_->num;i++) { + CGPointObject *point = parallaxArray_->arr[i]; + if( [point.child isEqual:node] ) { + ccArrayRemoveObjectAtIndex(parallaxArray_, i); + break; + } + } + [super removeChild:node cleanup:cleanup]; +} + +-(void) removeAllChildrenWithCleanup:(BOOL)cleanup +{ + ccArrayRemoveAllObjects(parallaxArray_); + [super removeAllChildrenWithCleanup:cleanup]; +} + +-(CGPoint) absolutePosition_ +{ + CGPoint ret = position_; + + CCNode *cn = self; + + while (cn.parent != nil) { + cn = cn.parent; + ret = ccpAdd( ret, cn.position ); + } + + return ret; +} + +/* + The positions are updated at visit because: + - using a timer is not guaranteed that it will called after all the positions were updated + - overriding "draw" will only precise if the children have a z > 0 +*/ +-(void) visit +{ +// CGPoint pos = position_; +// CGPoint pos = [self convertToWorldSpace:CGPointZero]; + CGPoint pos = [self absolutePosition_]; + if( ! CGPointEqualToPoint(pos, lastPosition) ) { + + for(unsigned int i=0; i < parallaxArray_->num; i++ ) { + + CGPointObject *point = parallaxArray_->arr[i]; + float x = -pos.x + pos.x * point.ratio.x + point.offset.x; + float y = -pos.y + pos.y * point.ratio.y + point.offset.y; + point.child.position = ccp(x,y); + } + + lastPosition = pos; + } + + [super visit]; +} +@end diff --git a/libs/cocos2d/CCParticleExamples.h b/libs/cocos2d/CCParticleExamples.h new file mode 100644 index 0000000..cd382c4 --- /dev/null +++ b/libs/cocos2d/CCParticleExamples.h @@ -0,0 +1,111 @@ +/* + * 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. + * + */ + + +#import + +#import "CCParticleSystemPoint.h" +#import "CCParticleSystemQuad.h" + +// build each architecture with the optimal particle system + +// ARMv7, Mac or Simulator use "Quad" particle +#if defined(__ARM_NEON__) || defined(__MAC_OS_X_VERSION_MAX_ALLOWED) || TARGET_IPHONE_SIMULATOR + #define ARCH_OPTIMAL_PARTICLE_SYSTEM CCParticleSystemQuad + +// ARMv6 use "Point" particle +#elif __arm__ + #define ARCH_OPTIMAL_PARTICLE_SYSTEM CCParticleSystemPoint +#else + #error(unknown architecture) +#endif + + +//! A fire particle system +@interface CCParticleFire: ARCH_OPTIMAL_PARTICLE_SYSTEM +{ +} +@end + +//! A fireworks particle system +@interface CCParticleFireworks : ARCH_OPTIMAL_PARTICLE_SYSTEM +{ +} +@end + +//! A sun particle system +@interface CCParticleSun : ARCH_OPTIMAL_PARTICLE_SYSTEM +{ +} +@end + +//! A galaxy particle system +@interface CCParticleGalaxy : ARCH_OPTIMAL_PARTICLE_SYSTEM +{ +} +@end + +//! A flower particle system +@interface CCParticleFlower : ARCH_OPTIMAL_PARTICLE_SYSTEM +{ +} +@end + +//! A meteor particle system +@interface CCParticleMeteor : ARCH_OPTIMAL_PARTICLE_SYSTEM +{ +} +@end + +//! An spiral particle system +@interface CCParticleSpiral : ARCH_OPTIMAL_PARTICLE_SYSTEM +{ +} +@end + +//! An explosion particle system +@interface CCParticleExplosion : ARCH_OPTIMAL_PARTICLE_SYSTEM +{ +} +@end + +//! An smoke particle system +@interface CCParticleSmoke : ARCH_OPTIMAL_PARTICLE_SYSTEM +{ +} +@end + +//! An snow particle system +@interface CCParticleSnow : ARCH_OPTIMAL_PARTICLE_SYSTEM +{ +} +@end + +//! A rain particle system +@interface CCParticleRain : ARCH_OPTIMAL_PARTICLE_SYSTEM +{ +} +@end diff --git a/libs/cocos2d/CCParticleExamples.m b/libs/cocos2d/CCParticleExamples.m new file mode 100644 index 0000000..38c8b46 --- /dev/null +++ b/libs/cocos2d/CCParticleExamples.m @@ -0,0 +1,926 @@ +/* + * 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. + * + */ + + +// cocos2d +#import "CCParticleExamples.h" +#import "CCTextureCache.h" +#import "CCDirector.h" +#import "Support/CGPointExtension.h" + +// +// ParticleFireworks +// +@implementation CCParticleFireworks +-(id) init +{ + return [self initWithTotalParticles:1500]; +} + +-(id) initWithTotalParticles:(NSUInteger)p +{ + if( (self=[super initWithTotalParticles:p]) ) { + // duration + duration = kCCParticleDurationInfinity; + + // Gravity Mode + self.emitterMode = kCCParticleModeGravity; + + // Gravity Mode: gravity + self.gravity = ccp(0,-90); + + // Gravity Mode: radial + self.radialAccel = 0; + self.radialAccelVar = 0; + + // Gravity Mode: speed of particles + self.speed = 180; + self.speedVar = 50; + + // emitter position + CGSize winSize = [[CCDirector sharedDirector] winSize]; + self.position = ccp(winSize.width/2, winSize.height/2); + + // angle + angle = 90; + angleVar = 20; + + // life of particles + life = 3.5f; + lifeVar = 1; + + // emits per frame + emissionRate = totalParticles/life; + + // color of particles + startColor.r = 0.5f; + startColor.g = 0.5f; + startColor.b = 0.5f; + startColor.a = 1.0f; + startColorVar.r = 0.5f; + startColorVar.g = 0.5f; + startColorVar.b = 0.5f; + startColorVar.a = 0.1f; + endColor.r = 0.1f; + endColor.g = 0.1f; + endColor.b = 0.1f; + endColor.a = 0.2f; + endColorVar.r = 0.1f; + endColorVar.g = 0.1f; + endColorVar.b = 0.1f; + endColorVar.a = 0.2f; + + // size, in pixels + startSize = 8.0f; + startSizeVar = 2.0f; + endSize = kCCParticleStartSizeEqualToEndSize; + + self.texture = [[CCTextureCache sharedTextureCache] addImage: @"fire.png"]; + + // additive + self.blendAdditive = NO; + } + + return self; +} +@end + +// +// ParticleFire +// +@implementation CCParticleFire +-(id) init +{ + return [self initWithTotalParticles:250]; +} + +-(id) initWithTotalParticles:(NSUInte