# Xcode
-build/*
+*/build/*
*.pbxuser
!default.pbxuser
*.mode1v3
--- /dev/null
+----------------------------------------------------------------------------------------------------
+Sparrow: Tips for building an app
+----------------------------------------------------------------------------------------------------
+
+--- A First look at Sparrow ------------------------------------------------------------------------
+
+In the folder 'samples/demo', you will find an Xcode project that shows the most basic Sparrow
+features and how to use them. Just open the project in Xcode, compile and run - everything should
+work out of the box.
+
+--- Creating a new Xcode project that uses Sparrow -------------------------------------------------
+
+In the folder 'samples/scaffold', you will find an Xcode project that contains a bare-bone Sparrow
+application. We recommend that you use this project as a starting point for your game. Please follow
+this steps to use this Scaffold:
+
+Preconditions:
+
+Sparrow is linked to your application via Xcode project references. This has the advantage that it's
+easy to update Sparrow (just download a new release and overwrite the old one in the same directory)
+and that you can easily step into Sparrow source code, I case you want to do so.
+
+This has to be done only once:
+
+1. Set up a shared build output directory that will be shared by all Xcode projects.
+ * In the Xcode preferences, tab: "Building", set "Place Build Products in" to
+ "Customized location" and specify a common build directory (anywhere you want).
+ * Set "Place Intermediate Build Files in" to "With build products."
+2. Add a "Source Tree" variable that Xcode can use to dynamically find Sparrow.
+ * In the Xcode preferences, tab: "Source Trees", create a new Source Tree variable.
+ * For Sparrow, create SPARROW_SRC and let it point to /some_valid_path/sparrow/src/
+
+Creating your new project:
+
+1. Copy the "scaffold"-folder to the place where you want to have your game project.
+2. Open "AppScaffold.xcodeproj"
+3. Build and run - just to see if everything works fine. If it does not work, check if you have
+ created the SPARROW_SRC variable in Xcode, and if it points to the right place.
+4. In Xcodes menu, click on "Project" -> "Rename ..."
+5. Enter the name of your game.
+6. That's it! Now you can start to develop your game with Sparrow.
+
+Selecting target hardware: iPhone / iPod Touch / iPad
+
+1. Enter the project Settings, tab: "Build"
+2. Select the target of your choice for the setting "Targeted Device Family"
\ No newline at end of file
--- /dev/null
+----------------------------------------------------------------------------------------------------
+Sparrow: Changelog
+----------------------------------------------------------------------------------------------------
+
+version 1.1 - 2011-01-12
+
+- added SPRenderTexture
+- added SPUtils class for easy random number generation (more to come)
+- added support for looping and reversing tweens (thanks, Shilo!)
+- added new transition method: 'randomize'
+- added support for uncompressed PVR texture formats (565, 5551, 4444)
+- added simple way to use HD textures on the iPad
+- added support for creating dynamic texture atlases (add/remove regions on the fly)
+- added methods to access the glyphs of SPBitmapFont directly
+- added new init method to SPImage: 'initWithContentsOfImage:(UIImage *)image'
+- added more factory methods to different classes
+- added support for changing the fps of SPMovieClip at runtime (thanks Shilo!)
+- added AppleDoc inline API documentation
+- added bash script that generates API documentation
+- updated SPView class to be more robust
+- updated general rendering code (moved more OpenGL calls to SPRenderTexture)
+- fixed bug with canceled touch events
+- fixed bug that could cause a breakdown of the touch handling (thanks Kodi!)
+- fixed 'isEqual' method of SPMatrix (thanks Matt!)
+- fixed bug that could cut the outermost pixels of HD textures
+- Special thanks to numerous forum members for bug reports, suggestions and feedback!
+
+version 1.0 - 2010-10-24
+
+- added support for PVRTC-textures
+- added new init method to SPTexture to allow simple use of Core Graphics drawings
+- added new init method to SPMovieClip that uses an NSArray containing the frames
+- added method 'childByName:' to SPDisplayObjectContainer
+- added method 'texturesStartingWith:' to SPTextureAtlas
+- added method 'scaleBy:' to SPPoint
+- added method 'interpolateFromPoint:toPoint:ratio:' to SPPoint
+- added property 'textBounds' to SPTextField
+- added property 'name' to SPDisplayObject
+- added workaround for unit test problem in Xcode 3.2.4
+- updated SPCompiledSprite-class to be public
+- updated texture utilities for sharper output
+- updated scaffold so that it is easier to create an iPad application
+- fixed bug that classes inheriting directly from SPQuad were not rendered
+- fixed that last-moment changes were not displayed when pausing stage
+- fixed bug that could lead to a white flash when textures were released
+- Special thanks to numerous forum members for bug reports, suggestions and feedback!
+
+version 0.9 - 2010-07-30
+
+- !!! interface change !!!
+ The IDs of vertices in SPQuad and SPImage have changed. ("colorOfVertex:", "texCoordsOfVertex:")
+ Before: 0 = top left, 1 = top right, 2 = bottom right, 3 = bottom left
+ AFTER: 0 = top left, 1 = top right, 2 = bottom left, 3 = bottom right
+- greatly improved rendering speed of SPTextField when used with bitmap fonts
+- greatly improved performance of touch event analysis
+- added Sparrow atlas generator (sparrow/util/atlas_generator)
+- added Sparrow texture resizer (sparrow/util/texture_scaler)
+- added support for high resolution screens (aka iPhone4's retina display)
+- added support for high resolution textures ("texture@2x.png")
+- added support for high resolution texture atlases ("atlas@2x.xml")
+- added support for high resolution bitmap fonts ("font@2x.xml")
+- added support for loading textures that are not inside the application bundle
+- added support for loading sounds that are not inside the application bundle
+- changed base SDK to iOS 4.0
+- added new event: SP_EVENT_TYPE_MOVIE_COMPLETED in SPMovieClip class
+- added experimental feature: SPCompiledSprite
+- added some missing "@private" declarations
+- fixed memory access violation when object was destroyed within an enter frame event listener
+- fixed bug that alpha values were only used when a texture was active
+- fixed bug that SPDelayedInvocation (aka SPJuggler::delayInvocationAtTarget) did not retain
+ its arguments
+- fixed bug that cancelled touch events would inhibit further user input
+- code cleanup (especially concerning designated initializers)
+- Special thanks to: Mike, Baike, Paolo, Jule and Alex_H for bug reports, suggestions and feedback!
+
+version 0.8 - 2010-06-05
+
+- added audio classes
+- added SPMovieClip class
+- added new transition functions:
+ - easeInBack / easeOutBack / easeInOutBack / easeOutInBack
+ - easeInElastic / easeOutElastic / easeInOutElastic / easeOutInElastic
+ - easeInBounce / easeOutBounce / easeInOutBounce / easeOutInBounce
+- added 'removeTweensWithTarget:'-method to juggler
+- added 'removeAllChildren'-method to SPDisplayObjectContainer
+- added new text-only constructor to SPTextField
+- added NSXMLParserDelegate protocol statement for iPhone SDK 4+
+- added packer2sparrow utility
+- added hiero2sparrow utility
+- changed transition function signature (removed delta)
+- changed rotation handling: angles now clamped from -180 to +180 degrees.
+ this should make most rotation tweens more intuitive.
+- changed license text to allow easy AppStore distribution
+- changed scaffold project to support audio
+- changed demo project
+ - new design
+ - new scene: sound
+ - new scene: movie
+- fixed touch issues when view size != stage size
+- fixed exception that occurred when the same object was added to a container twice
+- fixed flickering at application start
+- fixed method signatures in SPTexture.m
+- 'removeChild'-method in SPDisplayObjectContainer no longer throws an exception when the
+ object is not a child, but now silently ignores the failure.
+- the stage property is now accessible in the REMOVED_FROM_STAGE event
+- disabled unit test execution in iPhone SDK < 3 (unit tests are only supported by iPhone SDK 3+)
+
+version 0.7 - 2010-01-14
+
+- first public version
--- /dev/null
+----------------------------------------------------------------------------------------------
+Sparrow: Simplified BSD License
+----------------------------------------------------------------------------------------------
+
+Copyright 2010 incognitek. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ of conditions and the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+ 3. Redistributions via the Apple App Store constitute an exception to section 2. It is
+ sufficient to add a copy of this license to the application's internet page (if available).
+ The content of the following disclaimer is still in effect.
+
+THIS SOFTWARE IS PROVIDED BY INCOGNITEK ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INCOGNITEK OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those of the
+authors and should not be interpreted as representing official policies, either expressed
+or implied, of incognitek.
\ No newline at end of file
--- /dev/null
+----------------------------------------------------------------------------------------------------
+Sparrow: an Open Source Framework for iPhone game development
+----------------------------------------------------------------------------------------------------
+
+--- What is Sparrow? -------------------------------------------------------------------------------
+
+Sparrow is a pure Objective C library targeted on making game development as easy and hassle-free
+as possible. Sparrow makes it possible to write fast OpenGL applications without having to touch
+OpenGL or pure C (but easily allowing to do so, for those who wish). It uses a tried and tested
+API that is easy to use and hard to misuse.
+
+--- Who is Sparrow for? ----------------------------------------------------------------------------
+
+Obviously Sparrow is for iPhone developers, especially those involved in game development. You
+will need to have a basic understanding of Objective C – but there’s no way around that on the
+iPhone anyway.
+
+If you have already worked with Adobeâ„¢ Flash/Flex technology, you will immediately befriend with
+Sparrow since it uses lots of similar concepts and naming schemes. That said, everything is
+designed to be as intuitive as possible, so any Javaâ„¢ or .Netâ„¢ developer will get the hang of it
+quickly as well.
+
+--- How to start? ----------------------------------------------------------------------------------
+
+* Read through the file 'BUILDING' for a quick start with Sparrow.
+* Visit --> http://www.sparrow-framework.org <--
\ No newline at end of file
--- /dev/null
+#!/bin/bash
+
+# This script creates a nice API reference documentation for the Sparrow source
+# and installs it in Xcode.
+#
+# To execute it, you need the "AppleDoc"-tool. Download it here:
+# http://www.gentlebytes.com/home/appledocapp/
+
+echo "Please enter the version number (like '1.0'), followed by [ENTER]:"
+read version
+
+appledoc \
+ --project-name "Sparrow Framework" \
+ --project-company "Incognitek" \
+ --company-id com.incognitek \
+ --project-version "$version" \
+ --ignore ".m" \
+ --ignore "_Internal.h" \
+ --keep-undocumented-objects \
+ --keep-undocumented-members \
+ --keep-intermediate-files \
+ --docset-bundle-id "org.sparrow-framework.docset" \
+ --docset-bundle-name "Sparrow Framework API Documentation" \
+ --docset-atom-filename "docset.atom" \
+ --docset-feed-url "http://doc.sparrow-framework.org/core/feed/%DOCSETATOMFILENAME" \
+ --docset-package-url "http://doc.sparrow-framework.org/core/feed/%DOCSETPACKAGEFILENAME" \
+ --install-docset \
+ --publish-docset \
+ --output . \
+ ../src/Classes
+
+echo
+echo "Finished."
--- /dev/null
+//
+// SPALSound.h
+// Sparrow
+//
+// Created by Daniel Sperl on 28.05.10.
+// Copyright 2010 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+#import "SPSound.h"
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPALSound class is a concrete implementation of SPSound that uses OpenAL internally.
+
+ Don't create instances of this class manually. Use [SPSound initWithContentsOfFile:] instead.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPALSound : SPSound
+{
+ @private
+ uint mBufferID;
+ double mDuration;
+}
+
+/// --------------------
+/// @name Initialization
+/// --------------------
+
+/// Initializes a sound with its known properties.
+- (id)initWithData:(const void *)data size:(int)size channels:(int)channels frequency:(int)frequency
+ duration:(double)duration;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The OpenAL buffer ID of the sound.
+@property (nonatomic, readonly) uint bufferID;
+
+@end
--- /dev/null
+//
+// SPALSound.m
+// Sparrow
+//
+// Created by Daniel Sperl on 28.05.10.
+// Copyright 2010 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import "SPALSound.h"
+#import "SPALSoundChannel.h"
+#import "SPAudioEngine.h"
+
+#import <OpenAL/al.h>
+#import <OpenAL/alc.h>
+
+@implementation SPALSound
+
+@synthesize duration = mDuration;
+@synthesize bufferID = mBufferID;
+
+- (id)init
+{
+ [self release];
+ return nil;
+}
+
+- (id)initWithData:(void *)data size:(int)size channels:(int)channels frequency:(int)frequency
+ duration:(double)duration
+{
+ if (self = [super init])
+ {
+ BOOL success = NO;
+ mDuration = duration;
+
+ do
+ {
+ [SPAudioEngine start];
+
+ ALCcontext *const currentContext = alcGetCurrentContext();
+ if (!currentContext)
+ {
+ NSLog(@"Could not get current OpenAL context");
+ break;
+ }
+
+ ALenum errorCode;
+
+ alGenBuffers(1, &mBufferID);
+ errorCode = alGetError();
+ if (errorCode != AL_NO_ERROR)
+ {
+ NSLog(@"Could not allocate OpenAL buffer (%x)", errorCode);
+ break;
+ }
+
+ int format = (channels > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
+
+ alBufferData(mBufferID, format, data, size, frequency);
+ errorCode = alGetError();
+ if (errorCode != AL_NO_ERROR)
+ {
+ NSLog(@"Could not fill OpenAL buffer (%x)", errorCode);
+ break;
+ }
+
+ success = YES;
+ }
+ while (NO);
+
+ if (!success)
+ {
+ [self release];
+ return nil;
+ }
+ }
+ return self;
+}
+
+- (SPSoundChannel *)createChannel
+{
+ return [[[SPALSoundChannel alloc] initWithSound:self] autorelease];
+}
+
+- (void) dealloc
+{
+ alDeleteBuffers(1, &mBufferID);
+ mBufferID = 0;
+ [super dealloc];
+}
+
+@end
--- /dev/null
+//
+// SPALSoundChannel.h
+// Sparrow
+//
+// Created by Daniel Sperl on 28.05.10.
+// Copyright 2010 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+#import "SPSoundChannel.h"
+
+@class SPALSound;
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPALSoundChannel class is a concrete implementation of SPSoundChannel that uses
+ OpenAL internally.
+
+ Don't create instances of this class manually. Use [SPSound createChannel] instead.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPALSoundChannel : SPSoundChannel
+{
+ @private
+ SPALSound *mSound;
+ uint mSourceID;
+ float mVolume;
+ BOOL mLoop;
+
+ double mStartMoment;
+ double mPauseMoment;
+ BOOL mInterrupted;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a sound channel from an SPALSound object.
+- (id)initWithSound:(SPALSound *)sound;
+
+@end
--- /dev/null
+//
+// SPALSoundChannel.m
+// Sparrow
+//
+// Created by Daniel Sperl on 28.05.10.
+// Copyright 2010 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import "SPALSoundChannel.h"
+#import "SPALSound.h"
+#import "SPAudioEngine.h"
+#import "SPMacros.h"
+
+#import <QuartzCore/QuartzCore.h> // for CACurrentMediaTime
+#import <OpenAL/al.h>
+#import <OpenAL/alc.h>
+
+// --- private interface ---------------------------------------------------------------------------
+
+@interface SPALSoundChannel ()
+
+- (void)scheduleSoundCompletedEvent;
+- (void)revokeSoundCompletedEvent;
+
+@end
+
+// --- class implementation ------------------------------------------------------------------------
+
+@implementation SPALSoundChannel
+
+@synthesize volume = mVolume;
+@synthesize loop = mLoop;
+
+- (id)init
+{
+ [self release];
+ return nil;
+}
+
+- (id)initWithSound:(SPALSound *)sound
+{
+ if (self = [super init])
+ {
+ mSound = [sound retain];
+ mVolume = 1.0f;
+ mLoop = NO;
+ mInterrupted = NO;
+ mStartMoment = 0.0;
+ mPauseMoment = 0.0;
+
+ alGenSources(1, &mSourceID);
+ alSourcei(mSourceID, AL_BUFFER, sound.bufferID);
+ ALenum errorCode = alGetError();
+ if (errorCode != AL_NO_ERROR)
+ {
+ NSLog(@"Could not create OpenAL source (%x)", errorCode);
+ [self release];
+ return nil;
+ }
+
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc addObserver:self selector:@selector(onInterruptionBegan:)
+ name:SP_NOTIFICATION_AUDIO_INTERRUPTION_BEGAN object:nil];
+ [nc addObserver:self selector:@selector(onInterruptionEnded:)
+ name:SP_NOTIFICATION_AUDIO_INTERRUPTION_ENDED object:nil];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ alSourceStop(mSourceID);
+ alSourcei(mSourceID, AL_BUFFER, 0);
+ alDeleteSources(1, &mSourceID);
+ mSourceID = 0;
+ [mSound release];
+ [super dealloc];
+}
+
+- (void)play
+{
+ if (!self.isPlaying)
+ {
+ double now = CACurrentMediaTime();
+
+ if (mPauseMoment != 0.0) // paused
+ {
+ mStartMoment += now - mPauseMoment;
+ mPauseMoment = 0.0;
+ }
+ else // stopped
+ {
+ mStartMoment = now;
+ }
+
+ [self scheduleSoundCompletedEvent];
+ alSourcePlay(mSourceID);
+ }
+}
+
+- (void)pause
+{
+ if (self.isPlaying)
+ {
+ [self revokeSoundCompletedEvent];
+ mPauseMoment = CACurrentMediaTime();
+ alSourcePause(mSourceID);
+ }
+}
+
+- (void)stop
+{
+ [self revokeSoundCompletedEvent];
+ mStartMoment = mPauseMoment = 0.0;
+ alSourceStop(mSourceID);
+}
+
+- (BOOL)isPlaying
+{
+ ALint state;
+ alGetSourcei(mSourceID, AL_SOURCE_STATE, &state);
+ return state == AL_PLAYING;
+}
+
+- (BOOL)isPaused
+{
+ ALint state;
+ alGetSourcei(mSourceID, AL_SOURCE_STATE, &state);
+ return state == AL_PAUSED;
+}
+
+- (BOOL)isStopped
+{
+ ALint state;
+ alGetSourcei(mSourceID, AL_SOURCE_STATE, &state);
+ return state == AL_STOPPED;
+}
+
+- (void)setLoop:(BOOL)value
+{
+ if (value != mLoop)
+ {
+ mLoop = value;
+ alSourcei(mSourceID, AL_LOOPING, mLoop);
+ }
+}
+
+- (void)setVolume:(float)value
+{
+ if (value != mVolume)
+ {
+ mVolume = value;
+ alSourcef(mSourceID, AL_GAIN, mVolume);
+ }
+}
+
+- (double)duration
+{
+ return [mSound duration];
+}
+
+- (void)scheduleSoundCompletedEvent
+{
+ if (mStartMoment != 0.0)
+ {
+ double remainingTime = mSound.duration - (CACurrentMediaTime() - mStartMoment);
+ [self revokeSoundCompletedEvent];
+ if (remainingTime >= 0.0)
+ {
+ [self performSelector:@selector(dispatchSoundCompletedEvent) withObject:nil
+ afterDelay:remainingTime];
+ }
+ }
+}
+
+- (void)revokeSoundCompletedEvent
+{
+ [NSObject cancelPreviousPerformRequestsWithTarget:self
+ selector:@selector(dispatchSoundCompletedEvent) object:nil];
+}
+
+- (void)dispatchSoundCompletedEvent
+{
+ if (!mLoop)
+ {
+ SPEvent *event = [[SPEvent alloc] initWithType:SP_EVENT_TYPE_SOUND_COMPLETED];
+ [self dispatchEvent:event];
+ [event release];
+ }
+}
+
+- (void)onInterruptionBegan:(NSNotification *)notification
+{
+ if (self.isPlaying)
+ {
+ [self revokeSoundCompletedEvent];
+ mInterrupted = YES;
+ mPauseMoment = CACurrentMediaTime();
+ }
+}
+
+- (void)onInterruptionEnded:(NSNotification *)notification
+{
+ if (mInterrupted)
+ {
+ mStartMoment += CACurrentMediaTime() - mPauseMoment;
+ mPauseMoment = 0.0;
+ mInterrupted = NO;
+ [self scheduleSoundCompletedEvent];
+ }
+}
+
+@end
--- /dev/null
+//
+// SPAVSound.h
+// Sparrow
+//
+// Created by Daniel Sperl on 29.05.10.
+// Copyright 2010 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+#import <AVFoundation/AVFoundation.h>
+
+#import "SPSound.h"
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPAVSound class is a concrete implementation of SPSound that uses AVAudioPlayer internally.
+
+ Don't create instances of this class manually. Use [SPSound initWithContentsOfFile:] instead.
+
+ */
+
+@interface SPAVSound : SPSound
+{
+ @private
+ NSData *mSoundData;
+ double mDuration;
+}
+
+/// --------------------
+/// @name Initialization
+/// --------------------
+
+/// Initializes a sound with the contents of a file and the known duration.
+- (id)initWithContentsOfFile:(NSString *)path duration:(double)duration;
+
+/// -------------
+/// @name methods
+/// -------------
+
+/// Creates an AVAudioPlayer object from the sound.
+- (AVAudioPlayer *)createPlayer;
+
+@end
--- /dev/null
+//
+// SPAVSound.m
+// Sparrow
+//
+// Created by Daniel Sperl on 29.05.10.
+// Copyright 2010 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import "SPAVSound.h"
+#import "SPAVSoundChannel.h"
+
+@implementation SPAVSound
+
+@synthesize duration = mDuration;
+
+- (id)init
+{
+ [self release];
+ return nil;
+}
+
+- (id)initWithContentsOfFile:(NSString *)path duration:(double)duration
+{
+ if (self = [super init])
+ {
+ NSString *fullPath = [path isAbsolutePath] ?
+ path : [[NSBundle mainBundle] pathForResource:path ofType:nil];
+ mSoundData = [[NSData alloc] initWithContentsOfMappedFile:fullPath];
+ mDuration = duration;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [mSoundData release];
+ [super dealloc];
+}
+
+- (SPSoundChannel *)createChannel
+{
+ return [[[SPAVSoundChannel alloc] initWithSound:self] autorelease];
+}
+
+- (AVAudioPlayer *)createPlayer
+{
+ NSError *error = nil;
+ AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithData:mSoundData error:&error];
+ if (error) NSLog(@"Could not create AVAudioPlayer: %@", [error description]);
+ return [player autorelease];
+}
+
+@end
--- /dev/null
+//
+// SPAVSoundChannel.h
+// Sparrow
+//
+// Created by Daniel Sperl on 29.05.10.
+// Copyright 2010 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+#import <AVFoundation/AVFoundation.h>
+
+#import "SPSoundChannel.h"
+#import "SPAVSound.h"
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPAVSoundChannel class is a concrete implementation of SPSoundChannel that uses AVAudioPlayer
+ internally.
+
+ Don't create instances of this class manually. Use [SPSound createChannel] instead.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPAVSoundChannel : SPSoundChannel <AVAudioPlayerDelegate>
+{
+ @private
+ SPAVSound *mSound;
+ AVAudioPlayer *mPlayer;
+ BOOL mPaused;
+ float mVolume;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a sound channel from an SPAVSound object.
+- (id)initWithSound:(SPAVSound *)sound;
+
+@end
--- /dev/null
+//
+// SPAVSoundChannel.m
+// Sparrow
+//
+// Created by Daniel Sperl on 29.05.10.
+// Copyright 2010 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import "SPAVSoundChannel.h"
+#import "SPAudioEngine.h"
+#import "SPMacros.h"
+
+@implementation SPAVSoundChannel
+
+- (id)init
+{
+ [self release];
+ return nil;
+}
+
+- (id)initWithSound:(SPAVSound *)sound
+{
+ if (self = [super init])
+ {
+ mVolume = 1.0f;
+ mSound = [sound retain];
+ mPlayer = [[sound createPlayer] retain];
+ mPlayer.delegate = self;
+ [mPlayer prepareToPlay];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(onMasterVolumeChanged:)
+ name:SP_NOTIFICATION_MASTER_VOLUME_CHANGED object:nil];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [mPlayer release];
+ [mSound release];
+ [super dealloc];
+}
+
+- (void)play
+{
+ mPaused = NO;
+ [mPlayer play];
+}
+
+- (void)pause
+{
+ mPaused = YES;
+ [mPlayer pause];
+}
+
+- (void)stop
+{
+ mPaused = NO;
+ [mPlayer stop];
+ mPlayer.currentTime = 0;
+}
+
+- (BOOL)isPlaying
+{
+ return mPlayer.playing;
+}
+
+- (BOOL)isPaused
+{
+ return mPaused && !mPlayer.playing;
+}
+
+- (BOOL)isStopped
+{
+ return !mPaused && !mPlayer.playing;
+}
+
+- (BOOL)loop
+{
+ return mPlayer.numberOfLoops < 0;
+}
+
+- (void)setLoop:(BOOL)value
+{
+ mPlayer.numberOfLoops = value ? -1 : 0;
+}
+
+- (float)volume
+{
+ return mVolume;
+}
+
+- (void)setVolume:(float)value
+{
+ mVolume = value;
+ mPlayer.volume = value * [SPAudioEngine masterVolume];
+}
+
+- (double)duration
+{
+ return mPlayer.duration;
+}
+
+- (void)onMasterVolumeChanged:(NSNotification *)notification
+{
+ self.volume = mVolume;
+}
+
+#pragma mark AVAudioPlayerDelegate
+
+- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
+{
+ [self dispatchEvent:[SPEvent eventWithType:SP_EVENT_TYPE_SOUND_COMPLETED]];
+}
+
+- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error
+{
+ NSLog(@"Error during sound decoding: %@", [error description]);
+}
+
+- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player
+{
+ [player pause];
+}
+
+- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player
+{
+ [player play];
+}
+
+@end
--- /dev/null
+//
+// SPAnimatable.h
+// Sparrow
+//
+// Created by Daniel Sperl on 09.05.09.
+// Copyright 2009 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPAnimatable protocol describes objects that are animated depending on the passed time.
+ Any object that implements this protocol can be added to the SPJuggler.
+
+------------------------------------------------------------------------------------------------- */
+
+@protocol SPAnimatable
+
+/// Advance the animation by a number of seconds.
+- (void)advanceTime:(double)seconds;
+
+/// Indicates if the animation is finished. (The juggler will purge the object.)
+@property (nonatomic, readonly) BOOL isComplete;
+
+@end
--- /dev/null
+//
+// SPAudioEngine.h
+// Sparrow
+//
+// Created by Daniel Sperl on 14.11.09.
+// Copyright 2009 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+
+#define SP_NOTIFICATION_MASTER_VOLUME_CHANGED @"masterVolumeChanged"
+#define SP_NOTIFICATION_AUDIO_INTERRUPTION_BEGAN @"audioInterruptionBegan"
+#define SP_NOTIFICATION_AUDIO_INTERRUPTION_ENDED @"audioInterruptionEnded"
+
+typedef enum {
+ SPAudioSessionCategory_AmbientSound = 'ambi',
+ SPAudioSessionCategory_SoloAmbientSound = 'solo',
+ SPAudioSessionCategory_MediaPlayback = 'medi',
+ SPAudioSessionCategory_RecordAudio = 'reca',
+ SPAudioSessionCategory_PlayAndRecord = 'plar',
+ SPAudioSessionCategory_AudioProcessing = 'proc'
+} SPAudioSessionCategory;
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPAudioEngine prepares the system for audio playback and controls global volume.
+
+ Before you play sounds, you should start an audio session. The type of the audio session
+ defines how iOS will handle audio processing and how iPod music will mix with your audio.
+
+ * `SPAudioSessionCategory_AmbientSound:` iPod music mixes with your audio, audio silences on mute
+ * `SPAudioSessionCategory_SoloAmbientSound:` iPod music is silenced, audio silences on mute
+ * `SPAudioSessionCategory_MediaPlayback:` iPod music is silenced, audio continues on mute
+ * `SPAudioSessionCategory_RecordAudio:` iPod music is silenced, used for audio recording
+ * `SPAudioSessionCategory_PlayAndRecord:` iPod music is silenced, for simultaneous in- and output
+ * `SPAudioSessionCategory_AudioProcessing:` For using an audio hardware codec or signal processor
+
+ */
+
+@interface SPAudioEngine : NSObject
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Starts an audio session with a specified category. Call this at the start of your application.
++ (void)start:(SPAudioSessionCategory)category;
+
+/// Starts an audio session with with the category 'SoloAmbientSound'.
++ (void)start;
+
+/// Stops the audio session. Call this before the application shuts down.
++ (void)stop;
+
+/// The master volume for all audio. Default: 1.0
++ (float)masterVolume;
+
+/// Set the master volume for all audio. Range: [0.0 - 1.0]
++ (void)setMasterVolume:(float)volume;
+
+@end
--- /dev/null
+//
+// SPAudioEngine.m
+// Sparrow
+//
+// Created by Daniel Sperl on 14.11.09.
+// Copyright 2009 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import "SPAudioEngine.h"
+
+#import <AudioToolbox/AudioToolbox.h>
+#import <OpenAL/al.h>
+#import <OpenAL/alc.h>
+
+@interface SPAudioEngine ()
+
++ (BOOL)initAudioSession:(SPAudioSessionCategory)category;
++ (BOOL)initOpenAL;
+
++ (void)beginInterruption;
++ (void)endInterruption;
+
++ (void)postNotification:(NSString *)name object:(id)object;
+
+@end
+
+@implementation SPAudioEngine
+
+// --- C functions ---
+
+void interruptionCallback (void *inUserData, UInt32 interruptionState)
+{
+ if (interruptionState == kAudioSessionBeginInterruption)
+ [SPAudioEngine beginInterruption];
+ else if (interruptionState == kAudioSessionEndInterruption)
+ [SPAudioEngine endInterruption];
+}
+
+// --- static members ---
+
+static ALCdevice *device = NULL;
+static ALCcontext *context = NULL;
+static float masterVolume = 1.0f;
+
+// ---
+
+- (id)init
+{
+ [self release];
+ [NSException raise:NSGenericException format:@"Static class - do not initialize!"];
+ return nil;
+}
+
++ (void)start:(SPAudioSessionCategory)category
+{
+ if (!device)
+ {
+ if ([SPAudioEngine initAudioSession:category])
+ [SPAudioEngine initOpenAL];
+ }
+}
+
++ (void)start
+{
+ [SPAudioEngine start:SPAudioSessionCategory_SoloAmbientSound];
+}
+
++ (void)stop
+{
+ alcMakeContextCurrent(NULL);
+ alcDestroyContext(context);
+ alcCloseDevice(device);
+
+ device = NULL;
+ context = NULL;
+
+ AudioSessionSetActive(NO);
+}
+
++ (BOOL)initAudioSession:(SPAudioSessionCategory)category
+{
+ static BOOL sessionInitialized = NO;
+ OSStatus result;
+
+ if (!sessionInitialized)
+ {
+ result = AudioSessionInitialize(NULL, NULL, interruptionCallback, NULL);
+ if (result != kAudioSessionNoError)
+ {
+ NSLog(@"Could not initialize audio session: %x", result);
+ return NO;
+ }
+ sessionInitialized = YES;
+ }
+
+ UInt32 sessionCategory = category;
+ AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
+ sizeof(sessionCategory), &sessionCategory);
+
+ result = AudioSessionSetActive(YES);
+ if (result != kAudioSessionNoError)
+ {
+ NSLog(@"Could not activate audio session: %x", result);
+ return NO;
+ }
+
+ return YES;
+}
+
++ (BOOL)initOpenAL
+{
+ alGetError(); // reset any errors
+
+ device = alcOpenDevice(NULL);
+ if (!device)
+ {
+ NSLog(@"Could not open default OpenAL device");
+ return NO;
+ }
+
+ context = alcCreateContext(device, 0);
+ if (!context)
+ {
+ NSLog(@"Could not create OpenAL context for default device");
+ return NO;
+ }
+
+ BOOL success = alcMakeContextCurrent(context);
+ if (!success)
+ {
+ NSLog(@"Could not set current OpenAL context");
+ return NO;
+ }
+
+ return YES;
+}
+
++ (void)beginInterruption
+{
+ [SPAudioEngine postNotification:SP_NOTIFICATION_AUDIO_INTERRUPTION_BEGAN object:nil];
+ alcMakeContextCurrent(NULL);
+ AudioSessionSetActive(NO);
+}
+
++ (void)endInterruption
+{
+ AudioSessionSetActive(YES);
+ alcMakeContextCurrent(context);
+ alcProcessContext(context);
+ [SPAudioEngine postNotification:SP_NOTIFICATION_AUDIO_INTERRUPTION_ENDED object:nil];
+}
+
++ (float)masterVolume
+{
+ return masterVolume;
+}
+
++ (void)setMasterVolume:(float)volume
+{
+ masterVolume = volume;
+ alListenerf(AL_GAIN, volume);
+ [SPAudioEngine postNotification:SP_NOTIFICATION_MASTER_VOLUME_CHANGED object:nil];
+}
+
++ (void)postNotification:(NSString *)name object:(id)object
+{
+ [[NSNotificationCenter defaultCenter] postNotification:
+ [NSNotification notificationWithName:name object:object]];
+}
+
+@end
--- /dev/null
+//
+// SPBitmapChar.h
+// Sparrow
+//
+// Created by Daniel Sperl on 12.10.09.
+// Copyright 2009 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+#import "SPImage.h"
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPBitmapChar is an image that contains one char of a bitmap font. Its properties contain all
+ the information that is needed to arrange the char in a text.
+
+ _You don't have to use this class directly in most cases._
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPBitmapChar : SPImage <NSCopying>
+{
+ @private
+ int mCharID;
+ float mXOffset;
+ float mYOffset;
+ float mXAdvance;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a char with a texture and his properties.
+- (id)initWithID:(int)charID texture:(SPTexture *)texture
+ xOffset:(float)xOffset yOffset:(float)yOffset xAdvance:(float)xAdvance;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The unicode ID of the char.
+@property (nonatomic, readonly) int charID;
+
+/// The number of pixels to move the char in x direction on character arrangement.
+@property (nonatomic, readonly) float xOffset;
+
+/// The number of pixels to move the char in y direction on character arrangement.
+@property (nonatomic, readonly) float yOffset;
+
+/// The number of pixels the cursor has to be moved to the right for the next char.
+@property (nonatomic, readonly) float xAdvance;
+
+@end
--- /dev/null
+//
+// SPBitmapChar.m
+// Sparrow
+//
+// Created by Daniel Sperl on 12.10.09.
+// Copyright 2009 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import "SPBitmapChar.h"
+#import "SPTexture.h"
+
+@implementation SPBitmapChar
+
+@synthesize charID = mCharID;
+@synthesize xOffset = mXOffset;
+@synthesize yOffset = mYOffset;
+@synthesize xAdvance = mXAdvance;
+
+- (id)initWithID:(int)charID texture:(SPTexture *)texture
+ xOffset:(float)xOffset yOffset:(float)yOffset xAdvance:(float)xAdvance;
+{
+ if (self = [super initWithTexture:texture])
+ {
+ mCharID = charID;
+ mXOffset = xOffset;
+ mYOffset = yOffset;
+ mXAdvance = xAdvance;
+ }
+ return self;
+}
+
+- (id)initWithTexture:(SPTexture *)texture
+{
+ return [self initWithID:0 texture:texture xOffset:0 yOffset:0 xAdvance:texture.width];
+}
+
+- (id)init
+{
+ [self release];
+ return nil;
+}
+
+#pragma mark NSCopying
+
+- (id)copyWithZone:(NSZone*)zone;
+{
+ return [[[self class] allocWithZone:zone] initWithID:mCharID texture:self.texture
+ xOffset:mXOffset yOffset:mYOffset
+ xAdvance:mXAdvance];
+}
+
+@end
--- /dev/null
+//
+// SPBitmapFont.h
+// Sparrow
+//
+// Created by Daniel Sperl on 12.10.09.
+// Copyright 2009 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+#import "SPBitmapChar.h"
+#import "SPTextField.h"
+#import "SPMacros.h"
+
+@class SPTexture;
+@class SPDisplayObject;
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPBitmapFont class parses bitmap font files and arranges the glyphs in the form of a text.
+
+ The class parses the XML format as it is used in the AngelCode Bitmap Font Generator. This is what
+ the file format looks like:
+
+ <font>
+ <info face="BranchingMouse" size="40" />
+ <common lineHeight="40" />
+ <pages> <!-- currently, only one page is supported -->
+ <page id="0" file="texture.png" />
+ </pages>
+ <chars>
+ <char id="32" x="60" y="29" width="1" height="1" xoffset="0" yoffset="27" xadvance="8" />
+ <char id="33" x="155" y="144" width="9" height="21" xoffset="0" yoffset="6" xadvance="9" />
+ </chars>
+ </font>
+
+ _You don't have to use this class directly in most cases. SPTextField contains methods that
+ handle bitmap fonts for you._
+
+------------------------------------------------------------------------------------------------- */
+
+#ifdef __IPHONE_4_0
+@interface SPBitmapFont : NSObject <NSXMLParserDelegate>
+#else
+@interface SPBitmapFont : NSObject
+#endif
+{
+ @private
+ SPTexture *mFontTexture;
+ NSMutableDictionary *mChars;
+ NSString *mName;
+ float mSize;
+ float mLineHeight;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a bitmap font by parsing an XML file and uses the specified texture.
+- (id)initWithContentsOfFile:(NSString *)path texture:(SPTexture *)texture;
+
+/// Initializes a bitmap font by parsing an XML file and loads the texture that is specified there.
+- (id)initWithContentsOfFile:(NSString *)path;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Creates a single bitmap char with a certain character ID.
+- (SPBitmapChar *)charByID:(int)charID;
+
+/// Creates a display object that contains the given text by arranging individual chars.
+- (SPDisplayObject *)createDisplayObjectWithWidth:(float)width height:(float)height
+ text:(NSString *)text fontSize:(float)size color:(uint)color
+ hAlign:(SPHAlign)hAlign vAlign:(SPVAlign)vAlign
+ border:(BOOL)border;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The name of the font as it was parsed from the font file.
+@property (nonatomic, readonly) NSString *name;
+
+/// The native size of the font.
+@property (nonatomic, readonly) float size;
+
+/// The height of one line in pixels.
+@property (nonatomic, assign) float lineHeight;
+
+@end
--- /dev/null
+//
+// SPBitmapFont.m
+// Sparrow
+//
+// Created by Daniel Sperl on 12.10.09.
+// Copyright 2009 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import "SPBitmapFont.h"
+#import "SPBitmapChar.h"
+#import "SPTexture.h"
+#import "SPRectangle.h"
+#import "SPSubTexture.h"
+#import "SPDisplayObject.h"
+#import "SPSprite.h"
+#import "SPImage.h"
+#import "SPTextField.h"
+#import "SPStage.h"
+#import "SPNSExtensions.h"
+#import "SPCompiledSprite.h"
+
+#define CHAR_SPACE 32
+#define CHAR_TAB 9
+#define CHAR_NEWLINE 10
+
+// --- private interface ---------------------------------------------------------------------------
+
+@interface SPBitmapFont ()
+
+- (void)parseFontXml:(NSString*)path;
+
+@end
+
+// --- class implementation ------------------------------------------------------------------------
+
+@implementation SPBitmapFont
+
+@synthesize name = mName;
+@synthesize lineHeight = mLineHeight;
+@synthesize size = mSize;
+
+- (id)initWithContentsOfFile:(NSString *)path texture:(SPTexture *)texture
+{
+ if (self = [super init])
+ {
+ mName = [[NSString alloc] initWithString:@"unknown"];
+ mLineHeight = mSize = SP_DEFAULT_FONT_SIZE;
+ mFontTexture = [texture retain];
+
+ [self parseFontXml:path];
+ }
+ return self;
+}
+
+- (id)initWithContentsOfFile:(NSString *)path
+{
+ return [self initWithContentsOfFile:path texture:nil];
+}
+
+- (id)init
+{
+ [self release];
+ return nil;
+}
+
+- (void)parseFontXml:(NSString*)path
+{
+ SP_CREATE_POOL(pool);
+
+ [mChars release];
+ mChars = [[NSMutableDictionary alloc] init];
+
+ if (!path) return;
+
+ float scale = [SPStage contentScaleFactor];
+ NSString *fullPath = [[NSBundle mainBundle] pathForResource:path withScaleFactor:scale];
+ NSURL *xmlUrl = [NSURL fileURLWithPath:fullPath];
+ NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlUrl];
+ xmlParser.delegate = self;
+ BOOL success = [xmlParser parse];
+
+ SP_RELEASE_POOL(pool);
+
+ if (!success)
+ [NSException raise:SP_EXC_FILE_INVALID
+ format:@"could not parse bitmap font xml %@. Error code: %d, domain: %@",
+ path, xmlParser.parserError.code, xmlParser.parserError.domain];
+
+ [xmlParser release];
+}
+
+- (void)parser:(NSXMLParser*)parser didStartElement:(NSString*)elementName
+ namespaceURI:(NSString*)namespaceURI
+ qualifiedName:(NSString*)qName
+ attributes:(NSDictionary*)attributeDict
+{
+ if ([elementName isEqualToString:@"char"])
+ {
+ int charID = [[attributeDict valueForKey:@"id"] intValue];
+ float scale = mFontTexture.scale;
+
+ SPRectangle *region = [[SPRectangle alloc] init];
+ region.x = [[attributeDict valueForKey:@"x"] floatValue] / scale;
+ region.y = [[attributeDict valueForKey:@"y"] floatValue] / scale;
+ region.width = [[attributeDict valueForKey:@"width"] floatValue] / scale;
+ region.height = [[attributeDict valueForKey:@"height"] floatValue] / scale;
+ SPSubTexture *texture = [[SPSubTexture alloc] initWithRegion:region ofTexture:mFontTexture];
+ [region release];
+
+ float xOffset = [[attributeDict valueForKey:@"xoffset"] floatValue] / scale;
+ float yOffset = [[attributeDict valueForKey:@"yoffset"] floatValue] / scale;
+ float xAdvance = [[attributeDict valueForKey:@"xadvance"] floatValue] / scale;
+
+ SPBitmapChar *bitmapChar = [[SPBitmapChar alloc] initWithID:charID texture:texture
+ xOffset:xOffset yOffset:yOffset
+ xAdvance:xAdvance];
+ [texture release];
+
+ [mChars setObject:bitmapChar forKey:[NSNumber numberWithInt:charID]];
+ [bitmapChar release];
+ }
+ else if ([elementName isEqualToString:@"info"])
+ {
+ [mName release];
+ mName = [[attributeDict valueForKey:@"face"] copy];
+ mSize = [[attributeDict valueForKey:@"size"] floatValue];
+ }
+ else if ([elementName isEqualToString:@"common"])
+ {
+ mLineHeight = [[attributeDict valueForKey:@"lineHeight"] floatValue];
+ }
+ else if ([elementName isEqualToString:@"page"])
+ {
+ int id = [[attributeDict valueForKey:@"id"] intValue];
+ if (id != 0) [NSException raise:SP_EXC_FILE_INVALID
+ format:@"Bitmap fonts with multiple pages are not supported"];
+ if (!mFontTexture)
+ {
+ NSString *filename = [attributeDict valueForKey:@"file"];
+ mFontTexture = [[SPTexture alloc] initWithContentsOfFile:filename];
+
+ // update sizes, now that we know the scale setting
+ mSize /= mFontTexture.scale;
+ mLineHeight /= mFontTexture.scale;
+ }
+ }
+}
+
+- (SPBitmapChar *)charByID:(int)charID
+{
+ SPBitmapChar *bitmapChar = (SPBitmapChar *)[mChars objectForKey:[NSNumber numberWithInt:charID]];
+ return [[bitmapChar copy] autorelease];
+}
+
+- (SPDisplayObject *)createDisplayObjectWithWidth:(float)width height:(float)height
+ text:(NSString *)text fontSize:(float)size color:(uint)color
+ hAlign:(SPHAlign)hAlign vAlign:(SPVAlign)vAlign
+ border:(BOOL)border
+{
+ SPSprite *lineContainer = [SPSprite sprite];
+
+ if (size == SP_NATIVE_FONT_SIZE) size = mSize;
+ float scale = size / mSize;
+ lineContainer.scaleX = lineContainer.scaleY = scale;
+ float containerWidth = width / scale;
+ float containerHeight = height / scale;
+
+ int lastWhiteSpace = -1;
+ float currentX = 0;
+ SPSprite *currentLine = [SPSprite sprite];
+
+ for (int i=0; i<text.length; i++)
+ {
+ BOOL lineFull = NO;
+
+ int charID = [text characterAtIndex:i];
+ if (charID == CHAR_NEWLINE)
+ {
+ lineFull = YES;
+ }
+ else
+ {
+ if (charID == CHAR_SPACE || charID == CHAR_TAB)
+ lastWhiteSpace = i;
+
+ SPBitmapChar *bitmapChar = [self charByID:charID];
+ if (!bitmapChar) bitmapChar = [self charByID:CHAR_SPACE];
+
+ bitmapChar.x = currentX + bitmapChar.xOffset;
+ bitmapChar.y = bitmapChar.yOffset;
+ bitmapChar.color = color;
+ [currentLine addChild:bitmapChar];
+
+ currentX += bitmapChar.xAdvance;
+
+ if (currentX > containerWidth)
+ {
+ // remove characters and add them again to next line
+ int numCharsToRemove = lastWhiteSpace == -1 ? 1 : i - lastWhiteSpace;
+ int removeIndex = currentLine.numChildren - numCharsToRemove;
+
+ for (int i=0; i<numCharsToRemove; ++i)
+ [currentLine removeChildAtIndex:removeIndex];
+
+ if (currentLine.numChildren == 0)
+ break;
+
+ SPDisplayObject *lastChar = [currentLine childAtIndex:currentLine.numChildren-1];
+ currentX = lastChar.x + lastChar.width;
+
+ i -= numCharsToRemove;
+ lineFull = YES;
+ }
+ }
+
+ if (lineFull || i == text.length - 1)
+ {
+ float nextLineY = currentLine.y + mLineHeight;
+ [lineContainer addChild:currentLine];
+
+ if (nextLineY + mLineHeight <= containerHeight)
+ {
+ currentLine = [SPSprite sprite];
+ currentLine.y = nextLineY;
+ lastWhiteSpace = -1;
+ currentX = 0;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ // hAlign
+ if (hAlign != SPHAlignLeft)
+ {
+ for (SPSprite *line in lineContainer)
+ {
+ SPDisplayObject *lastChar = [line childAtIndex:line.numChildren-1];
+ float lineWidth = lastChar.x + lastChar.width;
+ float widthDiff = containerWidth - lineWidth;
+ line.x = (int) (hAlign == SPHAlignRight ? widthDiff : widthDiff / 2);
+ }
+ }
+
+ SPSprite *outerContainer = [SPCompiledSprite sprite];
+ [outerContainer addChild:lineContainer];
+
+ if (vAlign != SPVAlignTop)
+ {
+ float contentHeight = lineContainer.numChildren * mLineHeight * scale;
+ float heightDiff = height - contentHeight;
+ lineContainer.y = (int)(vAlign == SPVAlignBottom ? heightDiff : heightDiff / 2.0f);
+ }
+
+ if (border)
+ {
+ SPQuad *topBorder = [SPQuad quadWithWidth:width height:1];
+ SPQuad *bottomBorder = [SPQuad quadWithWidth:width height:1];
+ SPQuad *leftBorder = [SPQuad quadWithWidth:1 height:height-2];
+ SPQuad *rightBorder = [SPQuad quadWithWidth:1 height:height-2];
+
+ topBorder.color = bottomBorder.color = leftBorder.color = rightBorder.color = color;
+ bottomBorder.y = height - 1;
+ leftBorder.y = rightBorder.y = 1;
+ rightBorder.x = width - 1;
+
+ [outerContainer addChild:topBorder];
+ [outerContainer addChild:bottomBorder];
+ [outerContainer addChild:leftBorder];
+ [outerContainer addChild:rightBorder];
+ }
+
+ return outerContainer;
+}
+
+- (void)dealloc
+{
+ [mFontTexture release];
+ [mChars release];
+ [mName release];
+ [super dealloc];
+}
+
+@end
--- /dev/null
+//
+// SPButton.h
+// Sparrow
+//
+// Created by Daniel Sperl on 13.07.09.
+// Copyright 2009 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+#import "SPDisplayObjectContainer.h"
+
+@class SPTexture;
+@class SPImage;
+@class SPTextField;
+@class SPSprite;
+
+#define SP_EVENT_TYPE_TRIGGERED @"triggered"
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPButton is a simple button composed of an image and, optionally, text.
+
+ You can pass a texture for up- and downstate of the button. If you do not provide a down stage,
+ the button is simply scaled a little when it is touched.
+
+ In addition, you can overlay a text on the button. To customize the text, almost the same options
+ as those of SPTextField are provided. In addition, you can move the text to a certain position
+ with the help of the `textBounds` property.
+
+ To react on touches on a button, there is special event type: `SP_EVENT_TYPE_TRIGGERED`. Use
+ this event instead of normal touch events - that way, the button will behave just like standard
+ iOS interface buttons.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPButton : SPDisplayObjectContainer
+{
+ @private
+ SPTexture *mUpState;
+ SPTexture *mDownState;
+
+ SPSprite *mContents;
+ SPImage *mBackground;
+ SPTextField *mTextField;
+ SPRectangle *mTextBounds;
+
+ float mScaleWhenDown;
+ float mAlphaWhenDisabled;
+ BOOL mEnabled;
+ BOOL mIsDown;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a button with textures for up- and down-state. _Designated Initializer_.
+- (id)initWithUpState:(SPTexture*)upState downState:(SPTexture*)downState;
+
+/// Initializes a button with an up state texture and text.
+- (id)initWithUpState:(SPTexture*)upState text:(NSString*)text;
+
+/// Initializes a button only with an up state.
+- (id)initWithUpState:(SPTexture*)upState;
+
+/// Factory method.
++ (SPButton*)buttonWithUpState:(SPTexture*)upState downState:(SPTexture*)downState;
+
+/// Factory method.
++ (SPButton*)buttonWithUpState:(SPTexture*)upState text:(NSString*)text;
+
+/// Factory method.
++ (SPButton*)buttonWithUpState:(SPTexture*)upState;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The scale factor of the button on touch. Per default, a button with a down state texture won't scale.
+@property (nonatomic, assign) float scaleWhenDown;
+
+/// The alpha value of the button when it is disabled.
+@property (nonatomic, assign) float alphaWhenDisabled;
+
+/// Indicates if the button can be triggered.
+@property (nonatomic, assign) BOOL enabled;
+
+/// The text that is displayed on the button.
+@property (nonatomic, copy) NSString *text;
+
+/// The name of the font displayed on the button. May be a system font or a registered bitmap font.
+@property (nonatomic, copy) NSString *fontName;
+
+/// The size of the font.
+@property (nonatomic, assign) float fontSize;
+
+/// The color of the font.
+@property (nonatomic, assign) uint fontColor;
+
+/// The texture that is displayed when the button is not being touched.
+@property (nonatomic, retain) SPTexture *upState;
+
+/// The texture that is displayed while the button is touched.
+@property (nonatomic, retain) SPTexture *downState;
+
+/// The bounds of the textfield on the button. Allows moving the text to a custom position.
+@property (nonatomic, copy) SPRectangle *textBounds;
+
+@end
--- /dev/null
+//
+// SPButton.m
+// Sparrow
+//
+// Created by Daniel Sperl on 13.07.09.
+// Copyright 2009 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import "SPButton.h"
+#import "SPTouchEvent.h"
+#import "SPTexture.h"
+#import "SPGLTexture.h"
+#import "SPImage.h"
+#import "SPStage.h"
+#import "SPSprite.h"
+#import "SPTextField.h"
+
+// --- private interface ---------------------------------------------------------------------------
+
+@interface SPButton()
+
+- (void)resetContents;
+- (void)createTextField;
+
+@end
+
+
+// --- class implementation ------------------------------------------------------------------------
+
+@implementation SPButton
+
+@synthesize scaleWhenDown = mScaleWhenDown;
+@synthesize alphaWhenDisabled = mAlphaWhenDisabled;
+@synthesize enabled = mEnabled;
+@synthesize upState = mUpState;
+@synthesize downState = mDownState;
+@synthesize textBounds = mTextBounds;
+
+#define MAX_DRAG_DIST 40
+
+- (id)initWithUpState:(SPTexture*)upState downState:(SPTexture*)downState;
+{
+ if (self = [super init])
+ {
+ mUpState = [upState retain];
+ mDownState = [downState retain];
+ mContents = [[SPSprite alloc] init];
+ mBackground = [[SPImage alloc] initWithTexture:upState];
+ mTextField = nil;
+ mScaleWhenDown = 1.0f;
+ mAlphaWhenDisabled = 0.5f;
+ mEnabled = YES;
+ mIsDown = NO;
+ mTextBounds = [[SPRectangle alloc] initWithX:0 y:0 width:mUpState.width height:mUpState.height];
+
+ [mContents addChild:mBackground];
+ [self addChild:mContents];
+ [self addEventListener:@selector(onTouch:) atObject:self forType:SP_EVENT_TYPE_TOUCH];
+ }
+ return self;
+}
+
+- (id)initWithUpState:(SPTexture*)upState text:(NSString*)text
+{
+ self = [self initWithUpState:upState];
+ self.text = text;
+ return self;
+}
+
+- (id)initWithUpState:(SPTexture*)upState;
+{
+ self = [self initWithUpState:upState downState:upState];
+ mScaleWhenDown = 0.9f;
+ return self;
+}
+
+- (id)init
+{
+ SPTexture *texture = [[[SPGLTexture alloc] init] autorelease];
+ return [self initWithUpState:texture];
+}
+
+- (void)onTouch:(SPTouchEvent*)touchEvent
+{
+ if (!mEnabled) return;
+ SPTouch *touch = [[touchEvent touchesWithTarget:self] anyObject];
+
+ if (touch.phase == SPTouchPhaseBegan)
+ {
+ mBackground.texture = mDownState;
+ mContents.scaleX = mContents.scaleY = mScaleWhenDown;
+ mContents.x = (1.0f - mScaleWhenDown) / 2.0f * mDownState.width;
+ mContents.y = (1.0f - mScaleWhenDown) / 2.0f * mDownState.height;
+ mIsDown = YES;
+ }
+ else if (touch.phase == SPTouchPhaseMoved && mIsDown)
+ {
+ // reset button when user dragged to far away after pushing
+ SPRectangle *buttonRect = [self boundsInSpace:self.stage];
+ if (touch.globalX < buttonRect.x - MAX_DRAG_DIST ||
+ touch.globalY < buttonRect.y - MAX_DRAG_DIST ||
+ touch.globalX > buttonRect.x + buttonRect.width + MAX_DRAG_DIST ||
+ touch.globalY > buttonRect.y + buttonRect.height + MAX_DRAG_DIST)
+ {
+ [self resetContents];
+ }
+ }
+ else if (touch.phase == SPTouchPhaseEnded && mIsDown)
+ {
+ [self resetContents];
+ [self dispatchEvent:[SPEvent eventWithType:SP_EVENT_TYPE_TRIGGERED]];
+ }
+ else if (touch.phase == SPTouchPhaseCancelled && mIsDown)
+ {
+ [self resetContents];
+ }
+}
+
+- (void)resetContents
+{
+ mIsDown = NO;
+ mBackground.texture = mUpState;
+ mContents.x = mContents.y = 0;
+ mContents.scaleX = mContents.scaleY = 1.0f;
+}
+
+- (void)setEnabled:(BOOL)value
+{
+ mEnabled = value;
+ if (mEnabled)
+ {
+ mContents.alpha = 1.0f;
+ }
+ else
+ {
+ mContents.alpha = mAlphaWhenDisabled;
+ [self resetContents];
+ }
+}
+
+- (void)setUpState:(SPTexture*)upState
+{
+ if (upState != mUpState)
+ {
+ [mUpState release];
+ mUpState = [upState retain];
+ if (!mIsDown) mBackground.texture = upState;
+ }
+}
+
+- (void)setDownState:(SPTexture*)downState
+{
+ if (downState != mDownState)
+ {
+ [mDownState release];
+ mDownState = [downState retain];
+ if (mIsDown) mBackground.texture = downState;
+ }
+}
+
+- (void)createTextField
+{
+ if (!mTextField)
+ {
+ mTextField = [[SPTextField alloc] initWithWidth:100 height:100 text:@""];
+ mTextField.vAlign = SPVAlignCenter;
+ mTextField.hAlign = SPHAlignCenter;
+ [mContents addChild:mTextField];
+ }
+
+ mTextField.width = mTextBounds.width;
+ mTextField.height = mTextBounds.height;
+ mTextField.x = mTextBounds.x;
+ mTextField.y = mTextBounds.y;
+}
+
+- (NSString*)text
+{
+ if (mTextField) return mTextField.text;
+ else return @"";
+}
+
+- (void)setText:(NSString*)value
+{
+ [self createTextField];
+ mTextField.text = value;
+}
+
+- (void)setTextBounds:(SPRectangle *)value
+{
+ mTextBounds = [value copy];
+ [self createTextField];
+}
+
+- (NSString*)fontName
+{
+ if (mTextField) return mTextField.fontName;
+ else return SP_DEFAULT_FONT_NAME;
+}
+
+- (void)setFontName:(NSString*)value
+{
+ [self createTextField];
+ mTextField.fontName = value;
+}
+
+- (float)fontSize
+{
+ if (mTextField) return mTextField.fontSize;
+ else return SP_DEFAULT_FONT_SIZE;
+}
+
+- (void)setFontSize:(float)value
+{
+ [self createTextField];
+ mTextField.fontSize = value;
+}
+
+- (uint)fontColor
+{
+ if (mTextField) return mTextField.color;
+ else return SP_DEFAULT_FONT_COLOR;
+}
+
+- (void)setFontColor:(uint)value
+{
+ [self createTextField];
+ mTextField.color = value;
+}
+
++ (SPButton*)buttonWithUpState:(SPTexture*)upState downState:(SPTexture*)downState
+{
+ return [[[SPButton alloc] initWithUpState:upState downState:downState] autorelease];
+}
+
++ (SPButton*)buttonWithUpState:(SPTexture*)upState text:(NSString*)text
+{
+ return [[[SPButton alloc] initWithUpState:upState text:text] autorelease];
+}
+
++ (SPButton*)buttonWithUpState:(SPTexture*)upState
+{
+ return [[[SPButton alloc] initWithUpState:upState] autorelease];
+}
+
+- (void)dealloc
+{
+ [self removeEventListenersAtObject:self forType:SP_EVENT_TYPE_TOUCH];
+ [mTextBounds release];
+ [mUpState release];
+ [mDownState release];
+ [mBackground release];
+ [mTextField release];
+ [mContents release];
+ [super dealloc];
+}
+
+@end
--- /dev/null
+//
+// SPCompiledContainer.h
+// Sparrow
+//
+// Created by Daniel Sperl on 15.07.10.
+// Copyright 2010 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+#import "SPSprite.h"
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPCompiledSprite allows you to optimize the rendering of static parts of your display list.
+
+ It analyzes the tree of children attached to it and optimizes the OpenGL rendering calls in a
+ way that makes rendering them extremely fast. The downside is that you will no longe see any
+ changes in the properties of the childs (position, rotation, alpha, etc.). To update the object
+ after changes have happened, simply call `compile` again.
+
+ With the exception of this peculiarity, a compiled sprite can be use just like any other sprite.
+
+ SPCompiledSprite *sprite = [SPCompiledSprite sprite];
+ [sprite addChild:object1];
+ [sprite addChild:object2];
+
+ [sprite compile]; // this call is optional, it will be done on rendering automatically.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPCompiledSprite : SPSprite
+{
+ @private
+ NSArray *mTextureSwitches;
+ NSMutableData *mColorData;
+ uint *mCurrentColors;
+ BOOL mAlphaChanged;
+
+ uint mIndexBuffer;
+ uint mVertexBuffer;
+ uint mColorBuffer;
+ uint mTexCoordBuffer;
+}
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Compiles the children of the sprite to optimize rendering. After compilation, no changes in
+/// the children will show up. Call the method again to make changes visible.
+///
+/// @return Returns `YES` if compilation was successful. On error, it will `NSLog` the problem.
+- (BOOL)compile;
+
+/// Factory method.
++ (SPCompiledSprite *)sprite;
+
+@end
\ No newline at end of file
--- /dev/null
+//
+// SPCompiledContainer.m
+// Sparrow
+//
+// Created by Daniel Sperl on 15.07.10.
+// Copyright 2010 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import "SPCompiledSprite.h"
+#import "SPDisplayObjectContainer.h"
+#import "SPPoint.h"
+#import "SPMatrix.h"
+#import "SPImage.h"
+#import "SPQuad.h"
+#import "SPTexture.h"
+#import "SPRenderSupport.h"
+#import "SPMacros.h"
+
+#import <OpenGLES/EAGL.h>
+#import <OpenGLES/ES1/gl.h>
+#import <OpenGLES/ES1/glext.h>
+
+// --- SPQuad extension ----------------------------------------------------------------------------
+
+@interface SPQuad (SPCompiledSprite_Extension)
+
+- (void)copyVertexCoords:(float *)vertexCoords colors:(uint *)colors textureCoords:(float *)texCoords;
+
+@end
+
+@implementation SPQuad (SPCompiledSprite_Extension)
+
+- (void)copyVertexCoords:(float *)vertexCoords colors:(uint *)colors textureCoords:(float *)texCoords
+{
+ if (vertexCoords) memcpy(vertexCoords, mVertexCoords, 8 * sizeof(float));
+ if (colors) memcpy(colors, mVertexColors, 4 * sizeof(uint));
+}
+
+@end
+
+// --- SPImage extension ---------------------------------------------------------------------------
+
+@implementation SPImage (SPCompiledSprite_Extension)
+
+- (void)copyVertexCoords:(float *)vertexCoords colors:(uint *)colors textureCoords:(float *)texCoords
+{
+ [super copyVertexCoords:vertexCoords colors:colors textureCoords:texCoords];
+ if (texCoords) memcpy(texCoords, mTexCoords, 8 * sizeof(float));
+}
+
+@end
+
+// --- private interface ---------------------------------------------------------------------------
+
+@interface SPCompiledSprite ()
+
+- (BOOL)processVerticesOfObject:(SPDisplayObject *)object withMatrices:(NSMutableArray *)matrices
+ vertexData:(NSMutableData *)vertexData buffer:(void *)buffer;
+- (BOOL)processColorsOfObject:(SPDisplayObject *)object withAlpha:(float)alpha
+ colorData:(NSMutableData *)colorData buffer:(void *)buffer;
+- (BOOL)processTexturesOfObject:(SPDisplayObject *)object withTextures:(NSMutableArray *)textures
+ texCoordData:(NSMutableData *)texCoordData buffer:(void *)buffer;
+- (void)updateColorData;
+- (void)deleteBuffers;
+
+@end
+
+// --- class implementation ------------------------------------------------------------------------
+
+@implementation SPCompiledSprite
+
+- (id)init
+{
+ return [super init];
+}
+
++ (SPCompiledSprite *)sprite
+{
+ return [[[SPCompiledSprite alloc] init] autorelease];
+}
+
+- (void)dealloc
+{
+ free(mCurrentColors);
+ [mTextureSwitches release];
+ [mColorData release];
+ [self deleteBuffers];
+ [super dealloc];
+}
+
+- (BOOL)compile
+{
+ SP_CREATE_POOL(pool);
+
+ [self deleteBuffers];
+ [mTextureSwitches release];
+ [mColorData release];
+
+ free(mCurrentColors);
+ mCurrentColors = nil;
+
+ void *scratchBuffer = malloc(MAX(8 * sizeof(float), 4 * sizeof(uint)));
+
+ NSMutableData *vertexData = [[NSMutableData alloc] init];
+ NSMutableData *colorData = [[NSMutableData alloc] init];
+ NSMutableData *texCoordData = [[NSMutableData alloc] init];
+
+ NSMutableArray *matrices = [[NSMutableArray alloc] initWithObjects:
+ [SPMatrix matrixWithIdentity], nil];
+ NSMutableArray *textures = [[NSMutableArray alloc] initWithObjects:
+ [NSNull null], [NSNumber numberWithInt:0], nil];
+
+ BOOL success;
+
+ do
+ {
+ // compilation is done with an alpha of 1.0f, to get unscaled color data
+ float originalAlpha = self.alpha;
+ self.alpha = 1.0f;
+
+ success = [self processVerticesOfObject:self withMatrices:matrices
+ vertexData:vertexData buffer:scratchBuffer];
+ if (!success) break;
+
+ success = [self processColorsOfObject:self withAlpha:self.alpha
+ colorData:colorData buffer:scratchBuffer];
+ if (!success) break;
+
+ success = [self processTexturesOfObject:self withTextures:textures
+ texCoordData:texCoordData buffer:scratchBuffer];
+
+ self.alpha = originalAlpha;
+
+ } while (NO);
+
+ if (success)
+ {
+ glGenBuffers(4, &mIndexBuffer);
+
+ int numVertices = [vertexData length] / sizeof(float) / 2;
+ int numQuads = numVertices / 4;
+ int indexBufferSize = numQuads * 6; // 4 + 2 for degenerate triangles
+ GLushort *indices = malloc(indexBufferSize * sizeof(GLushort));
+
+ int pos = 0;
+ for (int i=0; i<numQuads; ++i)
+ {
+ indices[pos++] = (GLushort)(i*4);
+ for (int j=0; j<4; ++j) indices[pos++] = (GLushort)(i*4 + j);
+ indices[pos++] = (GLushort)(i*4 + 3);
+ }
+
+ // index buffer
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSize * sizeof(GLushort), indices, GL_STATIC_DRAW);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ free(indices);
+
+ // vertex buffer
+ glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
+ glBufferData(GL_ARRAY_BUFFER, vertexData.length, vertexData.bytes, GL_STATIC_DRAW);
+
+ // color buffer
+ glBindBuffer(GL_ARRAY_BUFFER, mColorBuffer);
+ glBufferData(GL_ARRAY_BUFFER, colorData.length, colorData.bytes, GL_DYNAMIC_DRAW);
+
+ // texture coordinate buffer
+ glBindBuffer(GL_ARRAY_BUFFER, mTexCoordBuffer);
+ glBufferData(GL_ARRAY_BUFFER, texCoordData.length, texCoordData.bytes, GL_STATIC_DRAW);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ }
+ else
+ {
+ [textures release];
+ [colorData release];
+ textures = nil;
+ colorData = nil;
+ }
+
+ mTextureSwitches = textures;
+ mColorData = colorData;
+
+ [matrices release];
+ [vertexData release];
+ [texCoordData release];
+
+ free(scratchBuffer);
+
+ SP_RELEASE_POOL(pool);
+ return success;
+}
+
+- (BOOL)processVerticesOfObject:(SPDisplayObject *)object withMatrices:(NSMutableArray *)matrices
+ vertexData:(NSMutableData *)vertexData buffer:(void *)buffer
+{
+ if (object.alpha == 0.0f || !object.visible) return YES;
+ SPMatrix *currentMatrix = [matrices lastObject];
+ BOOL success = YES;
+
+ if ([object isKindOfClass:[SPDisplayObjectContainer class]])
+ {
+ for (SPDisplayObject *child in (SPDisplayObjectContainer *)object)
+ {
+ SPMatrix *childMatrix = child.transformationMatrix;
+ [childMatrix concatMatrix:currentMatrix];
+ [matrices addObject:childMatrix];
+
+ success = [self processVerticesOfObject:child withMatrices:matrices vertexData:vertexData
+ buffer:buffer];
+ [matrices removeLastObject];
+ if (!success) break;
+ }
+ }
+ else if ([object isKindOfClass:[SPQuad class]])
+ {
+ SPQuad *quad = (SPQuad *)object;
+ float *vertexCoords = (float *)buffer;
+ [quad copyVertexCoords:vertexCoords colors:NULL textureCoords:NULL];
+
+ for (int i=0; i<4; ++i)
+ {
+ float x = vertexCoords[2*i];
+ float y = vertexCoords[2*i+1];
+ SPPoint *vertex = [currentMatrix transformPoint:[SPPoint pointWithX:x y:y]];
+ vertexCoords[2*i ] = vertex.x;
+ vertexCoords[2*i+1] = vertex.y;
+ }
+
+ [vertexData appendBytes:buffer length:8 * sizeof(float)];
+ }
+ else
+ {
+ NSLog(@"Objects of type '%@' are not supported for compilation", [object class]);
+ success = NO;
+ }
+
+ return success;
+}
+
+- (BOOL)processColorsOfObject:(SPDisplayObject *)object withAlpha:(float)alpha
+ colorData:(NSMutableData *)colorData buffer:(void *)buffer
+{
+ if (alpha == 0.0f || !object.visible) return YES;
+ BOOL success = YES;
+
+ if ([object isKindOfClass:[SPDisplayObjectContainer class]])
+ {
+ for (SPDisplayObject *child in (SPDisplayObjectContainer *)object)
+ {
+ success = [self processColorsOfObject:child withAlpha:alpha * child.alpha
+ colorData:colorData buffer:buffer];
+ if (!success) break;
+ }
+ }
+ else if ([object isKindOfClass:[SPQuad class]])
+ {
+ SPQuad *quad = (SPQuad *)object;
+ uint *colors = (uint *)buffer;
+ [quad copyVertexCoords:NULL colors:colors textureCoords:NULL];
+ uint alphaBits = (GLubyte)(alpha * 255) << 24;
+
+ // add alpha information
+ for (int i=0; i<4; ++i)
+ colors[i] |= alphaBits;
+
+ [colorData appendBytes:colors length:4 * sizeof(uint)];
+ }
+ else
+ {
+ success = NO;
+ }
+
+ return success;
+}
+
+- (BOOL)processTexturesOfObject:(SPDisplayObject *)object withTextures:(NSMutableArray *)textures
+ texCoordData:(NSMutableData *)texCoordData buffer:(void *)buffer
+{
+ if (object.alpha == 0.0f || !object.visible) return YES;
+ BOOL success = YES;
+
+ if ([object isKindOfClass:[SPDisplayObjectContainer class]])
+ {
+ for (SPDisplayObject *child in (SPDisplayObjectContainer *)object)
+ {
+ success = [self processTexturesOfObject:child withTextures:textures
+ texCoordData:texCoordData buffer:buffer];
+ }
+ }
+ else if ([object isKindOfClass:[SPQuad class]])
+ {
+ SPQuad *quad = (SPQuad *)object;
+ SPImage *image = [object isKindOfClass:[SPImage class]] ? (SPImage *)object : nil;
+
+ // process texture switches
+ // (textureData contains "texture, vertexCount, texture, vertexCount, etc.")
+
+ id texture = image.texture;
+ id lastTexture = [textures objectAtIndex:textures.count-2];
+ if ([lastTexture isKindOfClass:[NSNull class]]) lastTexture = nil;
+
+ uint textureID = [texture textureID];
+ uint lastTextureID = [lastTexture textureID];
+ uint lastTextureVertexCount = [[textures lastObject] intValue];
+
+ if (textureID != lastTextureID)
+ {
+ [textures addObject:texture ? texture : [NSNull null]];
+ [textures addObject:[NSNumber numberWithInt:4]];
+ }
+ else
+ {
+ [textures removeLastObject];
+ [textures addObject:[NSNumber numberWithInt:lastTextureVertexCount + 4]];
+ }
+
+ float *texCoords = (float *)buffer;
+ [quad copyVertexCoords:NULL colors:NULL textureCoords:texCoords];
+
+ if (textureID)
+ [texture adjustTextureCoordinates:texCoords saveAtTarget:texCoords numVertices:4];
+
+ [texCoordData appendBytes:texCoords length:8 * sizeof(float)];
+ }
+ else
+ {
+ success = NO;
+ }
+
+ return success;
+}
+
+- (void)setAlpha:(float)value
+{
+ if (value != self.alpha)
+ {
+ mAlphaChanged = YES;
+ [super setAlpha:value];
+ }
+}
+
+- (void)render:(SPRenderSupport *)support
+{
+ if (!mTextureSwitches) [self compile];
+ if (!mCurrentColors || mAlphaChanged) [self updateColorData];
+
+ int vertexOffset = 0;
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer);
+
+ for (int i=0; i<mTextureSwitches.count; i+=2)
+ {
+ int numVertices = [[mTextureSwitches objectAtIndex:i+1] intValue];
+ if (!numVertices) continue;
+
+ id texture = [mTextureSwitches objectAtIndex:i];
+ if ([texture isKindOfClass:[NSNull class]]) texture = nil;
+
+ int renderedVertices = (numVertices / 4) * 6;
+ [support bindTexture:texture];
+
+ if (texture)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, mTexCoordBuffer);
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+ glTexCoordPointer(2, GL_FLOAT, 0, 0);
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glVertexPointer(2, GL_FLOAT, 0, 0);
+
+ glBindBuffer(GL_ARRAY_BUFFER, mColorBuffer);
+ glEnableClientState(GL_COLOR_ARRAY);
+ glColorPointer(4, GL_UNSIGNED_BYTE, 0, 0);
+
+ glDrawElements(GL_TRIANGLE_STRIP, renderedVertices, GL_UNSIGNED_SHORT,
+ (void *)(vertexOffset * sizeof(GLushort)));
+
+ glDisableClientState(GL_VERTEX_ARRAY);
+ glDisableClientState(GL_COLOR_ARRAY);
+ glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+
+ vertexOffset += renderedVertices;
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
+
+- (void)updateColorData
+{
+ if (!mCurrentColors)
+ mCurrentColors = malloc(mColorData.length * sizeof(uint));
+
+ const uint *origColors = (const uint *)mColorData.bytes;
+ uint *newColors = mCurrentColors;
+ float alpha = self.alpha;
+
+ for (int i=0; i<mTextureSwitches.count; i+=2)
+ {
+ int numVertices = [[mTextureSwitches objectAtIndex:i+1] intValue];
+ if (!numVertices) continue;
+
+ id texture = [mTextureSwitches objectAtIndex:i];
+ if ([texture isKindOfClass:[NSNull class]]) texture = nil;
+ BOOL pma = [texture hasPremultipliedAlpha];
+
+ for (int i=0; i<numVertices; ++i)
+ {
+ uint origColor = *origColors;
+ float vertexAlpha = (origColor >> 24) / 255.0f * alpha;
+ *newColors = [SPRenderSupport convertColor:origColor alpha:vertexAlpha premultiplyAlpha:pma];
+ ++origColors;
+ ++newColors;
+ }
+ }
+
+ // update buffer
+ glBindBuffer(GL_ARRAY_BUFFER, mColorBuffer);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, mColorData.length, mCurrentColors);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+ mAlphaChanged = NO;
+}
+
+- (void)deleteBuffers
+{
+ glDeleteBuffers(4, &mIndexBuffer);
+ mIndexBuffer = mVertexBuffer = mColorBuffer = mTexCoordBuffer = 0;
+}
+
+@end
--- /dev/null
+//
+// SPDelayedInvocation.h
+// Sparrow
+//
+// Created by Daniel Sperl on 11.07.09.
+// Copyright 2009 Incognitek. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+#import "SPAnimatable.h"
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPDelayedInvocation is a proxy object that will forward any methods that are called on it
+ to a certain target - but only after a certain time has passed.
+
+ The easiest way to delay an invocation is by calling [SPJuggler delayInvocationAtTarget:byTime:].
+ This method will create a delayed invocation for you, adding it to the juggler right away.
+
+------------------------------------------------------------------------------------------------- */
+
+
+@interface SPDelayedInvocation : NSObject <SPAnimatable>
+{
+ @private
+ id mTarget;
+ NSMutableSet *mInvocations;
+ double mTotalTime;
+ double mCurrentTime;