# 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;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a delayed invocation.
+- (id)initWithTarget:(id)target delay:(double)time;
+
+/// Factory method.
++ (SPDelayedInvocation*)invocationWithTarget:(id)target delay:(double)time;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The target object to which messages will be forwarded.
+@property (nonatomic, readonly) id target;
+
+/// The time messages will be delayed (in seconds).
+@property (nonatomic, readonly) double totalTime;
+
+/// The time that has already passed (in seconds).
+@property (nonatomic, assign) double currentTime;
+
+@end
--- /dev/null
+//
+// SPDelayedInvocation.m
+// 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 "SPDelayedInvocation.h"
+
+
+@implementation SPDelayedInvocation
+
+@synthesize totalTime = mTotalTime;
+@synthesize currentTime = mCurrentTime;
+@synthesize target = mTarget;
+
+- (id)initWithTarget:(id)target delay:(double)time
+{
+ if (!target)
+ {
+ [self release];
+ return nil;
+ }
+
+ if (self = [super init])
+ {
+ mTotalTime = MAX(0.0001, time); // zero is not allowed
+ mCurrentTime = 0;
+ mTarget = [target retain];
+ mInvocations = [[NSMutableSet alloc] init];
+ }
+ return self;
+}
+
+- (id)init
+{
+ [self release];
+ return nil;
+}
+
+- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
+{
+ NSMethodSignature *sig = [[self class] instanceMethodSignatureForSelector:aSelector];
+ if (!sig) sig = [mTarget methodSignatureForSelector:aSelector];
+ return sig;
+}
+
+- (void)forwardInvocation:(NSInvocation*)anInvocation
+{
+ if ([mTarget respondsToSelector:[anInvocation selector]])
+ {
+ anInvocation.target = mTarget;
+ [anInvocation retainArguments];
+ [mInvocations addObject:anInvocation];
+ }
+}
+
+- (void)advanceTime:(double)seconds
+{
+ self.currentTime = mCurrentTime + seconds;
+}
+
+- (void)setCurrentTime:(double)currentTime
+{
+ double previousTime = mCurrentTime;
+ mCurrentTime = MIN(mTotalTime, currentTime);
+
+ if (previousTime < mTotalTime && mCurrentTime >= mTotalTime)
+ [mInvocations makeObjectsPerformSelector:@selector(invoke)];
+}
+
+- (BOOL)isComplete
+{
+ return mCurrentTime >= mTotalTime;
+}
+
++ (SPDelayedInvocation*)invocationWithTarget:(id)target delay:(double)time
+{
+ return [[[SPDelayedInvocation alloc] initWithTarget:target delay:time] autorelease];
+}
+
+- (void)dealloc
+{
+ [mTarget release];
+ [mInvocations release];
+ [super dealloc];
+}
+
+@end
--- /dev/null
+//
+// SPDisplayObject.h
+// Sparrow
+//
+// Created by Daniel Sperl on 15.03.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 "SPEventDispatcher.h"
+#import "SPRectangle.h"
+#import "SPMatrix.h"
+
+@class SPDisplayObjectContainer;
+@class SPStage;
+@class SPRenderSupport;
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPDisplayObject class is the base class for all objects that are rendered on the screen.
+
+ In Sparrow, all displayable objects are organized in a display tree. Only objects that are part of
+ the display tree will be displayed (rendered).
+
+ The display tree consists of leaf nodes (SPImage, SPQuad) that will be rendered directly to
+ the screen, and of container nodes (subclasses of SPDisplayObjectContainer, like SPSprite).
+ A container is simply a display object that has child nodes - which can, again, be either leaf
+ nodes or other containers.
+
+ At the root of the display tree, there is the SPStage, which is a container, too. To create a
+ Sparrow application, you let your main class inherit from SPStage, and build up your display
+ tree from there.
+
+ A display object has properties that define its position in relation to its parent
+ (`x`, `y`), as well as its rotation and scaling factors (`scaleX`, `scaleY`). Use the `alpha` and
+ `visible` properties to make an object translucent or invisible.
+
+ Every display object may be the target of touch events. If you don't want an object to be
+ touchable, you can disable the `touchable` property. When it's disabled, neither the object
+ nor its childs will receive any more touch events.
+
+ *Points vs. Pixels*
+
+ All sizes and distances are measured in points. What this means in pixels depends on the
+ contentScaleFactor of the stage. On a low resolution device (up to iPhone 3GS), one point is one
+ pixel. On devices with a retina display, one point may be 2 pixels.
+
+ *Transforming coordinates*
+
+ Within the display tree, each object has its own local coordinate system. If you rotate a container,
+ you rotate that coordinate system - and thus all the children of the container.
+
+ Sometimes you need to know where a certain point lies relative to another coordinate system.
+ That's the purpose of the method `transformationMatrixToSpace:`. It will create a matrix that
+ represents the transformation of a point in one coordinate system to another.
+
+ *Subclassing SPDisplayObject*
+
+ As SPDisplayObject is an abstract class, you can't instantiate it directly, but have to use one of
+ its subclasses instead. There are already a lot of them available, and most of the time they will
+ suffice.
+
+ However, you can create custom subclasses as well. That's especially useful when you want to
+ create an object with a custom render function.
+
+ You will need to implement the following methods when you subclass SPDisplayObject:
+
+ - (void)render:(SPRenderSupport*)support;
+ - (SPRectangle*)boundsInSpace:(SPDisplayObject*)targetCoordinateSpace;
+
+ Have a look at SPQuad for a sample implementation of those methods.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPDisplayObject : SPEventDispatcher
+{
+ @private
+ float mX;
+ float mY;
+ float mScaleX;
+ float mScaleY;
+ float mRotationZ;
+ float mAlpha;
+ BOOL mVisible;
+ BOOL mTouchable;
+
+ SPDisplayObjectContainer *mParent;
+ double mLastTouchTimestamp;
+ NSString *mName;
+}
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Renders the display object with the help of a support object.
+- (void)render:(SPRenderSupport*)support;
+
+/// Removes the object from its parent, if it has one.
+- (void)removeFromParent;
+
+/// Creates a matrix that represents the transformation from the local coordinate system to another.
+- (SPMatrix*)transformationMatrixToSpace:(SPDisplayObject*)targetCoordinateSpace;
+
+/// Returns a rectangle that completely encloses the object as it appears in another coordinate system.
+- (SPRectangle*)boundsInSpace:(SPDisplayObject*)targetCoordinateSpace;
+
+/// Transforms a point from the local coordinate system to global (stage) coordinates.
+- (SPPoint*)localToGlobal:(SPPoint*)localPoint;
+
+/// Transforms a point from global (stage) coordinates to the local coordinate system.
+- (SPPoint*)globalToLocal:(SPPoint*)globalPoint;
+
+/// Returns the object that is found topmost on a point in local coordinates, or nil if the test fails.
+- (SPDisplayObject*)hitTestPoint:(SPPoint*)localPoint forTouch:(BOOL)isTouch;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The x coordinates of the object relative to the local coordinates of the parent.
+@property (nonatomic, assign) float x;
+
+/// The y coordinates of the object relative to the local coordinates of the parent.
+@property (nonatomic, assign) float y;
+
+/// The horizontal scale factor. "1" means no scale, negative values flip the object.
+@property (nonatomic, assign) float scaleX;
+
+/// The vertical scale factor. "1" means no scale, negative values flip the object.
+@property (nonatomic, assign) float scaleY;
+
+/// The width of the object in points.
+@property (nonatomic, assign) float width;
+
+/// The height of the object in points.
+@property (nonatomic, assign) float height;
+
+/// The rotation of the object in radians. (In Sparrow, all angles are measured in radians.)
+@property (nonatomic, assign) float rotation;
+
+/// The opacity of the object. 0 = transparent, 1 = opaque.
+@property (nonatomic, assign) float alpha;
+
+/// The visibility of the object. An invisible object will be untouchable.
+@property (nonatomic, assign) BOOL visible;
+
+/// Indicates if this object (and its children) will receive touch events.
+@property (nonatomic, assign) BOOL touchable;
+
+/// The bounds of the object relative to the local coordinates of the parent.
+@property (nonatomic, readonly) SPRectangle *bounds;
+
+/// The display object container that contains this display object.
+@property (nonatomic, readonly) SPDisplayObjectContainer *parent;
+
+/// The topmost object in the display tree the object is part of.
+@property (nonatomic, readonly) SPDisplayObject *root;
+
+/// The stage the display object is connected to, or nil if it is not connected to a stage.
+@property (nonatomic, readonly) SPStage *stage;
+
+/// The transformation matrix of the object relative to its parent.
+@property (nonatomic, readonly) SPMatrix *transformationMatrix;
+
+/// The name of the display object (default: nil). Used by `childByName:` of display object containers.
+@property (nonatomic, copy) NSString *name;
+
+@end
--- /dev/null
+//
+// SPDisplayObject.m
+// Sparrow
+//
+// Created by Daniel Sperl on 15.03.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 "SPDisplayObject.h"
+#import "SPDisplayObject_Internal.h"
+#import "SPDisplayObjectContainer.h"
+#import "SPStage.h"
+#import "SPMacros.h"
+#import "SPTouchEvent.h"
+
+// --- class implementation ------------------------------------------------------------------------
+
+@implementation SPDisplayObject
+
+@synthesize x = mX;
+@synthesize y = mY;
+@synthesize scaleX = mScaleX;
+@synthesize scaleY = mScaleY;
+@synthesize rotation = mRotationZ;
+@synthesize parent = mParent;
+@synthesize alpha = mAlpha;
+@synthesize visible = mVisible;
+@synthesize touchable = mTouchable;
+@synthesize name = mName;
+
+- (id)init
+{
+ #ifdef DEBUG
+ if ([self isMemberOfClass:[SPDisplayObject class]])
+ {
+ [self release];
+ [NSException raise:SP_EXC_ABSTRACT_CLASS
+ format:@"Attempting to initialize abstract class SPDisplayObject."];
+ return nil;
+ }
+ #endif
+
+ if (self = [super init])
+ {
+ mAlpha = 1.0f;
+ mScaleX = 1.0f;
+ mScaleY = 1.0f;
+ mVisible = YES;
+ mTouchable = YES;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [mName release];
+ [super dealloc];
+}
+
+- (void)render:(SPRenderSupport*)support
+{
+ // override in subclass
+}
+
+- (void)removeFromParent
+{
+ [mParent removeChild:self];
+}
+
+- (SPMatrix*)transformationMatrixToSpace:(SPDisplayObject*)targetCoordinateSpace
+{
+ if (targetCoordinateSpace == self)
+ {
+ return [SPMatrix matrixWithIdentity];
+ }
+ else if (!targetCoordinateSpace)
+ {
+ // targetCoordinateSpace 'nil' represents the target coordinate of the root object.
+ // -> move up from self to root
+ SPMatrix *selfMatrix = [[SPMatrix alloc] init];
+ SPDisplayObject *currentObject = self;
+ while (currentObject)
+ {
+ [selfMatrix concatMatrix:currentObject.transformationMatrix];
+ currentObject = currentObject->mParent;
+ }
+ return [selfMatrix autorelease];
+ }
+ else if (targetCoordinateSpace->mParent == self) // optimization
+ {
+ SPMatrix *targetMatrix = targetCoordinateSpace.transformationMatrix;
+ [targetMatrix invert];
+ return targetMatrix;
+ }
+ else if (mParent == targetCoordinateSpace) // optimization
+ {
+ return self.transformationMatrix;
+ }
+
+ // 1.: Find a common parent of self and the target coordinate space.
+ //
+ // This method is used very often during touch testing, so we optimized the code.
+ // Instead of using an NSSet or NSArray (which would make the code much cleaner), we
+ // use a C array here to save the ancestors.
+
+ static SPDisplayObject *ancestors[SP_MAX_DISPLAY_TREE_DEPTH];
+
+ int count = 0;
+ SPDisplayObject *commonParent = nil;
+ SPDisplayObject *currentObject = self;
+ while (currentObject && count < SP_MAX_DISPLAY_TREE_DEPTH)
+ {
+ ancestors[count++] = currentObject;
+ currentObject = currentObject->mParent;
+ }
+
+ currentObject = targetCoordinateSpace;
+ while (currentObject && !commonParent)
+ {
+ for (int i=0; i<count; ++i)
+ {
+ if (currentObject == ancestors[i])
+ {
+ commonParent = ancestors[i];
+ break;
+ }
+ }
+ currentObject = currentObject->mParent;
+ }
+
+ if (!commonParent)
+ [NSException raise:SP_EXC_NOT_RELATED format:@"Object not connected to target"];
+
+ // 2.: Move up from self to common parent
+ SPMatrix *selfMatrix = [[SPMatrix alloc] init];
+ currentObject = self;
+ while (currentObject != commonParent)
+ {
+ [selfMatrix concatMatrix:currentObject.transformationMatrix];
+ currentObject = currentObject->mParent;
+ }
+
+ // 3.: Now move up from target until we reach the common parent
+ SPMatrix *targetMatrix = [[SPMatrix alloc] init];
+ currentObject = targetCoordinateSpace;
+ while (currentObject && currentObject != commonParent)
+ {
+ [targetMatrix concatMatrix:currentObject.transformationMatrix];
+ currentObject = currentObject->mParent;
+ }
+
+ // 4.: Combine the two matrices
+ [targetMatrix invert];
+ [selfMatrix concatMatrix:targetMatrix];
+ [targetMatrix release];
+
+ return [selfMatrix autorelease];
+}
+
+- (SPRectangle*)boundsInSpace:(SPDisplayObject*)targetCoordinateSpace
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD
+ format:@"Method needs to be implemented in subclass"];
+ return nil;
+}
+
+- (SPRectangle*)bounds
+{
+ return [self boundsInSpace:mParent];
+}
+
+- (SPDisplayObject*)hitTestPoint:(SPPoint*)localPoint forTouch:(BOOL)isTouch;
+{
+ // on a touch test, invisible or untouchable objects cause the test to fail
+ if (isTouch && (!mVisible || !mTouchable)) return nil;
+
+ // otherwise, check bounding box
+ if ([[self boundsInSpace:self] containsPoint:localPoint]) return self;
+ else return nil;
+}
+
+- (SPPoint*)localToGlobal:(SPPoint*)localPoint
+{
+ // move up until parent is nil
+ SPMatrix *transformationMatrix = [[SPMatrix alloc] init];
+ SPDisplayObject *currentObject = self;
+ while (currentObject)
+ {
+ [transformationMatrix concatMatrix:currentObject.transformationMatrix];
+ currentObject = [currentObject parent];
+ }
+
+ SPPoint *globalPoint = [transformationMatrix transformPoint:localPoint];
+ [transformationMatrix release];
+ return globalPoint;
+}
+
+- (SPPoint*)globalToLocal:(SPPoint*)globalPoint
+{
+ // move up until parent is nil, then invert matrix
+ SPMatrix *transformationMatrix = [[SPMatrix alloc] init];
+ SPDisplayObject *currentObject = self;
+ while (currentObject)
+ {
+ [transformationMatrix concatMatrix:currentObject.transformationMatrix];
+ currentObject = [currentObject parent];
+ }
+
+ [transformationMatrix invert];
+ SPPoint *localPoint = [transformationMatrix transformPoint:globalPoint];
+ [transformationMatrix release];
+ return localPoint;
+}
+
+- (void)dispatchEvent:(SPEvent*)event
+{
+ // on one given moment, there is only one set of touches -- thus,
+ // we process only one touch event with a certain timestamp
+ if ([event isKindOfClass:[SPTouchEvent class]])
+ {
+ SPTouchEvent *touchEvent = (SPTouchEvent*)event;
+ if (touchEvent.timestamp == mLastTouchTimestamp) return;
+ else mLastTouchTimestamp = touchEvent.timestamp;
+ }
+
+ [super dispatchEvent:event];
+}
+
+- (float)width
+{
+ return [self boundsInSpace:mParent].width;
+}
+
+- (void)setWidth:(float)value
+{
+ // this method calls 'self.scaleX' instead of changing mScaleX directly.
+ // that way, subclasses reacting on size changes need to override only the scaleX method.
+
+ mScaleX = 1.0f;
+ float actualWidth = self.width;
+ if (actualWidth != 0.0f) self.scaleX = value / actualWidth;
+ else self.scaleX = 1.0f;
+}
+
+- (float)height
+{
+ return [self boundsInSpace:mParent].height;
+}
+
+- (void)setHeight:(float)value
+{
+ mScaleY = 1.0f;
+ float actualHeight = self.height;
+ if (actualHeight != 0.0f) self.scaleY = value / actualHeight;
+ else self.scaleY = 1.0f;
+}
+
+- (void)setRotation:(float)value
+{
+ // clamp between [-180 deg, +180 deg]
+ while (value < -PI) value += TWO_PI;
+ while (value > PI) value -= TWO_PI;
+ mRotationZ = value;
+}
+
+- (void)setAlpha:(float)value
+{
+ mAlpha = MAX(0.0f, MIN(1.0f, value));
+}
+
+- (SPDisplayObject*)root
+{
+ SPDisplayObject *currentObject = self;
+ while (currentObject->mParent)
+ currentObject = currentObject->mParent;
+ return currentObject;
+}
+
+- (SPStage*)stage
+{
+ SPDisplayObject *root = self.root;
+ if ([root isKindOfClass:[SPStage class]]) return (SPStage*) root;
+ else return nil;
+}
+
+- (SPMatrix*)transformationMatrix
+{
+ SPMatrix *matrix = [[SPMatrix alloc] init];
+
+ if (mScaleX != 1.0f || mScaleY != 1.0f) [matrix scaleXBy:mScaleX yBy:mScaleY];
+ if (mRotationZ != 0.0f) [matrix rotateBy:mRotationZ];
+ if (mX != 0.0f || mY != 0.0f) [matrix translateXBy:mX yBy:mY];
+
+ return [matrix autorelease];
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPDisplayObject (Internal)
+
+- (void)setParent:(SPDisplayObjectContainer*)parent
+{
+ // only assigned, not retained -- otherwise, we would create a circular reference.
+ mParent = parent;
+}
+
+- (void)dispatchEventOnChildren:(SPEvent *)event
+{
+ [self dispatchEvent:event];
+}
+
+@end
--- /dev/null
+//
+// SPDisplayObjectContainer.h
+// Sparrow
+//
+// Created by Daniel Sperl on 15.03.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 "SPDisplayObject.h"
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPDisplayObjectContainer represents a collection of display objects.
+
+ It is the base class of all display objects that act as a container for other objects. By
+ maintaining an ordered list of children, it defines the back-to-front positioning of the children
+ within the display tree.
+
+ A container does not have size in itself. The width and height properties represent the extents
+ of its children. Changing those properties will scale all children accordingly.
+
+ As this is an abstract class, you can't instantiate it directly, but have to
+ use a subclass instead. The most lightweight container class is SPSprite.
+
+ *Adding and removing children*
+
+ The class defines methods that allow you to add or remove children. When you add a child, it will
+ be added at the foremost position, possibly occluding a child that was added before. You can access
+ the children via an index. The first child will have index 0, the second child index 1, etc.
+
+ Adding and removing objects from a container triggers non-bubbling events.
+
+ * `SP_EVENT_TYPE_ADDED`: the object was added to a parent.
+ * `SP_EVENT_TYPE_ADDED_TO_STAGE`: the object was added to a parent that is connected to the stage,
+ thus becoming visible now.
+ * `SP_EVENT_TYPE_REMOVED`: the object was removed from a parent.
+ * `SP_EVENT_TYPE_REMOVED_FROM_STAGE`: the object was removed from a parent that is connected to
+ the stage, thus becoming invisible now.
+
+ Especially the `ADDED_TO_STAGE` event is very helpful, as it allows you to automatically execute
+ some logic (e.g. start an animation) when an object is rendered the first time.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPDisplayObjectContainer : SPDisplayObject <NSFastEnumeration>
+{
+ @private
+ NSMutableArray *mChildren;
+}
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Adds a child to the container. It will be at the topmost position.
+- (void)addChild:(SPDisplayObject *)child;
+
+/// Adds a child to the container at a certain index.
+- (void)addChild:(SPDisplayObject *)child atIndex:(int)index;
+
+/// Determines if a certain object is a child of the container (recursively).
+- (BOOL)containsChild:(SPDisplayObject *)child;
+
+/// Returns a child object at a certain index.
+- (SPDisplayObject *)childAtIndex:(int)index;
+
+/// Returns a child object with a certain name (non-recursively).
+- (SPDisplayObject *)childByName:(NSString *)name;
+
+/// Returns the index of a child within the container.
+- (int)childIndex:(SPDisplayObject *)child;
+
+/// Removes a child from the container. If the object is not a child, nothing happens.
+- (void)removeChild:(SPDisplayObject *)child;
+
+/// Removes a child at a certain index. Children above the child will move down.
+- (void)removeChildAtIndex:(int)index;
+
+/// Removes all children from the container.
+- (void)removeAllChildren;
+
+/// Swaps the indexes of two children.
+- (void)swapChild:(SPDisplayObject*)child1 withChild:(SPDisplayObject*)child2;
+
+/// Swaps the indexes of two children.
+- (void)swapChildAtIndex:(int)index1 withChildAtIndex:(int)index2;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The number of children of this container.
+@property (nonatomic, readonly) int numChildren;
+
+
+@end
--- /dev/null
+//
+// SPDisplayObjectContainer.m
+// Sparrow
+//
+// Created by Daniel Sperl on 15.03.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 "SPDisplayObjectContainer.h"
+#import "SPEnterFrameEvent.h"
+#import "SPDisplayObject_Internal.h"
+#import "SPMacros.h"
+
+// --- C functions ---------------------------------------------------------------------------------
+
+static void getChildEventListeners(SPDisplayObject *object, NSString *eventType,
+ NSMutableArray *listeners)
+{
+ // some events (ENTER_FRAME, ADDED_TO_STAGE, etc.) are dispatched very often and traverse
+ // the entire display tree -- thus, it pays off handling them in their own c function.
+
+ if ([object hasEventListenerForType:eventType])
+ [listeners addObject:object];
+
+ if ([object isKindOfClass:[SPDisplayObjectContainer class]])
+ for (SPDisplayObject *child in (SPDisplayObjectContainer *)object)
+ getChildEventListeners(child, eventType, listeners);
+}
+
+// --- class implementation ------------------------------------------------------------------------
+
+@implementation SPDisplayObjectContainer
+
+- (id)init
+{
+ #if DEBUG
+ if ([[self class] isEqual:[SPDisplayObjectContainer class]])
+ {
+ [NSException raise:SP_EXC_ABSTRACT_CLASS
+ format:@"Attempting to instantiate SPDisplayObjectContainer directly."];
+ [self release];
+ return nil;
+ }
+ #endif
+
+ if (self = [super init])
+ {
+ mChildren = [[NSMutableArray alloc] init];
+ }
+ return self;
+}
+
+- (void)addChild:(SPDisplayObject *)child
+{
+ [self addChild:child atIndex:[mChildren count]];
+}
+
+- (void)addChild:(SPDisplayObject *)child atIndex:(int)index
+{
+ if (index >= 0 && index <= [mChildren count])
+ {
+ [child retain];
+ [child removeFromParent];
+ [mChildren insertObject:child atIndex:MIN(mChildren.count, index)];
+ child.parent = self;
+
+ SPEvent *addedEvent = [[SPEvent alloc] initWithType:SP_EVENT_TYPE_ADDED];
+ [child dispatchEvent:addedEvent];
+ [addedEvent release];
+
+ if (self.stage)
+ {
+ SPEvent *addedToStageEvent = [[SPEvent alloc] initWithType:SP_EVENT_TYPE_ADDED_TO_STAGE];
+ [child dispatchEventOnChildren:addedToStageEvent];
+ [addedToStageEvent release];
+ }
+
+ [child release];
+ }
+ else [NSException raise:SP_EXC_INDEX_OUT_OF_BOUNDS format:@"Invalid child index"];
+}
+
+- (BOOL)containsChild:(SPDisplayObject *)child
+{
+ if ([self isEqual:child]) return YES;
+
+ for (SPDisplayObject *currentChild in mChildren)
+ {
+ if ([currentChild isKindOfClass:[SPDisplayObjectContainer class]])
+ {
+ if ([(SPDisplayObjectContainer *)currentChild containsChild:child]) return YES;
+ }
+ else
+ {
+ if (currentChild == child) return YES;
+ }
+ }
+
+ return NO;
+}
+
+- (SPDisplayObject *)childAtIndex:(int)index
+{
+ return [mChildren objectAtIndex:index];
+}
+
+- (SPDisplayObject *)childByName:(NSString *)name
+{
+ for (SPDisplayObject *currentChild in mChildren)
+ if ([currentChild.name isEqualToString:name]) return currentChild;
+
+ return nil;
+}
+
+- (int)childIndex:(SPDisplayObject *)child
+{
+ int index = [mChildren indexOfObject:child];
+ if (index == NSNotFound) return SP_NOT_FOUND;
+ else return index;
+}
+
+- (void)removeChild:(SPDisplayObject *)child
+{
+ int childIndex = [self childIndex:child];
+ if (childIndex != SP_NOT_FOUND)
+ [self removeChildAtIndex:childIndex];
+}
+
+- (void)removeChildAtIndex:(int)index
+{
+ if (index >= 0 && index < [mChildren count])
+ {
+ SPDisplayObject *child = [[mChildren objectAtIndex:index] retain];
+
+ SPEvent *remEvent = [[SPEvent alloc] initWithType:SP_EVENT_TYPE_REMOVED];
+ [child dispatchEvent:remEvent];
+ [remEvent release];
+
+ if (self.stage)
+ {
+ SPEvent *remFromStageEvent = [[SPEvent alloc] initWithType:SP_EVENT_TYPE_REMOVED_FROM_STAGE];
+ [child dispatchEventOnChildren:remFromStageEvent];
+ [remFromStageEvent release];
+ }
+
+ [mChildren removeObjectAtIndex:index];
+ child.parent = nil;
+
+ [child release];
+ }
+ else [NSException raise:SP_EXC_INDEX_OUT_OF_BOUNDS format:@"Invalid child index"];
+}
+
+- (void)swapChild:(SPDisplayObject*)child1 withChild:(SPDisplayObject*)child2
+{
+ int index1 = [self childIndex:child1];
+ int index2 = [self childIndex:child2];
+ [self swapChildAtIndex:index1 withChildAtIndex:index2];
+}
+
+- (void)swapChildAtIndex:(int)index1 withChildAtIndex:(int)index2
+{
+ int numChildren = [mChildren count];
+ if (index1 < 0 || index1 >= numChildren || index2 < 0 || index2 >= numChildren)
+ [NSException raise:SP_EXC_INVALID_OPERATION format:@"invalid child indices"];
+ [mChildren exchangeObjectAtIndex:index1 withObjectAtIndex:index2];
+}
+
+- (void)removeAllChildren
+{
+ for (int i=mChildren.count-1; i>=0; --i)
+ [self removeChildAtIndex:i];
+}
+
+- (int)numChildren
+{
+ return [mChildren count];
+}
+
+- (SPRectangle*)boundsInSpace:(SPDisplayObject*)targetCoordinateSpace
+{
+ int numChildren = [mChildren count];
+
+ if (numChildren == 0)
+ return [SPRectangle rectangleWithX:0 y:0 width:0 height:0];
+ else if (numChildren == 1)
+ return [[mChildren objectAtIndex:0] boundsInSpace:targetCoordinateSpace];
+ else
+ {
+ float minX = FLT_MAX, maxX = -FLT_MAX, minY = FLT_MAX, maxY = -FLT_MAX;
+ for (SPDisplayObject *child in mChildren)
+ {
+ SPRectangle *childBounds = [child boundsInSpace:targetCoordinateSpace];
+ minX = MIN(minX, childBounds.x);
+ maxX = MAX(maxX, childBounds.x + childBounds.width);
+ minY = MIN(minY, childBounds.y);
+ maxY = MAX(maxY, childBounds.y + childBounds.height);
+ }
+ return [SPRectangle rectangleWithX:minX y:minY width:maxX-minX height:maxY-minY];
+ }
+}
+
+- (SPDisplayObject*)hitTestPoint:(SPPoint*)localPoint forTouch:(BOOL)isTouch;
+{
+ if (isTouch && (!self.visible || !self.touchable))
+ return nil;
+
+ for (int i=[mChildren count]-1; i>=0; --i) // front to back!
+ {
+ SPDisplayObject *child = [mChildren objectAtIndex:i];
+ SPMatrix *transformationMatrix = [self transformationMatrixToSpace:child];
+ SPPoint *transformedPoint = [transformationMatrix transformPoint:localPoint];
+ SPDisplayObject *target = [child hitTestPoint:transformedPoint forTouch:isTouch];
+ if (target) return target;
+ }
+
+ return nil;
+}
+
+- (void)dispatchEventOnChildren:(SPEvent *)event
+{
+ // the event listeners might modify the display tree, which could make the loop crash.
+ // thus, we collect them in a list and iterate over that list instead.
+
+ NSMutableArray *listeners = [[NSMutableArray alloc] init];
+ getChildEventListeners(self, event.type, listeners);
+ [listeners makeObjectsPerformSelector:@selector(dispatchEvent:) withObject:event];
+ [listeners release];
+}
+
+- (void)dealloc
+{
+ // 'self' is becoming invalid; thus, we have to remove any references to it.
+ [mChildren makeObjectsPerformSelector:@selector(setParent:) withObject:nil];
+ [mChildren release];
+ [super dealloc];
+}
+
+#pragma mark NSFastEnumeration
+
+- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf
+ count:(NSUInteger)len
+{
+ return [mChildren countByEnumeratingWithState:state objects:stackbuf count:len];
+}
+
+@end
--- /dev/null
+//
+// SPDisplayObject_Internal.h
+// Sparrow
+//
+// Created by Daniel Sperl on 03.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>
+#import "SPDisplayObject.h"
+
+@interface SPDisplayObject (Internal)
+
+- (void)setParent:(SPDisplayObjectContainer*)parent;
+- (void)dispatchEventOnChildren:(SPEvent *)event;
+
+@end
--- /dev/null
+//
+// SPEnterFrameEvent.h
+// Sparrow
+//
+// Created by Daniel Sperl on 30.04.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 "SPEvent.h"
+
+#define SP_EVENT_TYPE_ENTER_FRAME @"enterFrame"
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPEnterFrameEvent is triggered once per frame and is dispatched to all objects in the
+ display tree.
+
+ It contains information about the time that has passed since the last frame. That way, you
+ can easily make animations that are independet of the frame rate, but take the passed time
+ into account.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPEnterFrameEvent : SPEvent
+{
+ @private
+ double mPassedTime;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes an enter frame event with the passed time. _Designated Initializer_.
+- (id)initWithType:(NSString*)type bubbles:(BOOL)bubbles passedTime:(double)seconds;
+
+/// Initializes an enter frame event that does not bubble (recommended).
+- (id)initWithType:(NSString*)type passedTime:(double)seconds;
+
+/// Factory method.
++ (SPEnterFrameEvent*)eventWithType:(NSString*)type passedTime:(double)seconds;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The time that has passed since the last frame (in seconds).
+@property (nonatomic, readonly) double passedTime;
+
+@end
--- /dev/null
+//
+// SPEnterFrameEvent.m
+// Sparrow
+//
+// Created by Daniel Sperl on 30.04.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 "SPEnterFrameEvent.h"
+
+
+@implementation SPEnterFrameEvent
+
+@synthesize passedTime = mPassedTime;
+
+- (id)initWithType:(NSString*)type bubbles:(BOOL)bubbles passedTime:(double)seconds
+{
+ if (self = [super initWithType:type bubbles:bubbles])
+ {
+ mPassedTime = seconds;
+ }
+ return self;
+}
+
+- (id)initWithType:(NSString*)type passedTime:(double)seconds;
+{
+ return [self initWithType:type bubbles:NO passedTime:seconds];
+}
+
+- (id)initWithType:(NSString*)type bubbles:(BOOL)bubbles
+{
+ return [self initWithType:type bubbles:bubbles passedTime:0.0f];
+}
+
++ (SPEnterFrameEvent*)eventWithType:(NSString*)type passedTime:(double)seconds
+{
+ return [[[SPEnterFrameEvent alloc] initWithType:type passedTime:seconds] autorelease];
+}
+
+- (void)dealloc
+{
+ [super dealloc];
+}
+
+@end
--- /dev/null
+//
+// SPEvent.h
+// Sparrow
+//
+// Created by Daniel Sperl on 27.04.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_EVENT_TYPE_ADDED @"added"
+#define SP_EVENT_TYPE_ADDED_TO_STAGE @"addedToStage"
+#define SP_EVENT_TYPE_REMOVED @"removed"
+#define SP_EVENT_TYPE_REMOVED_FROM_STAGE @"removedFromStage"
+
+@class SPEventDispatcher;
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPEvent class contains data that describes an event.
+
+ `SPEventDispatcher`s create instances of this class and send them to registered listeners. An event
+ contains information that characterizes an event, most importantly the event type and if the event
+ bubbles. The target of an event is the object that dispatched it.
+
+ For some event types, this information is sufficient; other events may need additional information
+ to be carried to the listener.
+ In that case, you can subclass SPEvent and add properties with all the information you require.
+ The SPEnterFrameEvent is an example for this practice; it adds a property about the time that
+ has passed since the last frame.
+
+ Furthermore, the event class contains methods that can stop the event from being processed by
+ other listeners - either completely or at the next bubble stage.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPEvent : NSObject
+{
+ @private
+ SPEventDispatcher *mTarget;
+ SPEventDispatcher *mCurrentTarget;
+ NSString *mType;
+ BOOL mStopsImmediatePropagation;
+ BOOL mStopsPropagation;
+ BOOL mBubbles;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes an event object that can be passed to listeners. _Designated Initializer_.
+- (id)initWithType:(NSString*)type bubbles:(BOOL)bubbles;
+
+/// Initializes a non-bubbling event.
+- (id)initWithType:(NSString*)type;
+
+/// Factory method.
++ (SPEvent*)eventWithType:(NSString*)type bubbles:(BOOL)bubbles;
+
+/// Factory method.
++ (SPEvent*)eventWithType:(NSString*)type;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Prevents any other listeners from receiving the event.
+- (void)stopImmediatePropagation;
+
+/// Prevents listeners at the next bubble stage from receiving the event.
+- (void)stopPropagation;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// A string that identifies the event.
+@property (nonatomic, readonly) NSString *type;
+
+/// Indicates if event will bubble.
+@property (nonatomic, readonly) BOOL bubbles;
+
+/// The object that dispatched the event.
+@property (nonatomic, readonly) SPEventDispatcher *target;
+
+/// The object the event is currently bubbling at.
+@property (nonatomic, readonly) SPEventDispatcher *currentTarget;
+
+@end
--- /dev/null
+//
+// SPEvent.m
+// Sparrow
+//
+// Created by Daniel Sperl on 27.04.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 "SPEvent.h"
+#import "SPEvent_Internal.h"
+
+@implementation SPEvent
+
+@synthesize target = mTarget;
+@synthesize currentTarget = mCurrentTarget;
+@synthesize type = mType;
+@synthesize bubbles = mBubbles;
+
+- (id)initWithType:(NSString*)type bubbles:(BOOL)bubbles
+{
+ if (self = [super init])
+ {
+ mType = [[NSString alloc] initWithString:type];
+ mBubbles = bubbles;
+ }
+ return self;
+}
+
+- (id)initWithType:(NSString*)type
+{
+ return [self initWithType:type bubbles:NO];
+}
+
+- (id)init
+{
+ return [self initWithType:@"undefined"];
+}
+
+- (void)stopImmediatePropagation
+{
+ mStopsImmediatePropagation = YES;
+}
+
+- (void)stopPropagation
+{
+ mStopsPropagation = YES;
+}
+
++ (SPEvent*)eventWithType:(NSString*)type bubbles:(BOOL)bubbles
+{
+ return [[[SPEvent alloc] initWithType:type bubbles:bubbles] autorelease];
+}
+
++ (SPEvent*)eventWithType:(NSString*)type
+{
+ return [[[SPEvent alloc] initWithType:type] autorelease];
+}
+
+- (void)dealloc
+{
+ [mType release];
+ [mTarget release];
+ [mCurrentTarget release];
+ [super dealloc];
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPEvent (Internal)
+
+- (BOOL)stopsImmediatePropagation
+{
+ return mStopsImmediatePropagation;
+}
+
+- (BOOL)stopsPropagation
+{
+ return mStopsPropagation;
+}
+
+- (void)setTarget:(SPEventDispatcher*)target
+{
+ if (target != mTarget)
+ {
+ [mTarget release];
+ mTarget = [target retain];
+ }
+}
+
+- (void)setCurrentTarget:(SPEventDispatcher*)currentTarget
+{
+ if (currentTarget != mCurrentTarget)
+ {
+ [mCurrentTarget release];
+ mCurrentTarget = [currentTarget retain];
+ }
+}
+
+@end
--- /dev/null
+//
+// SPEventDispatcher.h
+// Sparrow
+//
+// Created by Daniel Sperl on 15.03.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 "SPEvent.h"
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPEventDispatcher class is the base for all classes that dispatch events.
+
+ The event mechanism is a key feature of Sparrow's architecture. Objects can communicate with
+ each other over events.
+
+ An event dispatcher can dispatch events (objects of type SPEvent or one of its subclasses)
+ to objects that have registered themselves as listeners. A string (the event type) is used to
+ identify different events.
+
+ Here is a sample:
+
+ // dispatching an event
+ [self dispatchEvent:[SPEvent eventWithType:@"eventType"]];
+
+ // listening to an event from 'object'
+ [object addEventListener:@selector(onEvent:) atObject:self forType:@"eventType"];
+
+ // the corresponding event listener
+ - (void)onEvent:(SPEvent *)event
+ {
+ // an event was triggered
+ }
+
+ As SPDisplayObject, the base object of all rendered objects, inherits from SPEventDispatcher,
+ the event mechanism is tightly bound to the display list. Events that have their `bubbles`-property
+ enabled will rise up the display list until they reach its root (normally the stage). That means
+ that a listener can register for the event type not only on the object that will dispatch it, but
+ on any object that is a direct or indirect parent of the dispatcher.
+
+ Different to _Adobe Flash_, events in Sparrow do not have a capture-phase.
+
+ @see [SPEvent]
+ @see [SPDisplayObject]
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPEventDispatcher : NSObject
+{
+ @private
+ NSMutableDictionary *mEventListeners;
+}
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Registers an event listener at a certain object.
+- (void)addEventListener:(SEL)listener atObject:(id)object forType:(NSString*)eventType
+ retainObject:(BOOL)retain;
+
+/// Registers an event listener at a certain object without retaining it (recommended).
+- (void)addEventListener:(SEL)listener atObject:(id)object forType:(NSString*)eventType;
+
+/// Removes an event listener at an object.
+- (void)removeEventListener:(SEL)listener atObject:(id)object forType:(NSString*)eventType;
+
+/// Removes all event listeners at an objct that have a certain type.
+- (void)removeEventListenersAtObject:(id)object forType:(NSString*)eventType;
+
+/// Dispatches an event to all objects that have registered for events of the same type.
+- (void)dispatchEvent:(SPEvent*)event;
+
+/// Returns if there are listeners registered for a certain event type.
+- (BOOL)hasEventListenerForType:(NSString*)eventType;
+
+@end
--- /dev/null
+//
+// SPEventDispatcher.m
+// Sparrow
+//
+// Created by Daniel Sperl on 15.03.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 "SPEventDispatcher.h"
+#import "SPDisplayObject.h"
+#import "SPEvent_Internal.h"
+#import "SPMacros.h"
+#import "SPNSExtensions.h"
+
+@implementation SPEventDispatcher
+
+- (void)addEventListener:(SEL)listener atObject:(id)object forType:(NSString*)eventType
+ retainObject:(BOOL)doRetain
+{
+ if (!mEventListeners)
+ mEventListeners = [[NSMutableDictionary alloc] init];
+
+ NSInvocation *invocation = [NSInvocation invocationWithTarget:object selector:listener];
+ if (doRetain) [invocation retainArguments];
+
+ // When an event listener is added or removed, a new NSArray object is created, instead of
+ // changing the array. The reason for this is that we can avoid creating a copy of the NSArray
+ // in the "dispatchEvent"-method, which is called far more often than
+ // "add"- and "removeEventListener".
+
+ NSArray *listeners = [mEventListeners objectForKey:eventType];
+ if (!listeners)
+ {
+ listeners = [[NSArray alloc] initWithObjects:invocation, nil];
+ [mEventListeners setObject:listeners forKey:eventType];
+ [listeners release];
+ }
+ else
+ {
+ listeners = [listeners arrayByAddingObject:invocation];
+ [mEventListeners setObject:listeners forKey:eventType];
+ }
+}
+
+- (void)addEventListener:(SEL)listener atObject:(id)object forType:(NSString*)eventType
+{
+ [self addEventListener:listener atObject:object forType:eventType retainObject:NO];
+}
+
+- (void)removeEventListener:(SEL)listener atObject:(id)object forType:(NSString*)eventType
+{
+ NSArray *listeners = [mEventListeners objectForKey:eventType];
+ if (listeners)
+ {
+ NSMutableArray *remainingListeners = [[NSMutableArray alloc] init];
+ for (NSInvocation *inv in listeners)
+ {
+ if (inv.target != object || (listener != nil && inv.selector != listener))
+ [remainingListeners addObject:inv];
+ }
+
+ if (remainingListeners.count == 0) [mEventListeners removeObjectForKey:eventType];
+ else [mEventListeners setObject:remainingListeners forKey:eventType];
+
+ [remainingListeners release];
+ }
+}
+
+- (void)removeEventListenersAtObject:(id)object forType:(NSString*)eventType
+{
+ [self removeEventListener:nil atObject:object forType:eventType];
+}
+
+- (BOOL)hasEventListenerForType:(NSString*)eventType
+{
+ return [mEventListeners objectForKey:eventType] != nil;
+}
+
+- (void)dispatchEvent:(SPEvent*)event
+{
+ NSMutableArray *listeners = [mEventListeners objectForKey:event.type];
+ if (!event.bubbles && !listeners) return; // no need to do anything.
+
+ // if the event already has a current target, it was re-dispatched by user -> we change the
+ // target to 'self' for now, but undo that later on (instead of creating a copy, which could
+ // lead to the creation of a huge amount of objects).
+ SPEventDispatcher *previousTarget = event.target;
+ if (!event.target || event.currentTarget) event.target = self;
+ event.currentTarget = self;
+
+ [self retain]; // the event listener could release 'self', so we have to make sure that it
+ // stays valid while we're here.
+
+ BOOL stopImmediatPropagation = NO;
+ if (listeners.count != 0)
+ {
+ // we can enumerate directly over the array, since "add"- and "removeEventListener" won't
+ // change it, but instead always create a new array.
+ [listeners retain];
+ for (NSInvocation *inv in listeners)
+ {
+ [inv setArgument:&event atIndex:2];
+ [inv invoke];
+ if (event.stopsImmediatePropagation)
+ {
+ stopImmediatPropagation = YES;
+ break;
+ }
+ }
+ [listeners release];
+ }
+
+ if (!stopImmediatPropagation)
+ {
+ event.currentTarget = nil; // this is how we can find out later if the event was redispatched
+ if (event.bubbles && !event.stopsPropagation && [self isKindOfClass:[SPDisplayObject class]])
+ {
+ SPDisplayObject *target = (SPDisplayObject*)self;
+ [target.parent dispatchEvent:event];
+ }
+ }
+
+ if (previousTarget) event.target = previousTarget;
+
+ // we use autorelease instead of release to avoid having to make additional "retain"-calls
+ // in calling methods (like "dispatchEventsOnChildren"). Those methods might be called very
+ // often, so we save some time by avoiding that.
+ [self autorelease];
+}
+
+- (void)dealloc
+{
+ [mEventListeners release];
+ [super dealloc];
+}
+
+@end
--- /dev/null
+//
+// SPEvent_Internal.h
+// Sparrow
+//
+// Created by Daniel Sperl on 03.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>
+#import "SPEvent.h"
+
+@interface SPEvent (Internal)
+
+- (BOOL)stopsImmediatePropagation;
+- (BOOL)stopsPropagation;
+- (void)setTarget:(SPEventDispatcher*)target;
+- (void)setCurrentTarget:(SPEventDispatcher*)currentTarget;
+
+@end
+
--- /dev/null
+//
+// SPGLTexture.h
+// Sparrow
+//
+// Created by Daniel Sperl on 27.06.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 "SPTexture.h"
+#import "SPMacros.h"
+
+@class SPRectangle;
+
+typedef enum
+{
+ SPTextureFormatRGBA,
+ SPTextureFormatAlpha,
+ SPTextureFormatPvrtcRGB2,
+ SPTextureFormatPvrtcRGBA2,
+ SPTextureFormatPvrtcRGB4,
+ SPTextureFormatPvrtcRGBA4,
+ SPTextureFormat565,
+ SPTextureFormat5551,
+ SPTextureFormat4444
+} SPTextureFormat;
+
+typedef struct
+{
+ SPTextureFormat format;
+ int width;
+ int height;
+ int numMipmaps;
+ BOOL generateMipmaps;
+ BOOL premultipliedAlpha;
+} SPTextureProperties;
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPGLTexture class is a concrete implementation of the abstract class SPTexture,
+ containing a standard 2D OpenGL texture.
+
+ Don't use this class directly, but load textures with the init-methods of SPTexture instead.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPGLTexture : SPTexture
+{
+ @private
+ uint mTextureID;
+ float mWidth;
+ float mHeight;
+ float mScale;
+ BOOL mRepeat;
+ BOOL mPremultipliedAlpha;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a texture with raw pixel data and a set of properties.
+- (id)initWithData:(const void *)imgData properties:(SPTextureProperties)properties;
+
+/// Factory method.
++ (SPGLTexture*)textureWithData:(const void *)imgData properties:(SPTextureProperties)properties;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// Indicates if the texture should repeat like a wallpaper or stretch the outermost pixels.
+@property (nonatomic, assign) BOOL repeat;
+
+/// The scale factor, which influences `width` and `height` properties.
+@property (nonatomic, assign) float scale;
+
+@end
\ No newline at end of file
--- /dev/null
+//
+// SPGLTexture.m
+// Sparrow
+//
+// Created by Daniel Sperl on 27.06.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 "SPGLTexture.h"
+#import "SPMacros.h"
+#import "SPRectangle.h"
+
+#import <OpenGLES/ES1/gl.h>
+#import <OpenGLES/ES1/glext.h>
+
+@implementation SPGLTexture
+
+@synthesize textureID = mTextureID;
+@synthesize repeat = mRepeat;
+@synthesize hasPremultipliedAlpha = mPremultipliedAlpha;
+@synthesize scale = mScale;
+
+- (id)initWithData:(const void*)imgData properties:(SPTextureProperties)properties
+{
+ if (self = [super init])
+ {
+ mWidth = properties.width;
+ mHeight = properties.height;
+ mRepeat = NO;
+ mPremultipliedAlpha = properties.premultipliedAlpha;
+ mScale = 1.0f;
+
+ GLenum glTexType = GL_UNSIGNED_BYTE;
+ GLenum glTexFormat;
+ int bitsPerPixel;
+ BOOL compressed = NO;
+
+ switch (properties.format)
+ {
+ default:
+ case SPTextureFormatRGBA:
+ bitsPerPixel = 8;
+ glTexFormat = GL_RGBA;
+ break;
+ case SPTextureFormatAlpha:
+ bitsPerPixel = 8;
+ glTexFormat = GL_ALPHA;
+ break;
+ case SPTextureFormatPvrtcRGBA2:
+ compressed = YES;
+ bitsPerPixel = 2;
+ glTexFormat = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
+ break;
+ case SPTextureFormatPvrtcRGB2:
+ compressed = YES;
+ bitsPerPixel = 2;
+ glTexFormat = GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
+ break;
+ case SPTextureFormatPvrtcRGBA4:
+ compressed = YES;
+ bitsPerPixel = 4;
+ glTexFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
+ break;
+ case SPTextureFormatPvrtcRGB4:
+ compressed = YES;
+ bitsPerPixel = 4;
+ glTexFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
+ break;
+ case SPTextureFormat565:
+ bitsPerPixel = 16;
+ glTexFormat = GL_RGB;
+ glTexType = GL_UNSIGNED_SHORT_5_6_5;
+ break;
+ case SPTextureFormat5551:
+ bitsPerPixel = 16;
+ glTexFormat = GL_RGBA;
+ glTexType = GL_UNSIGNED_SHORT_5_5_5_1;
+ break;
+ case SPTextureFormat4444:
+ bitsPerPixel = 16;
+ glTexFormat = GL_RGBA;
+ glTexType = GL_UNSIGNED_SHORT_4_4_4_4;
+ break;
+ }
+
+ glGenTextures(1, &mTextureID);
+ glBindTexture(GL_TEXTURE_2D, mTextureID);
+
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mRepeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mRepeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
+
+ if (!compressed)
+ {
+ if (properties.numMipmaps > 0 || properties.generateMipmaps)
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+ else
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+ if (properties.numMipmaps == 0 && properties.generateMipmaps)
+ glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
+
+ int levelWidth = mWidth;
+ int levelHeight = mHeight;
+ unsigned char *levelData = (unsigned char *)imgData;
+
+ for (int level=0; level<=properties.numMipmaps; ++level)
+ {
+ int size = levelWidth * levelHeight * bitsPerPixel / 8;
+ glTexImage2D(GL_TEXTURE_2D, level, glTexFormat, levelWidth, levelHeight,
+ 0, glTexFormat, glTexType, levelData);
+ levelData += size;
+ levelWidth /= 2;
+ levelHeight /= 2;
+ }
+ }
+ else
+ {
+ // 'generateMipmaps' not supported for compressed textures
+
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, properties.numMipmaps == 0 ?
+ GL_LINEAR : GL_LINEAR_MIPMAP_NEAREST);
+
+ int levelWidth = mWidth;
+ int levelHeight = mHeight;
+ unsigned char *levelData = (unsigned char *)imgData;
+
+ for (int level=0; level<=properties.numMipmaps; ++level)
+ {
+ int size = MAX(32, levelWidth * levelHeight * bitsPerPixel / 8);
+ glCompressedTexImage2D(GL_TEXTURE_2D, level, glTexFormat,
+ levelWidth, levelHeight, 0, size, levelData);
+ levelData += size;
+ levelWidth /= 2;
+ levelHeight /= 2;
+ }
+ }
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ }
+ return self;
+}
+
+- (id)init
+{
+ return [self initWithData:NULL properties:(SPTextureProperties){ .width = 32, .height = 32 }];
+}
+
+- (float)width
+{
+ return mWidth / mScale;
+}
+
+- (float)height
+{
+ return mHeight / mScale;
+}
+
+- (void)setRepeat:(BOOL)value
+{
+ mRepeat = value;
+ glBindTexture(GL_TEXTURE_2D, mTextureID);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mRepeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mRepeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
+ glBindTexture(GL_TEXTURE_2D, 0);
+}
+
++ (SPGLTexture*)textureWithData:(const void *)imgData properties:(SPTextureProperties)properties
+{
+ return [[[SPGLTexture alloc] initWithData:imgData properties:properties] autorelease];
+}
+
+- (void)dealloc
+{
+ glDeleteTextures(1, &mTextureID);
+ [super dealloc];
+}
+
+@end
--- /dev/null
+//
+// SPImage.h
+// Sparrow
+//
+// Created by Daniel Sperl on 19.06.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 "SPQuad.h"
+
+@class SPTexture;
+@class SPPoint;
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPImage displays a quad with a texture mapped onto it.
+
+ Sparrow uses the SPTexture class to represent textures. To display a texture, you have to map
+ it on a quad - and that's what SPImage is for.
+
+ As SPImage inherits from SPQuad, you can also give it a color. The resulting color is then the
+ result of the multiplication between the color of the texture and the color of the quad. That way,
+ you can easily tint textures with a certain color. Furthermore, SPImage allows the manipulation of
+ texture coordinates.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPImage : SPQuad
+{
+ @protected
+ SPTexture *mTexture;
+ float mTexCoords[8];
+}
+
+/// --------------------
+/// @name Initialization
+/// --------------------
+
+/// Initialize a quad with a texture mapped onto it. _Designated Initializer_.
+- (id)initWithTexture:(SPTexture*)texture;
+
+/// Initialize a quad with a texture loaded from a file.
+- (id)initWithContentsOfFile:(NSString*)path;
+
+/// Factory method.
++ (SPImage*)imageWithTexture:(SPTexture*)texture;
+
+/// Factory method.
++ (SPImage*)imageWithContentsOfFile:(NSString*)path;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Sets the texture coordinates of a vertex. Coordinates are in the range [0, 1].
+- (void)setTexCoords:(SPPoint*)coords ofVertex:(int)vertexID;
+
+/// Gets the texture coordinates of a vertex.
+- (SPPoint*)texCoordsOfVertex:(int)vertexID;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The texture that is displayed on the quad.
+@property (nonatomic, retain) SPTexture *texture;
+
+@end
--- /dev/null
+//
+// SPImage.m
+// Sparrow
+//
+// Created by Daniel Sperl on 19.06.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 "SPImage.h"
+#import "SPPoint.h"
+#import "SPTexture.h"
+#import "SPGLTexture.h"
+
+@implementation SPImage
+
+@synthesize texture = mTexture;
+
+- (id)initWithTexture:(SPTexture*)texture;
+{
+ if (!texture) [NSException raise:SP_EXC_INVALID_OPERATION format:@"texture cannot be nil!"];
+
+ if (self = [super initWithWidth:texture.width height:texture.height])
+ {
+ self.texture = texture;
+ mTexCoords[0] = 0.0f; mTexCoords[1] = 0.0f;
+ mTexCoords[2] = 1.0f; mTexCoords[3] = 0.0f;
+ mTexCoords[4] = 0.0f; mTexCoords[5] = 1.0f;
+ mTexCoords[6] = 1.0f; mTexCoords[7] = 1.0f;
+ }
+ return self;
+}
+
+- (id)initWithContentsOfFile:(NSString*)path
+{
+ return [self initWithTexture:[SPTexture textureWithContentsOfFile:path]];
+}
+
+- (id)initWithWidth:(float)width height:(float)height
+{
+ SPTextureProperties properties = { .width = width, .height = height };
+ return [self initWithTexture:[SPGLTexture textureWithData:NULL properties:properties]];
+}
+
+- (void)setTexCoords:(SPPoint*)coords ofVertex:(int)vertexID
+{
+ if (vertexID < 0 || vertexID > 3)
+ [NSException raise:SP_EXC_INDEX_OUT_OF_BOUNDS format:@"invalid vertex id"];
+
+ mTexCoords[2*vertexID ] = coords.x;
+ mTexCoords[2*vertexID+1] = coords.y;
+}
+
+- (SPPoint*)texCoordsOfVertex:(int)vertexID
+{
+ if (vertexID < 0 || vertexID > 3)
+ [NSException raise:SP_EXC_INDEX_OUT_OF_BOUNDS format:@"invalid vertex id"];
+
+ return [SPPoint pointWithX:mTexCoords[vertexID*2] y:mTexCoords[vertexID*2+1]];
+}
+
++ (SPImage*)imageWithTexture:(SPTexture*)texture
+{
+ return [[[SPImage alloc] initWithTexture:texture] autorelease];
+}
+
++ (SPImage*)imageWithContentsOfFile:(NSString*)path
+{
+ return [[[SPImage alloc] initWithContentsOfFile:path] autorelease];
+}
+
+- (void)dealloc
+{
+ [mTexture release];
+ [super dealloc];
+}
+
+@end
\ No newline at end of file
--- /dev/null
+//
+// SPJuggler.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>
+#import "SPAnimatable.h"
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPJuggler takes objects that implement SPAnimatable (e.g. `SPTween`s) and executes them.
+
+ A juggler is a simple object. It does no more than saving a list of objects implementing
+ `SPAnimatable` and advancing their time if he is told to do so (by calling its own `advanceTime:`
+ method). When an animation is completed, it throws it away.
+
+ There is a default juggler in every stage. You can access it by calling
+
+ SPJuggler *juggler = self.stage.juggler;
+
+ in any object that has access to the stage. You can, however, create juggler objects yourself, too.
+ That way, you can group your game into logical components that handle their animations independently.
+
+ A cool feature of the juggler is to delay method calls. Say you want to remove an object from its
+ parent 2 seconds from now. Call:
+
+ [[juggler delayInvocationAtTarget:object byTime:2.0] removeFromParent];
+
+ This line of code will execute the following method 2 seconds in the future:
+
+ [object removeFromParent];
+
+ The Sparrow blog contains three extensive articles about the juggler:
+
+ * http://www.sparrow-framework.org/2010/08/tweens-jugglers-animating-your-stage/
+ * http://www.sparrow-framework.org/2010/09/tweens-jugglers-an-in-depth-look-at-the-juggler/
+ * http://www.sparrow-framework.org/2010/10/tweens-jugglers-unleashed/
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPJuggler : NSObject <SPAnimatable>
+{
+ @private
+ NSMutableSet *mObjects;
+ double mElapsedTime;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Factory method.
++ (SPJuggler *)juggler;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Adds an object to the juggler.
+- (void)addObject:(id<SPAnimatable>)object;
+
+/// Removes an object from the juggler.
+- (void)removeObject:(id<SPAnimatable>)object;
+
+/// Removes all objects at once.
+- (void)removeAllObjects;
+
+/// Removes all objects of type `SPTween` that have a certain target.
+- (void)removeTweensWithTarget:(id)object;
+
+/// Delays the execution of a certain method. Returns a proxy object on which to call the method
+/// instead. Execution will be delayed until `time` has passed.
+- (id)delayInvocationAtTarget:(id)target byTime:(double)time;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The total life time of the juggler.
+@property (nonatomic, readonly) double elapsedTime;
+
+@end
--- /dev/null
+//
+// SPJuggler.m
+// 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 "SPJuggler.h"
+#import "SPAnimatable.h"
+#import "SPDelayedInvocation.h"
+
+@implementation SPJuggler
+
+@synthesize elapsedTime = mElapsedTime;
+
+- (id)init
+{
+ if (self = [super init])
+ {
+ mObjects = [[NSMutableSet alloc] init];
+ mElapsedTime = 0.0;
+ }
+ return self;
+}
+
+- (BOOL)isComplete
+{
+ return NO;
+}
+
+- (void)advanceTime:(double)seconds
+{
+ mElapsedTime += seconds;
+
+ // we need work with a copy, since user-code could modify the collection during the enumeration
+ for (id<SPAnimatable> object in [mObjects allObjects])
+ {
+ [object advanceTime:seconds];
+ if (object.isComplete) [self removeObject:object];
+ }
+}
+
+- (void)addObject:(id<SPAnimatable>)object
+{
+ if (object)
+ [mObjects addObject:object];
+}
+
+- (void)removeObject:(id<SPAnimatable>)object
+{
+ [mObjects removeObject:object];
+}
+
+- (void)removeAllObjects;
+{
+ [mObjects removeAllObjects];
+}
+
+- (void)removeTweensWithTarget:(id)object
+{
+ SEL targetSel = @selector(target);
+ NSMutableSet *remainingObjects = [[NSMutableSet alloc] init];
+
+ for (id currentObject in mObjects)
+ {
+ if (![currentObject respondsToSelector:targetSel] || ![[currentObject target] isEqual:object])
+ [remainingObjects addObject:currentObject];
+ }
+
+ [mObjects release];
+ mObjects = remainingObjects;
+}
+
+- (id)delayInvocationAtTarget:(id)target byTime:(double)time
+{
+ SPDelayedInvocation *delayedInvoc = [SPDelayedInvocation invocationWithTarget:target delay:time];
+ [self addObject:delayedInvoc];
+ return delayedInvoc;
+}
+
++ (SPJuggler *)juggler
+{
+ return [[[SPJuggler alloc] init] autorelease];
+}
+
+- (void)dealloc
+{
+ [mObjects release];
+ [super dealloc];
+}
+
+@end
--- /dev/null
+//
+// SPMacros.h
+// Sparrow
+//
+// Created by Daniel Sperl on 15.03.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 <math.h>
+
+// constants
+
+#define PI 3.14159265359f
+#define PI_HALF 1.57079632679f
+#define TWO_PI 6.28318530718f
+
+#define SP_FLOAT_EPSILON 0.0001f
+
+#define SP_WHITE 0xFFFFFF
+#define SP_BLACK 0x000000
+
+#define SP_NOT_FOUND -1
+#define SP_MAX_DISPLAY_TREE_DEPTH 16
+
+// exceptions
+
+#define SP_EXC_ABSTRACT_CLASS @"AbstractClass"
+#define SP_EXC_ABSTRACT_METHOD @"AbstractMethod"
+#define SP_EXC_NOT_RELATED @"NotRelated"
+#define SP_EXC_INDEX_OUT_OF_BOUNDS @"IndexOutOfBounds"
+#define SP_EXC_INVALID_OPERATION @"InvalidOperation"
+#define SP_EXC_FILE_NOT_FOUND @"FileNotFound"
+#define SP_EXC_FILE_INVALID @"FileInvalid"
+
+// macros
+
+#define SP_CREATE_POOL(pool) NSAutoreleasePool *(pool) = [[NSAutoreleasePool alloc] init];
+#define SP_RELEASE_POOL(pool) [(pool) release];
+
+#define SP_R2D(rad) ((rad) / PI * 180.0f)
+#define SP_D2R(deg) ((deg) / 180.0f * PI)
+
+#define SP_COLOR_PART_ALPHA(color) (((color) >> 24) & 0xff)
+#define SP_COLOR_PART_RED(color) (((color) >> 16) & 0xff)
+#define SP_COLOR_PART_GREEN(color) (((color) >> 8) & 0xff)
+#define SP_COLOR_PART_BLUE(color) ( (color) & 0xff)
+
+#define SP_COLOR(r, g, b) (((r) << 16) | ((g) << 8) | (b))
+
+#define SP_IS_FLOAT_EQUAL(f1, f2) (fabsf((f1)-(f2)) < SP_FLOAT_EPSILON)
+
+
--- /dev/null
+//
+// SPMatrix.h
+// Sparrow
+//
+// Created by Daniel Sperl on 26.03.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 "SPPoolObject.h"
+
+@class SPPoint;
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPMatrix class describes an affine, 2D transformation Matrix. It provides methods to
+ manipulate the matrix in convenient ways, and can be used to transform points.
+
+ The matrix has the following form:
+
+ |a c tx|
+ |b d ty|
+ |0 0 1|
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPMatrix : SPPoolObject <NSCopying>
+{
+ @private
+ float mA, mB, mC, mD;
+ float mTx, mTy;
+}
+
+/// -----------------
+/// @name Intializers
+/// -----------------
+
+/// Initializes a matrix with the specified components. _Designated Initializer_.
+- (id)initWithA:(float)a b:(float)b c:(float)c d:(float)d tx:(float)tx ty:(float)ty;
+
+/// Initializes an identity matrix.
+- (id)init;
+
+/// Factory method.
++ (SPMatrix*)matrixWithA:(float)a b:(float)b c:(float)c d:(float)d tx:(float)tx ty:(float)ty;
+
+/// Factory method.
++ (SPMatrix*)matrixWithIdentity;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Compares to matrices.
+- (BOOL)isEqual:(id)other;
+
+/// Concatenates a matrix with the current matrix, combining the geometric effects of the two.
+- (void)concatMatrix:(SPMatrix*)matrix;
+
+/// Translates the matrix along the x and y axes.
+- (void)translateXBy:(float)dx yBy:(float)dy;
+
+/// Applies a scaling transformation to the matrix.
+- (void)scaleXBy:(float)sx yBy:(float)sy;
+
+/// Applies a uniform scaling transformation to the matrix.
+- (void)scaleBy:(float)scale;
+
+/// Applies a rotation on the matrix (angle in RAD).
+- (void)rotateBy:(float)angle;
+
+/// Sets each matrix property to a value that causes a null transformation.
+- (void)identity;
+
+/// Performs the opposite transformation of the matrix.
+- (void)invert;
+
+/// Applies the geometric transformation represented by the matrix to the specified point.
+- (SPPoint*)transformPoint:(SPPoint*)point;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// Matrix component.
+@property (nonatomic, assign) float a;
+@property (nonatomic, assign) float b;
+@property (nonatomic, assign) float c;
+@property (nonatomic, assign) float d;
+@property (nonatomic, assign) float tx;
+@property (nonatomic, assign) float ty;
+
+/// The determinant of the matrix.
+@property (nonatomic, readonly) float determinant;
+
+@end
--- /dev/null
+//
+// SPMatrix.m
+// Sparrow
+//
+// Created by Daniel Sperl on 26.03.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 "SPMatrix.h"
+#import "SPPoint.h"
+#import "SPMacros.h"
+
+#define U 0
+#define V 0
+#define W 1
+
+@implementation SPMatrix
+
+@synthesize a=mA, b=mB, c=mC, d=mD, tx=mTx, ty=mTy;
+
+// --- c functions ---
+
+static void setValues(SPMatrix *matrix, float a, float b, float c, float d, float tx, float ty)
+{
+ matrix->mA = a;
+ matrix->mB = b;
+ matrix->mC = c;
+ matrix->mD = d;
+ matrix->mTx = tx;
+ matrix->mTy = ty;
+}
+
+// ---
+
+- (id)initWithA:(float)a b:(float)b c:(float)c d:(float)d tx:(float)tx ty:(float)ty
+{
+ if (self = [super init])
+ {
+ mA = a; mB = b; mC = c; mD = d;
+ mTx = tx; mTy = ty;
+ }
+ return self;
+}
+
+- (id)init
+{
+ return [self initWithA:1 b:0 c:0 d:1 tx:0 ty:0];
+}
+
+- (void)setValuesA:(float)a b:(float)b c:(float)c d:(float)d tx:(float)tx ty:(float)ty
+{
+ mA = a; mB = b; mC = c; mD = d;
+ mTx = tx; mTy = ty;
+}
+
+- (float)determinant
+{
+ return mA * mD - mC * mB;
+}
+
+- (void)concatMatrix:(SPMatrix*)matrix
+{
+ setValues(self, matrix->mA * mA + matrix->mC * mB,
+ matrix->mB * mA + matrix->mD * mB,
+ matrix->mA * mC + matrix->mC * mD,
+ matrix->mB * mC + matrix->mD * mD,
+ matrix->mA * mTx + matrix->mC * mTy + matrix->mTx * W,
+ matrix->mB * mTx + matrix->mD * mTy + matrix->mTy * W);
+}
+
+- (void)translateXBy:(float)dx yBy:(float)dy
+{
+ mTx += dx;
+ mTy += dy;
+}
+
+- (void)scaleXBy:(float)sx yBy:(float)sy
+{
+ mA *= sx;
+ mB *= sy;
+ mC *= sx;
+ mD *= sy;
+ mTx *= sx;
+ mTy *= sy;
+}
+
+- (void)scaleBy:(float)scale
+{
+ [self scaleXBy:scale yBy:scale];
+}
+
+- (void)rotateBy:(float)angle
+{
+ SPMatrix *rotMatrix = [[SPMatrix alloc] initWithA:cosf(angle) b:sinf(angle)
+ c:-sinf(angle) d:cosf(angle) tx:0 ty:0];
+ [self concatMatrix:rotMatrix];
+ [rotMatrix release];
+}
+
+- (void)identity
+{
+ setValues(self, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
+}
+
+- (SPPoint*)transformPoint:(SPPoint*)point
+{
+ return [SPPoint pointWithX:mA*point.x + mC*point.y + mTx
+ y:mB*point.x + mD*point.y + mTy];
+}
+
+- (void)invert
+{
+ float det = self.determinant;
+ setValues(self, mD/det, -mB/det, -mC/det, mA/det, (mC*mTy-mD*mTx)/det, (mB*mTx-mA*mTy)/det);
+}
+
+- (BOOL)isEqual:(id)other
+{
+ if (other == self) return YES;
+ else if (!other || ![other isKindOfClass:[self class]]) return NO;
+ else
+ {
+ SPMatrix *matrix = (SPMatrix*)other;
+ return SP_IS_FLOAT_EQUAL(mA, matrix->mA) && SP_IS_FLOAT_EQUAL(mB, matrix->mB) &&
+ SP_IS_FLOAT_EQUAL(mC, matrix->mC) && SP_IS_FLOAT_EQUAL(mD, matrix->mD) &&
+ SP_IS_FLOAT_EQUAL(mTx, matrix->mTx) && SP_IS_FLOAT_EQUAL(mTy, matrix->mTy);
+ }
+}
+
+- (NSString *)description
+{
+ return [NSString stringWithFormat:@"(a=%f, b=%f, c=%f, d=%f, tx=%f, ty=%f)",
+ mA, mB, mC, mD, mTx, mTy];
+}
+
++ (SPMatrix*)matrixWithA:(float)a b:(float)b c:(float)c d:(float)d tx:(float)tx ty:(float)ty
+{
+ return [[[SPMatrix alloc] initWithA:a b:b c:c d:d tx:tx ty:ty] autorelease];
+}
+
++ (SPMatrix*)matrixWithIdentity
+{
+ return [[[SPMatrix alloc] init] autorelease];
+}
+
+#pragma mark NSCopying
+
+- (id)copyWithZone:(NSZone*)zone;
+{
+ return [[[self class] allocWithZone:zone] initWithA:mA b:mB c:mC d:mD
+ tx:mTx ty:mTy];
+}
+
+#pragma mark SPPoolObject
+
++ (SPPoolInfo *)poolInfo
+{
+ static SPPoolInfo poolInfo;
+ return &poolInfo;
+}
+
+@end
--- /dev/null
+//
+// SPMovieClip.h
+// Sparrow
+//
+// Created by Daniel Sperl on 01.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 "SPSprite.h"
+#import "SPTexture.h"
+#import "SPAnimatable.h"
+#import "SPImage.h"
+#import "SPSoundChannel.h"
+
+#define SP_EVENT_TYPE_MOVIE_COMPLETED @"movieCompleted"
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPMovieClip is a simple way to display an animation depicted by a number of textures.
+
+ You can add the frames one by one or pass them all at once (in an array) at initialization time.
+ The movie clip will have the width and height of the first frame.
+
+ At initialization, you can specify the desired framerate. You can, however, manually give each
+ frame a custom duration. You can also play a sound whenever a certain frame appears.
+
+ The methods `play` and `pause` control playback of the movie. You will receive an event of type
+ `SP_EVENT_TYPE_MOVIE_COMPLETED` when the movie finished playback (except when you enabled looping).
+
+ As any animated object, a movie clip has to be added to a juggler (or have its `advanceTime:`
+ method called regularly) to run.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPMovieClip : SPImage <SPAnimatable>
+{
+ @private
+ NSMutableArray *mFrames;
+ NSMutableArray *mSounds;
+ NSMutableArray *mFrameDurations;
+
+ double mDefaultFrameDuration;
+ double mTotalDuration;
+ double mElapsedTime;
+ BOOL mLoop;
+ BOOL mPlaying;
+ int mCurrentFrame;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a movie with the first frame and the default number of frames per second. _Designated initializer_.
+- (id)initWithFrame:(SPTexture *)texture fps:(float)fps; // designated initializer
+
+/// Initializes a movie with an array of textures and the default number of frames per second.
+- (id)initWithFrames:(NSArray *)textures fps:(float)fps;
+
+/// Factory method.
++ (SPMovieClip *)movieWithFrame:(SPTexture *)texture fps:(float)fps;
+
+/// Factory method.
++ (SPMovieClip *)movieWithFrames:(NSArray *)textures fps:(float)fps;
+
+/// --------------------------------
+/// @name Frame Manipulation Methods
+/// --------------------------------
+
+/// Adds a frame with the default duration.
+- (int)addFrame:(SPTexture *)texture;
+
+/// Adds a frame with a specified duration.
+- (int)addFrame:(SPTexture *)texture withDuration:(double)duration;
+
+/// Inserts a frame at the index specified.
+- (void)insertFrame:(SPTexture *)texture atIndex:(int)frameID;
+
+/// Removes the frame at the index specified.
+- (void)removeFrameAtIndex:(int)frameID;
+
+/// Sets the texture of a certain frame.
+- (void)setFrame:(SPTexture *)texture atIndex:(int)frameID;
+
+/// Sets the sound that will be played back when a certain frame is active.
+- (void)setSound:(SPSoundChannel *)sound atIndex:(int)frameID;
+
+/// Sets the duration of a certain frame in seconds.
+- (void)setDuration:(double)duration atIndex:(int)frameID;
+
+/// Returns the texture of a frame at a certain index.
+- (SPTexture *)frameAtIndex:(int)frameID;
+
+/// Returns the sound of a frame at a certain index.
+- (SPSoundChannel *)soundAtIndex:(int)frameID;
+
+/// Returns the duration (in seconds) of a frame at a certain index.
+- (double)durationAtIndex:(int)frameID;
+
+/// ----------------------
+/// @name Playback Methods
+/// ----------------------
+
+/// Start playback. Beware that the clip has to be added to a juggler, too!
+- (void)play;
+
+/// Pause playback.
+- (void)pause;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The number of frames of the clip.
+@property (nonatomic, readonly) int numFrames;
+
+/// The accumulated duration of all frames.
+@property (nonatomic, readonly) double duration;
+
+/// Indicates if the movie is currently playing.
+@property (nonatomic, readonly) BOOL isPlaying;
+
+/// Indicates if the movie is looping.
+@property (nonatomic, assign) BOOL loop;
+
+/// The ID of the frame that is currently displayed.
+@property (nonatomic, assign) int currentFrame;
+
+/// The default frames per second. Used when you add a frame without specifying a duration.
+@property (nonatomic, assign) float fps;
+
+@end
--- /dev/null
+//
+// SPMovieClip.m
+// Sparrow
+//
+// Created by Daniel Sperl on 01.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 "SPMovieClip.h"
+#import "SPMacros.h"
+
+// --- private interface ---------------------------------------------------------------------------
+
+@interface SPMovieClip ()
+
+- (void)updateCurrentFrame;
+- (void)playCurrentSound;
+- (void)checkIndex:(int)frameID;
+
+@end
+
+
+// --- class implementation ------------------------------------------------------------------------
+
+@implementation SPMovieClip
+
+@synthesize loop = mLoop;
+@synthesize isPlaying = mPlaying;
+@synthesize currentFrame = mCurrentFrame;
+@synthesize duration = mTotalDuration;
+
+- (id)initWithFrame:(SPTexture *)texture fps:(float)fps
+{
+ if (self = [super initWithTexture:texture])
+ {
+ self.fps = fps;
+ mLoop = YES;
+ mPlaying = YES;
+ mTotalDuration = 0.0;
+ mElapsedTime = 0.0;
+ mCurrentFrame = 0;
+ mFrames = [[NSMutableArray alloc] init];
+ mSounds = [[NSMutableArray alloc] init];
+ mFrameDurations = [[NSMutableArray alloc] init];
+ [self addFrame:texture];
+ }
+ return self;
+}
+
+- (id)initWithFrames:(NSArray *)textures fps:(float)fps
+{
+ if (textures.count == 0)
+ [NSException raise:SP_EXC_INVALID_OPERATION format:@"empty texture array"];
+
+ [self initWithFrame:[textures objectAtIndex:0] fps:fps];
+
+ if (textures.count > 1)
+ for (int i=1; i<textures.count; ++i)
+ [self addFrame:[textures objectAtIndex:i]];
+
+ return self;
+}
+
+- (id)initWithTexture:(SPTexture *)texture
+{
+ return [self initWithFrame:texture fps:10];
+}
+
+- (void)dealloc
+{
+ [mFrames release];
+ [mSounds release];
+ [mFrameDurations release];
+ [super dealloc];
+}
+
+- (int)addFrame:(SPTexture *)texture
+{
+ return [self addFrame:texture withDuration:mDefaultFrameDuration];
+}
+
+- (int)addFrame:(SPTexture *)texture withDuration:(double)duration
+{
+ mTotalDuration += duration;
+ [mFrames addObject:texture];
+ [mFrameDurations addObject:[NSNumber numberWithDouble:duration]];
+ [mSounds addObject:[NSNull null]];
+ return mFrames.count - 1;
+}
+
+- (void)insertFrame:(SPTexture *)texture atIndex:(int)frameID
+{
+ [self checkIndex:frameID];
+ [mFrames insertObject:texture atIndex:frameID];
+ [mSounds insertObject:[NSNull null] atIndex:frameID];
+ [mFrameDurations insertObject:[NSNumber numberWithDouble:mDefaultFrameDuration] atIndex:frameID];
+ mTotalDuration += mDefaultFrameDuration;
+}
+
+- (void)removeFrameAtIndex:(int)frameID
+{
+ [self checkIndex:frameID];
+ [mFrames removeObjectAtIndex:frameID];
+ [mSounds removeObjectAtIndex:frameID];
+ mTotalDuration -= [self durationAtIndex:frameID];
+ [mFrameDurations removeObjectAtIndex:frameID];
+}
+
+- (void)setFrame:(SPTexture *)texture atIndex:(int)frameID
+{
+ [self checkIndex:frameID];
+ [mFrames replaceObjectAtIndex:frameID withObject:texture];
+}
+
+- (void)setSound:(SPSoundChannel *)sound atIndex:(int)frameID
+{
+ [self checkIndex:frameID];
+ id soundObject = sound;
+ if (!sound) soundObject = [NSNull null];
+ [mSounds replaceObjectAtIndex:frameID withObject:soundObject];
+}
+
+- (void)setDuration:(double)duration atIndex:(int)frameID
+{
+ [self checkIndex:frameID];
+ mTotalDuration -= [self durationAtIndex:frameID];
+ [mFrameDurations replaceObjectAtIndex:frameID withObject:[NSNumber numberWithDouble:duration]];
+ mTotalDuration += duration;
+}
+
+- (SPTexture *)frameAtIndex:(int)frameID
+{
+ [self checkIndex:frameID];
+ return [mFrames objectAtIndex:frameID];
+}
+
+- (SPSoundChannel *)soundAtIndex:(int)frameID
+{
+ [self checkIndex:frameID];
+
+ id sound = [mSounds objectAtIndex:frameID];
+ if ([NSNull class] != [sound class]) return sound;
+ else return nil;
+}
+
+- (double)durationAtIndex:(int)frameID
+{
+ [self checkIndex:frameID];
+ return [[mFrameDurations objectAtIndex:frameID] doubleValue];
+}
+
+- (void)setFps:(float)fps
+{
+ float newFrameDuration = (fps == 0.0f ? INT_MAX : 1.0 / fps);
+ float acceleration = newFrameDuration / mDefaultFrameDuration;
+ mElapsedTime *= acceleration;
+ mDefaultFrameDuration = newFrameDuration;
+
+ for (int i=0; i<self.numFrames; ++i)
+ [self setDuration:[self durationAtIndex:i] * acceleration atIndex:i];
+}
+
+- (float)fps
+{
+ return (float)(1.0 / mDefaultFrameDuration);
+}
+
+- (int)numFrames
+{
+ return mFrames.count;
+}
+
+- (void)play
+{
+ mPlaying = YES;
+}
+
+- (void)pause
+{
+ mPlaying = NO;
+}
+
+- (void)updateCurrentFrame
+{
+ self.texture = [mFrames objectAtIndex:mCurrentFrame];
+}
+
+- (void)playCurrentSound
+{
+ id sound = [mSounds objectAtIndex:mCurrentFrame];
+ if ([NSNull class] != [sound class])
+ [sound play];
+}
+
+- (void)setCurrentFrame:(int)frameID
+{
+ mCurrentFrame = frameID;
+ mElapsedTime = 0.0;
+
+ for (int i=0; i<frameID; ++i)
+ mElapsedTime += [[mFrameDurations objectAtIndex:i] doubleValue];
+
+ [self updateCurrentFrame];
+}
+
+- (void)checkIndex:(int)frameID
+{
+ if (frameID < 0 || frameID > mFrames.count)
+ [NSException raise:SP_EXC_INDEX_OUT_OF_BOUNDS format:@"invalid frame index"];
+}
+
++ (SPMovieClip *)movieWithFrame:(SPTexture *)texture fps:(float)fps
+{
+ return [[[SPMovieClip alloc] initWithFrame:texture fps:fps] autorelease];
+}
+
++ (SPMovieClip *)movieWithFrames:(NSArray *)textures fps:(float)fps
+{
+ return [[[SPMovieClip alloc] initWithFrames:textures fps:fps] autorelease];
+}
+
+#pragma mark SPAnimatable
+
+- (void)advanceTime:(double)seconds
+{
+ if (!mPlaying || (!mLoop && mElapsedTime == mTotalDuration)) return;
+
+ double previousElapsedTime = mElapsedTime;
+ mElapsedTime += seconds;
+
+ if (mLoop)
+ {
+ while (mElapsedTime > mTotalDuration)
+ mElapsedTime -= mTotalDuration;
+ }
+ else
+ {
+ mElapsedTime = MIN(mTotalDuration, mElapsedTime);
+ }
+
+ double durationSum = 0.0;
+ int i = 0;
+
+ for (NSNumber *frameDuration in mFrameDurations)
+ {
+ double fd = [frameDuration doubleValue];
+ if (durationSum + fd >= mElapsedTime)
+ {
+ if (mCurrentFrame != i)
+ {
+ mCurrentFrame = i;
+ [self updateCurrentFrame];
+ [self playCurrentSound];
+ }
+ break;
+ }
+
+ ++i;
+ durationSum += fd;
+ }
+
+ if (!mLoop && previousElapsedTime < mTotalDuration && mElapsedTime >= mTotalDuration)
+ [self dispatchEvent:[SPEvent eventWithType:SP_EVENT_TYPE_MOVIE_COMPLETED]];
+}
+
+- (BOOL)isComplete
+{
+ return NO;
+}
+
+@end
\ No newline at end of file
--- /dev/null
+//
+// SPNSAdditions.h
+// Sparrow
+//
+// Created by Daniel Sperl on 13.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>
+
+/** Sparrow extensions for the NSInvocation class. */
+@interface NSInvocation (SPNSExtensions)
+
+/// Creates an invocation with a specified target and selector.
++ (NSInvocation *)invocationWithTarget:(id)target selector:(SEL)selector;
+
+@end
+
+/** Sparrow extensions for the NSString class. */
+@interface NSString (SPNSExtensions)
+
+/// Creates a string by appending a suffix to a filename in front of its extension.
+- (NSString *)stringByAppendingSuffixToFilename:(NSString *)suffix;
+
+@end
+
+/** Sparrow extensions for the NSBundle class. */
+@interface NSBundle (SPNSExtensions)
+
+/// Determines if a resource with a certain scale factor suffix (@2x) exists.
+///
+/// @return Returns the path to the scaled resource if it exists; otherwise, the path to the
+// unscaled resource - or nil if that does not exist, either.
+- (NSString *)pathForResource:(NSString *)name withScaleFactor:(float)factor;
+
+@end
\ No newline at end of file
--- /dev/null
+//
+// SPNSAdditions.m
+// Sparrow
+//
+// Created by Daniel Sperl on 13.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 "SPNSExtensions.h"
+
+@implementation NSInvocation (SPNSExtensions)
+
++ (NSInvocation*)invocationWithTarget:(id)target selector:(SEL)selector
+{
+ NSMethodSignature *signature = [target methodSignatureForSelector:selector];
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
+ invocation.selector = selector;
+ invocation.target = target;
+ return invocation;
+}
+
+@end
+
+@implementation NSString (SPNSExtensions)
+
+- (NSString *)stringByAppendingSuffixToFilename:(NSString *)suffix
+{
+ return [[self stringByDeletingPathExtension] stringByAppendingFormat:@"%@.%@",
+ suffix, [self pathExtension]];
+}
+
+@end
+
+@implementation NSBundle (SPNSExtensions)
+
+- (NSString *)pathForResource:(NSString *)name withScaleFactor:(float)factor
+{
+ if (factor != 1.0f)
+ {
+ NSString *suffix = [NSString stringWithFormat:@"@%@x", [NSNumber numberWithFloat:factor]];
+ NSString *path = [self pathForResource:[name stringByAppendingSuffixToFilename:suffix] ofType:nil];
+ if (path) return path;
+ }
+ return [self pathForResource:name ofType:nil];
+}
+
+@end
\ No newline at end of file
--- /dev/null
+//
+// SPPoint.h
+// Sparrow
+//
+// Created by Daniel Sperl on 23.03.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 "SPPoolObject.h"
+
+/** The SPPoint class describes a two dimensional point or vector. */
+
+@interface SPPoint : SPPoolObject <NSCopying>
+{
+ @private
+ float mX;
+ float mY;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a point with its x and y components. _Designated Initializer_.
+- (id)initWithX:(float)x y:(float)y;
+
+/// Initializes a point with the distance and angle in respect to the origin.
+- (id)initWithPolarLength:(float)length angle:(float)angle;
+
+/// Factory method.
++ (SPPoint *)pointWithPolarLength:(float)length angle:(float)angle;
+
+/// Factory method.
++ (SPPoint *)pointWithX:(float)x y:(float)y;
+
+/// Factory method.
++ (SPPoint *)point;
+
+/// -------------
+/// @name Methods
+// --------------
+
+/// Adds a point to the current point and returns the resulting point.
+- (SPPoint *)addPoint:(SPPoint *)point;
+
+/// Substracts a point from the current point and returns the resulting point.
+- (SPPoint *)subtractPoint:(SPPoint *)point;
+
+/// Scales the point by a certain factor and returns the resulting point.
+- (SPPoint *)scaleBy:(float)scalar;
+
+/// Scales the line segment between the origin and the current point to one.
+- (SPPoint *)normalize;
+
+/// Compares two points.
+- (BOOL)isEqual:(id)other;
+
+/// Calculates the distance between two points.
++ (float)distanceFromPoint:(SPPoint *)p1 toPoint:(SPPoint *)p2;
+
+/// Determines a point between two specified points. `ratio = 0 -> p1, ratio = 1 -> p2`
++ (SPPoint *)interpolateFromPoint:(SPPoint *)p1 toPoint:(SPPoint *)p2 ratio:(float)ratio;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// Point component.
+@property (nonatomic, assign) float x;
+@property (nonatomic, assign) float y;
+
+/// The distance to the origin (or the length of the vector).
+@property (readonly) float length;
+
+/// The angle between the origin and the point (in RAD).
+@property (readonly) float angle;
+
+@end
--- /dev/null
+//
+// SPPoint.m
+// Sparrow
+//
+// Created by Daniel Sperl on 23.03.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 "SPPoint.h"
+#import "SPMacros.h"
+#import <math.h>
+
+// --- class implementation ------------------------------------------------------------------------
+
+#define SQ(x) ((x)*(x))
+
+@implementation SPPoint
+
+@synthesize x = mX;
+@synthesize y = mY;
+
+// designated initializer
+- (id)initWithX:(float)x y:(float)y
+{
+ if (self = [super init])
+ {
+ mX = x;
+ mY = y;
+ }
+ return self;
+}
+
+- (id)initWithPolarLength:(float)length angle:(float)angle
+{
+ return [self initWithX:cosf(angle)*length y:sinf(angle)*length];
+}
+
+- (id)init
+{
+ return [self initWithX:0.0f y:0.0f];
+}
+
+- (float)length
+{
+ return sqrtf(SQ(mX) + SQ(mY));
+}
+
+- (float)angle
+{
+ return atan2f(mY, mX);
+}
+
+- (SPPoint*)addPoint:(SPPoint*)point
+{
+ SPPoint *result = [[SPPoint alloc] initWithX:mX+point->mX y:mY+point->mY];
+ return [result autorelease];
+}
+
+- (SPPoint*)subtractPoint:(SPPoint*)point
+{
+ SPPoint *result = [[SPPoint alloc] initWithX:mX-point->mX y:mY-point->mY];
+ return [result autorelease];
+}
+
+- (SPPoint *)scaleBy:(float)scalar
+{
+ SPPoint *result = [[SPPoint alloc] initWithX:mX * scalar y:mY * scalar];
+ return [result autorelease];
+}
+
+- (SPPoint*)normalize
+{
+ if (mX == 0 && mY == 0)
+ [NSException raise:SP_EXC_INVALID_OPERATION format:@"Cannot normalize point in the origin"];
+
+ float inverseLength = 1.0f / self.length;
+ SPPoint *result = [[SPPoint alloc] initWithX:mX * inverseLength y:mY * inverseLength];
+ return [result autorelease];
+}
+
+- (BOOL)isEqual:(id)other
+{
+ if (other == self) return YES;
+ else if (!other || ![other isKindOfClass:[self class]]) return NO;
+ else
+ {
+ SPPoint *point = (SPPoint*)other;
+ return SP_IS_FLOAT_EQUAL(mX, point->mX) && SP_IS_FLOAT_EQUAL(mY, point->mY);
+ }
+}
+
+- (NSString *)description
+{
+ return [NSString stringWithFormat:@"(x=%f, y=%f)", mX, mY];
+}
+
++ (float)distanceFromPoint:(SPPoint*)p1 toPoint:(SPPoint*)p2
+{
+ return sqrtf(SQ(p2->mX - p1->mX) + SQ(p2->mY - p1->mY));
+}
+
++ (SPPoint *)interpolateFromPoint:(SPPoint *)p1 toPoint:(SPPoint *)p2 ratio:(float)ratio
+{
+ float invRatio = 1.0f - ratio;
+ return [SPPoint pointWithX:invRatio * p1->mX + ratio * p2->mX
+ y:invRatio * p1->mY + ratio * p2->mY];
+}
+
++ (SPPoint *)pointWithPolarLength:(float)length angle:(float)angle
+{
+ return [[[SPPoint alloc] initWithPolarLength:length angle:angle] autorelease];
+}
+
++ (SPPoint *)pointWithX:(float)x y:(float)y
+{
+ return [[[SPPoint alloc] initWithX:x y:y] autorelease];
+}
+
++ (SPPoint*)point
+{
+ return [[[SPPoint alloc] init] autorelease];
+}
+
+#pragma mark NSCopying
+
+- (id)copyWithZone:(NSZone*)zone;
+{
+ return [[[self class] allocWithZone:zone] initWithX:mX y:mY];
+}
+
+#pragma mark SPPoolObject
+
++ (SPPoolInfo *)poolInfo
+{
+ static SPPoolInfo poolInfo;
+ return &poolInfo;
+}
+
+@end
--- /dev/null
+//
+// SPPoolObject.h
+// Sparrow
+//
+// Created by Daniel Sperl on 17.09.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>
+
+@class SPPoolObject;
+
+typedef struct
+{
+ Class poolClass;
+ SPPoolObject *lastElement;
+} SPPoolInfo;
+
+#ifndef DISABLE_MEMORY_POOLING
+
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPPoolObject class is an alternative to the base class `NSObject` that manages a pool of
+ objects.
+
+ Subclasses of SPPoolObject do not deallocate object instances when the retain counter reaches
+ zero. Instead, the objects stay in memory and will be re-used when a new instance of the object
+ is requested. That way, object initialization is accelerated. You can release the memory of all
+ recycled objects anytime by calling the `purgePool` method.
+
+ Sparrow uses this class for `SPPoint`, `SPRectangle` and `SPMatrix`, as they are created very often
+ as helper objects.
+
+ To use memory pooling for another class, you just have to inherit from SPPoolObject and implement
+ the following method:
+
+ + (SPPoolInfo *) poolInfo
+ {
+ static SPPoolInfo poolInfo;
+ return &poolInfo;
+ }
+
+ ------------------------------------------------------------------------------------------------- */
+
+@interface SPPoolObject : NSObject
+{
+ SPPoolObject *mPoolPredecessor;
+}
+
+/// The pool info structure needed to access the pool. Needs to be implemented in any inheriting class.
++ (SPPoolInfo *)poolInfo;
+
+/// Purge all unused objects.
++ (int)purgePool;
+
+@end
+
+#else
+
+typedef NSObject SPPoolObject;
+
+/// Sparrow extensions for NSObject.
+@interface NSObject (SPPoolObjectExtensions)
+
+/// Dummy implementation of SPPoolObject method to simplify switching between NSObject and SPPoolObject.
++ (SPPoolInfo *)poolInfo;
+
+/// Dummy implementation of SPPoolObject method to simplify switching between NSObject and SPPoolObject.
++ (int)purgePool;
+
+@end
+
+
+#endif
\ No newline at end of file
--- /dev/null
+//
+// SPPoolObject.m
+// Sparrow
+//
+// Created by Daniel Sperl on 17.09.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 "SPPoolObject.h"
+#import <malloc/malloc.h>
+
+#ifndef DISABLE_MEMORY_POOLING
+
+#define COMPLAIN_MISSING_IMP @"Class %@ needs this code:\n\
++ (SPPoolInfo *) poolInfo\n\
+{\n\
+ static SPPoolInfo poolInfo;\n\
+ return &poolInfo;\n\
+}"
+
+@implementation SPPoolObject
+
++ (id)allocWithZone:(NSZone *)zone
+{
+ SPPoolInfo *poolInfo = [self poolInfo];
+ if (!poolInfo->poolClass) // first allocation
+ {
+ poolInfo->poolClass = self;
+ poolInfo->lastElement = NULL;
+ }
+ else
+ {
+ if (poolInfo->poolClass != self)
+ [NSException raise:NSGenericException format:COMPLAIN_MISSING_IMP, self];
+ }
+
+ if (!poolInfo->lastElement)
+ {
+ // pool is empty -> allocate
+ return NSAllocateObject(self, 0, NULL);
+ }
+ else
+ {
+ // recycle element, update poolInfo
+ SPPoolObject *object = poolInfo->lastElement;
+ poolInfo->lastElement = object->mPoolPredecessor;
+
+ // zero out memory. (do not overwrite isa & mPoolPredecessor, thus "+2" and "-8")
+ memset((id)object + 2, 0, malloc_size(object) - 8);
+
+ return object;
+ }
+}
+
+- (void)dealloc
+{
+ SPPoolInfo *poolInfo = [isa poolInfo];
+ self->mPoolPredecessor = poolInfo->lastElement;
+ poolInfo->lastElement = self;
+
+ if (0) [super dealloc]; // just to shut down a compiler warning ...
+}
+
+- (void)purge
+{
+ [super dealloc];
+}
+
++ (int)purgePool
+{
+ SPPoolInfo *poolInfo = [self poolInfo];
+ SPPoolObject *lastElement;
+
+ int count=0;
+ while (lastElement = poolInfo->lastElement)
+ {
+ ++count;
+ poolInfo->lastElement = lastElement->mPoolPredecessor;
+ [lastElement purge];
+ }
+
+ return count;
+}
+
++ (SPPoolInfo *)poolInfo
+{
+ [NSException raise:NSGenericException format:COMPLAIN_MISSING_IMP, self];
+ return 0;
+}
+
+@end
+
+#else
+
+@implementation NSObject (SPPoolObjectExtensions)
+
++ (SPPoolInfo *)poolInfo
+{
+ return nil;
+}
+
++ (int)purgePool
+{
+ return 0;
+}
+
+@end
+
+
+#endif
\ No newline at end of file
--- /dev/null
+//
+// SPQuad.h
+// Sparrow
+//
+// Created by Daniel Sperl on 15.03.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 "SPDisplayObject.h"
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPQuad displays a single, colored rectangle.
+ It renders a rectangular area with a single color or a color gradient.
+
+ You can set one color per vertex. The colors will smoothly fade into each other over the area
+ of the quad. To display a simple linear color gradient, assign one color to vertices 0 and 1 and
+ another color to vertices 2 and 3.
+
+ The indices of the vertices are arranged like this:
+
+ 0 - 1
+ | / |
+ 2 - 3
+
+ *Colors*
+
+ Colors in Sparrow are defined as unsigned integers, that's exactly 8 bit per color. The easiest
+ way to define a color is by writing it as a hexadecimal number. A color has the following
+ structure:
+
+ 0xRRGGBB
+
+ That means that you can create the base colors like this:
+
+ 0xFF0000 -> red
+ 0x00FF00 -> green
+ 0x0000FF -> blue
+
+ Other simple colors:
+
+ 0x000000 or 0x0 -> black
+ 0xFFFFFF -> white
+ 0x808080 -> 50% gray
+
+ If you're not comfortable with that, there is also a utility macro available that takes the
+ values for R, G and B separately:
+
+ uint red = SP_COLOR(255, 0, 0)
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPQuad : SPDisplayObject
+{
+ @protected
+ float mVertexCoords[8];
+ uint mVertexColors[4];
+}
+
+/// --------------------
+/// @name Initialization
+/// --------------------
+
+/// Initializes a quad with a certain size and color. _Designated Initializer_.
+- (id)initWithWidth:(float)width height:(float)height color:(uint)color;
+
+/// Initializes a white quad with a certain size.
+- (id)initWithWidth:(float)width height:(float)height;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Sets the color of a vertex.
+- (void)setColor:(uint)color ofVertex:(int)vertexID;
+
+/// Returns the color of a vertex.
+- (uint)colorOfVertex:(int)vertexID;
+
+/// Factory method.
++ (SPQuad*)quadWithWidth:(float)width height:(float)height;
+
+/// Factory method.
++ (SPQuad*)quadWithWidth:(float)width height:(float)height color:(uint)color;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// Sets the colors of all vertices simultaneously. Returns the color of vertex '0'.
+@property (nonatomic, assign) uint color;
+
+@end
--- /dev/null
+//
+// SPQuad.m
+// Sparrow
+//
+// Created by Daniel Sperl on 15.03.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 "SPQuad.h"
+#import "SPRectangle.h"
+#import "SPMacros.h"
+#import "SPPoint.h"
+
+@implementation SPQuad
+
+- (id)initWithWidth:(float)width height:(float)height color:(uint)color
+{
+ if (self = [super init])
+ {
+ mVertexCoords[2] = width;
+ mVertexCoords[5] = height;
+ mVertexCoords[6] = width;
+ mVertexCoords[7] = height;
+
+ self.color = color;
+ }
+ return self;
+}
+
+- (id)initWithWidth:(float)width height:(float)height
+{
+ return [self initWithWidth:width height:height color:SP_WHITE];
+}
+
+- (id)init
+{
+ return [self initWithWidth:32 height:32];
+}
+
+- (SPRectangle*)boundsInSpace:(SPDisplayObject*)targetCoordinateSpace
+{
+ if (targetCoordinateSpace == self) // optimization
+ return [SPRectangle rectangleWithX:0 y:0 width:mVertexCoords[6] height:mVertexCoords[7]];
+
+ SPMatrix *transformationMatrix = [self transformationMatrixToSpace:targetCoordinateSpace];
+ SPPoint *point = [[SPPoint alloc] init];
+
+ float minX = FLT_MAX, maxX = -FLT_MAX, minY = FLT_MAX, maxY = -FLT_MAX;
+ for (int i=0; i<4; ++i)
+ {
+ point.x = mVertexCoords[2*i];
+ point.y = mVertexCoords[2*i+1];
+ SPPoint *transformedPoint = [transformationMatrix transformPoint:point];
+ float tfX = transformedPoint.x;
+ float tfY = transformedPoint.y;
+ minX = MIN(minX, tfX);
+ maxX = MAX(maxX, tfX);
+ minY = MIN(minY, tfY);
+ maxY = MAX(maxY, tfY);
+ }
+ [point release];
+ return [SPRectangle rectangleWithX:minX y:minY width:maxX-minX height:maxY-minY];
+}
+
+- (void)setColor:(uint)color ofVertex:(int)vertexID
+{
+ if (vertexID < 0 || vertexID > 3)
+ [NSException raise:SP_EXC_INDEX_OUT_OF_BOUNDS format:@"invalid vertex id"];
+
+ mVertexColors[vertexID] = color;
+}
+
+- (uint)colorOfVertex:(int)vertexID
+{
+ if (vertexID < 0 || vertexID > 3)
+ [NSException raise:SP_EXC_INDEX_OUT_OF_BOUNDS format:@"invalid vertex id"];
+
+ return mVertexColors[vertexID];
+}
+
+- (void)setColor:(uint)color
+{
+ for (int i=0; i<4; ++i) [self setColor:color ofVertex:i];
+}
+
+- (uint)color
+{
+ return [self colorOfVertex:0];
+}
+
++ (SPQuad*)quadWithWidth:(float)width height:(float)height
+{
+ return [[[SPQuad alloc] initWithWidth:width height:height] autorelease];
+}
+
++ (SPQuad*)quadWithWidth:(float)width height:(float)height color:(uint)color
+{
+ return [[[SPQuad alloc] initWithWidth:width height:height color:color] autorelease];
+}
+
+@end
--- /dev/null
+//
+// SPRectangle.h
+// Sparrow
+//
+// Created by Daniel Sperl on 21.03.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 "SPPoolObject.h"
+#import "SPPoint.h"
+
+/** The SPRectangle class describes a rectangle by its top-left corner point (x, y) and by
+ its width and height. */
+
+@interface SPRectangle : SPPoolObject <NSCopying>
+{
+ @private
+ float mX;
+ float mY;
+ float mWidth;
+ float mHeight;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a rectangle with the specified components. _Designated Initializer_.
+- (id)initWithX:(float)x y:(float)y width:(float)width height:(float)height;
+
+/// Factory method.
++ (SPRectangle*)rectangleWithX:(float)x y:(float)y width:(float)width height:(float)height;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Determines if a point is within the rectangle.
+- (BOOL)containsX:(float)x y:(float)y;
+
+/// Determines if a point is within the rectangle.
+- (BOOL)containsPoint:(SPPoint*)point;
+
+/// Determines if another rectangle is within the rectangle.
+- (BOOL)containsRectangle:(SPRectangle*)rectangle;
+
+/// Determines if another rectangle contains or intersects the rectangle.
+- (BOOL)intersectsRectangle:(SPRectangle*)rectangle;
+
+/// If the specified rectangle intersects with the rectangle, returns the area of intersection.
+- (SPRectangle*)intersectionWithRectangle:(SPRectangle*)rectangle;
+
+/// Adds two rectangles together to create a new Rectangle object (by filling in the space between
+/// the two rectangles).
+- (SPRectangle*)uniteWithRectangle:(SPRectangle*)rectangle;
+
+/// Sets width and height components to zero.
+- (void)setEmpty;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// Rectangle component.
+@property (nonatomic, assign) float x;
+@property (nonatomic, assign) float y;
+@property (nonatomic, assign) float width;
+@property (nonatomic, assign) float height;
+
+/// Determines if a rectangle has an empty area.
+@property (nonatomic, readonly) BOOL isEmpty;
+
+@end
\ No newline at end of file
--- /dev/null
+//
+// SPRectangle.m
+// Sparrow
+//
+// Created by Daniel Sperl on 21.03.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 "SPRectangle.h"
+#import "SPMacros.h"
+
+@implementation SPRectangle
+
+@synthesize x = mX;
+@synthesize y = mY;
+@synthesize width = mWidth;
+@synthesize height = mHeight;
+
+- (id)initWithX:(float)x y:(float)y width:(float)width height:(float)height
+{
+ if (self = [super init])
+ {
+ mX = x;
+ mY = y;
+ mWidth = width;
+ mHeight = height;
+ }
+
+ return self;
+}
+
+- (id)init
+{
+ return [self initWithX:0.0f y:0.0f width:0.0f height:0.0f];
+}
+
+- (BOOL)containsX:(float)x y:(float)y
+{
+ return x >= mX && y >= mY && x <= mX + mWidth && y <= mY + mHeight;
+}
+
+- (BOOL)containsPoint:(SPPoint*)point
+{
+ return [self containsX:point.x y:point.y];
+}
+
+- (BOOL)containsRectangle:(SPRectangle*)rectangle
+{
+ float rX = rectangle->mX;
+ float rY = rectangle->mY;
+ float rWidth = rectangle->mWidth;
+ float rHeight = rectangle->mHeight;
+
+ return rX >= mX && rX + rWidth <= mX + mWidth &&
+ rY >= mY && rY + rHeight <= mY + mHeight;
+}
+
+- (BOOL)intersectsRectangle:(SPRectangle*)rectangle
+{
+ float rX = rectangle->mX;
+ float rY = rectangle->mY;
+ float rWidth = rectangle->mWidth;
+ float rHeight = rectangle->mHeight;
+
+ BOOL outside =
+ (rX <= mX && rX + rWidth <= mX) || (rX >= mX + mWidth && rX + rWidth >= mX + mWidth) ||
+ (rY <= mY && rY + rHeight <= mY) || (rY >= mY + mHeight && rY + rHeight >= mY + mHeight);
+ return !outside;
+}
+
+- (SPRectangle*)intersectionWithRectangle:(SPRectangle*)rectangle
+{
+ float left = MAX(mX, rectangle->mX);
+ float right = MIN(mX + mWidth, rectangle->mX + rectangle->mWidth);
+ float top = MAX(mY, rectangle->mY);
+ float bottom = MIN(mY + mHeight, rectangle->mY + rectangle->mHeight);
+
+ if (left > right || top > bottom)
+ return [SPRectangle rectangleWithX:0 y:0 width:0 height:0];
+ else
+ return [SPRectangle rectangleWithX:left y:top width:right-left height:bottom-top];
+}
+
+- (SPRectangle*)uniteWithRectangle:(SPRectangle*)rectangle
+{
+ float left = MIN(mX, rectangle->mX);
+ float right = MAX(mX + mWidth, rectangle->mX + rectangle->mWidth);
+ float top = MIN(mY, rectangle->mY);
+ float bottom = MAX(mY + mHeight, rectangle->mY + rectangle->mHeight);
+ return [SPRectangle rectangleWithX:left y:top width:right-left height:bottom-top];
+}
+
+- (void)setEmpty
+{
+ mX = mY = mWidth = mHeight = 0;
+}
+
+- (BOOL)isEmpty
+{
+ return mWidth == 0 || mHeight == 0;
+}
+
+- (BOOL)isEqual:(id)other
+{
+ if (other == self) return YES;
+ else if (!other || ![other isKindOfClass:[self class]]) return NO;
+ else
+ {
+ SPRectangle *rect = (SPRectangle*)other;
+ return SP_IS_FLOAT_EQUAL(mX, rect->mX) && SP_IS_FLOAT_EQUAL(mY, rect->mY) &&
+ SP_IS_FLOAT_EQUAL(mWidth, rect->mWidth) && SP_IS_FLOAT_EQUAL(mHeight, rect->mHeight);
+ }
+}
+
+- (NSString*)description
+{
+ return [NSString stringWithFormat:@"(x: %f, y: %f, width: %f, height: %f)", mX, mY, mWidth, mHeight];
+}
+
++ (SPRectangle*)rectangleWithX:(float)x y:(float)y width:(float)width height:(float)height
+{
+ return [[[SPRectangle alloc] initWithX:x y:y width:width height:height] autorelease];
+}
+
+#pragma mark NSCopying
+
+- (id)copyWithZone:(NSZone*)zone;
+{
+ return [[[self class] allocWithZone:zone] initWithX:mX y:mY width:mWidth height:mHeight];
+}
+
+#pragma mark SPPoolObject
+
++ (SPPoolInfo *)poolInfo
+{
+ static SPPoolInfo poolInfo;
+ return &poolInfo;
+}
+
+@end
--- /dev/null
+//
+// SPRenderSupport.h
+// Sparrow
+//
+// Created by Daniel Sperl on 28.09.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>
+
+@class SPTexture;
+@class SPDisplayObject;
+
+/** ------------------------------------------------------------------------------------------------
+
+ A class that contains helper methods simplifying OpenGL rendering.
+
+ An SPRenderSupport instance is passed to any render: method. It saves information about the currently
+ bound texture, which allows it to avoid unecessary texture switches.
+
+ Furthermore, several static helper methods can be used for different needs whenever some
+ OpenGL processing is required.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPRenderSupport : NSObject
+{
+ @private
+ uint mBoundTextureID;
+ BOOL mPremultipliedAlpha;
+}
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Binds a texture if it is not already bound. Pass `nil` to unbind any texture.
+- (void)bindTexture:(SPTexture *)texture;
+
+/// Converts color and alpha into the format needed by OpenGL. Premultiplies alpha depending on state.
+- (uint)convertColor:(uint)color alpha:(float)alpha;
+
+/// Converts color and alpha into the format needed by OpenGL, optionally premultiplying alpha values.
++ (uint)convertColor:(uint)color alpha:(float)alpha premultiplyAlpha:(BOOL)pma;
+
+/// Clears OpenGL's color buffer.
++ (void)clearWithColor:(uint)color alpha:(float)alpha;
+
+/// Transforms OpenGL's matrix into the local coordinate system of the object.
++ (void)transformMatrixForObject:(SPDisplayObject *)object;
+
+/// Sets up OpenGL's projection matrix for 2D rendering.
++ (void)setupOrthographicRenderingWithLeft:(float)left right:(float)right
+ bottom:(float)bottom top:(float)top;
+
+/// Checks for an OpenGL error. If there is one, it is logged an the error code is returned.
++ (uint)checkForOpenGLError;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// Indicates if the bound texture has its alpha channel premultiplied.
+@property (nonatomic, readonly) BOOL usingPremultipliedAlpha;
+
+@end
--- /dev/null
+//
+// SPRenderSupport.m
+// Sparrow
+//
+// Created by Daniel Sperl on 28.09.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 "SPRenderSupport.h"
+#import "SPDisplayObject.h"
+#import "SPTexture.h"
+#import "SPMacros.h"
+
+#import <OpenGLES/EAGL.h>
+#import <OpenGLES/ES1/gl.h>
+#import <OpenGLES/ES1/glext.h>
+
+@implementation SPRenderSupport
+
+@synthesize usingPremultipliedAlpha = mPremultipliedAlpha;
+
+- (id)init
+{
+ if (self = [super init])
+ {
+ mBoundTextureID = UINT_MAX;
+ mPremultipliedAlpha = YES;
+ [self bindTexture:nil];
+ }
+ return self;
+}
+
+- (void)bindTexture:(SPTexture *)texture
+{
+ uint newTextureID = texture.textureID;
+ BOOL newPMA = texture.hasPremultipliedAlpha;
+
+ if (newTextureID != mBoundTextureID)
+ glBindTexture(GL_TEXTURE_2D, newTextureID);
+
+ if (newPMA != mPremultipliedAlpha || !mBoundTextureID)
+ {
+ if (newPMA) glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ else glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ }
+
+ mBoundTextureID = newTextureID;
+ mPremultipliedAlpha = newPMA;
+}
+
+- (uint)convertColor:(uint)color alpha:(float)alpha
+{
+ return [SPRenderSupport convertColor:color alpha:alpha premultiplyAlpha:mPremultipliedAlpha];
+}
+
++ (uint)convertColor:(uint)color alpha:(float)alpha premultiplyAlpha:(BOOL)pma
+{
+ if (pma)
+ {
+ return (GLubyte)(SP_COLOR_PART_RED(color) * alpha) |
+ (GLubyte)(SP_COLOR_PART_GREEN(color) * alpha) << 8 |
+ (GLubyte)(SP_COLOR_PART_BLUE(color) * alpha) << 16 |
+ (GLubyte)(alpha * 255) << 24;
+ }
+ else
+ {
+ return (GLubyte)SP_COLOR_PART_RED(color) |
+ (GLubyte)SP_COLOR_PART_GREEN(color) << 8 |
+ (GLubyte)SP_COLOR_PART_BLUE(color) << 16 |
+ (GLubyte)(alpha * 255) << 24;
+ }
+}
+
++ (void)clearWithColor:(uint)color alpha:(float)alpha;
+{
+ float red = SP_COLOR_PART_RED(color);
+ float green = SP_COLOR_PART_GREEN(color);
+ float blue = SP_COLOR_PART_BLUE(color);
+
+ glClearColor(red, green, blue, alpha);
+ glClear(GL_COLOR_BUFFER_BIT);
+}
+
++ (void)transformMatrixForObject:(SPDisplayObject *)object
+{
+ float x = object.x;
+ float y = object.y;
+ float rotation = object.rotation;
+ float scaleX = object.scaleX;
+ float scaleY = object.scaleY;
+
+ if (x != 0.0f || y != 0.0f) glTranslatef(x, y, 0);
+ if (rotation != 0.0f) glRotatef(SP_R2D(rotation), 0.0f, 0.0f, 1.0f);
+ if (scaleX != 0.0f || scaleY != 0.0f) glScalef(scaleX, scaleY, 1.0f);
+}
+
++ (void)setupOrthographicRenderingWithLeft:(float)left right:(float)right
+ bottom:(float)bottom top:(float)top
+{
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_LIGHTING);
+ glDisable(GL_DEPTH_TEST);
+
+ glEnable(GL_TEXTURE_2D);
+ glEnable(GL_BLEND);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrthof(left, right, bottom, top, -1.0f, 1.0f);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+}
+
++ (uint)checkForOpenGLError
+{
+ GLenum error = glGetError();
+ if (error != 0) NSLog(@"Warning: There was an OpenGL error: #%d", error);
+ return error;
+}
+
+@end
--- /dev/null
+//
+// SPRenderTexture.h
+// Sparrow
+//
+// Created by Daniel Sperl on 04.12.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 <OpenGLES/EAGL.h>
+#import <OpenGLES/ES1/gl.h>
+#import <OpenGLES/ES1/glext.h>
+
+#import "SPDisplayObject.h"
+#import "SPTexture.h"
+#import "SPRenderSupport.h"
+
+typedef void (^SPDrawingBlock)();
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPRenderTexture is a dynamic texture on which you can draw any display object.
+
+ After creating a render texture, just call the `drawObject:` method to render an object directly
+ onto the texture. The object will be drawn onto the texture at its current position, adhering
+ its current rotation, scale and alpha properties.
+
+ Drawing is done very efficiently, as it is happening directly in graphics memory. After you have
+ drawn objects on the texture, the performance will be just like that of a normal texture - no
+ matter how many objects you have drawn.
+
+ If you draw lots of objects at once, it is recommended to bundle the drawing calls in a block
+ via the `bundleDrawCalls:` method, like shown below. That will speed it up immensely, allowing
+ you to draw hundreds of objects very quickly.
+
+ [renderTexture bundleDrawCalls:^
+ {
+ for (int i=0; i<numDrawings; ++i)
+ {
+ image.rotation = (2 * PI / numDrawings) * i;
+ [renderTexture drawObject:image];
+ }
+ }];
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPRenderTexture : SPTexture
+{
+ @private
+ GLuint mFramebuffer;
+ BOOL mFramebufferIsActive;
+ SPTexture *mTexture;
+ SPRenderSupport *mRenderSupport;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a transparent render texture with the scale factor of the stage.
+- (id)initWithWidth:(float)width height:(float)height;
+
+/// Initializes a render texture with a certain ARGB color (0xAARRGGBB).
+- (id)initWithWidth:(float)width height:(float)height fillColor:(uint)argb;
+
+/// Initializes a render texture with a certain ARGB color (0xAARRGGBB) and a scale factor.
+- (id)initWithWidth:(float)width height:(float)height fillColor:(uint)argb scale:(float)scale;
+
+/// Factory method.
++ (SPRenderTexture *)textureWithWidth:(float)width height:(float)height;
+
+/// Factory method.
++ (SPRenderTexture *)textureWithWidth:(float)width height:(float)height fillColor:(uint)argb;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Draws an object onto the texture, adhering its properties for position, scale, rotation and alpha.
+- (void)drawObject:(SPDisplayObject *)object;
+
+/// Bundles several calls to `drawObject:` together in a block. This avoids framebuffer switches.
+- (void)bundleDrawCalls:(SPDrawingBlock)block;
+
+/// Clears the texture with a certain color and alpha value.
+- (void)clearWithColor:(uint)color alpha:(float)alpha;
+
+@end
--- /dev/null
+//
+// SPRenderTexture.m
+// Sparrow
+//
+// Created by Daniel Sperl on 04.12.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 "SPRenderTexture.h"
+#import "SPGLTexture.h"
+#import "SPMacros.h"
+#import "SPUtils.h"
+#import "SPStage.h"
+
+@interface SPRenderTexture ()
+
+- (void)createFramebuffer;
+- (void)destroyFramebuffer;
+- (void)renderToFramebuffer:(SPDrawingBlock)block;
+
+@end
+
+@implementation SPRenderTexture
+
+- (id)initWithWidth:(float)width height:(float)height fillColor:(uint)argb scale:(float)scale
+{
+ if (self = [super init])
+ {
+ int legalWidth = [SPUtils nextPowerOfTwo:width * scale];
+ int legalHeight = [SPUtils nextPowerOfTwo:height * scale];
+
+ SPTextureProperties properties = {
+ .format = SPTextureFormatRGBA,
+ .width = legalWidth,
+ .height = legalHeight,
+ .generateMipmaps = NO,
+ .premultipliedAlpha = NO
+ };
+
+ SPGLTexture *glTexture = [[SPGLTexture alloc] initWithData:NULL properties:properties];
+ glTexture.scale = scale;
+
+ SPRectangle *region = [SPRectangle rectangleWithX:0 y:0 width:width height:height];
+ mTexture = [[SPTexture alloc] initWithRegion:region ofTexture:glTexture];
+ [glTexture release];
+
+ mRenderSupport = [[SPRenderSupport alloc] init];
+
+ [self createFramebuffer];
+ [self clearWithColor:argb alpha:SP_COLOR_PART_ALPHA(argb)];
+ }
+ return self;
+}
+
+- (id)initWithWidth:(float)width height:(float)height fillColor:(uint)argb
+{
+ return [self initWithWidth:width height:height fillColor:argb scale:[SPStage contentScaleFactor]];
+}
+
+- (id)initWithWidth:(float)width height:(float)height
+{
+ return [self initWithWidth:width height:height fillColor:0x0];
+}
+
+- (id)init
+{
+ return [self initWithWidth:256 height:256];
+}
+
+- (void)dealloc
+{
+ [mTexture release];
+ [mRenderSupport release];
+ [self destroyFramebuffer];
+ [super dealloc];
+}
+
+- (void)createFramebuffer
+{
+ // create framebuffer
+ glGenFramebuffersOES(1, &mFramebuffer);
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, mFramebuffer);
+
+ // attach renderbuffer
+ glFramebufferTexture2DOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_TEXTURE_2D,
+ mTexture.textureID, 0);
+
+ if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES)
+ NSLog(@"failed to create frame buffer for render texture");
+
+ // unbind frame buffer
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);
+}
+
+- (void)destroyFramebuffer
+{
+ glDeleteFramebuffersOES(1, &mFramebuffer);
+ mFramebuffer = 0;
+}
+
+- (void)renderToFramebuffer:(SPDrawingBlock)block
+{
+ if (!block) return;
+
+ // the block may call a draw-method again, so we're making sure that the frame buffer switching
+ // happens only in the outermost block.
+
+ int stdFramebuffer = -1;
+
+ if (!mFramebufferIsActive)
+ {
+ mFramebufferIsActive = YES;
+
+ // remember standard frame buffer
+ glGetIntegerv(GL_FRAMEBUFFER_BINDING_OES, &stdFramebuffer);
+
+ // switch to the texture's framebuffer for rendering
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, mFramebuffer);
+
+ // prepare viewport and OpenGL matrices
+ glViewport(0, 0, mTexture.width * mTexture.scale, mTexture.height * mTexture.scale);
+ [SPRenderSupport setupOrthographicRenderingWithLeft:0 right:mTexture.width
+ bottom:0 top:mTexture.height];
+
+ // reset texture binding
+ [mRenderSupport bindTexture:nil];
+ }
+
+ block();
+
+ if (stdFramebuffer != -1)
+ {
+ mFramebufferIsActive = NO;
+
+ // return to standard frame buffer
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, stdFramebuffer);
+ }
+}
+
+- (void)drawObject:(SPDisplayObject *)object
+{
+ [self renderToFramebuffer:^
+ {
+ glPushMatrix();
+
+ [SPRenderSupport transformMatrixForObject:object];
+ [object render:mRenderSupport];
+
+ glPopMatrix();
+ }];
+}
+
+- (void)bundleDrawCalls:(SPDrawingBlock)block
+{
+ [self renderToFramebuffer:block];
+}
+
+- (void)clearWithColor:(uint)color alpha:(float)alpha
+{
+ [self renderToFramebuffer:^
+ {
+ [SPRenderSupport clearWithColor:color alpha:alpha];
+ }];
+}
+
+- (void)adjustTextureCoordinates:(const float *)texCoords saveAtTarget:(float *)targetTexCoords
+ numVertices:(int)numVertices
+{
+ [mTexture adjustTextureCoordinates:texCoords saveAtTarget:targetTexCoords numVertices:numVertices];
+}
+
+- (float)width
+{
+ return mTexture.width;
+}
+
+- (float)height
+{
+ return mTexture.height;
+}
+
+- (uint)textureID
+{
+ return mTexture.textureID;
+}
+
+- (BOOL)hasPremultipliedAlpha
+{
+ return mTexture.hasPremultipliedAlpha;
+}
+
+- (float)scale
+{
+ return mTexture.scale;
+}
+
++ (SPRenderTexture *)textureWithWidth:(float)width height:(float)height
+{
+ return [[[SPRenderTexture alloc] initWithWidth:width height:height] autorelease];
+}
+
++ (SPRenderTexture *)textureWithWidth:(float)width height:(float)height fillColor:(uint)argb
+{
+ return [[[SPRenderTexture alloc] initWithWidth:width height:height fillColor:argb] autorelease];
+}
+
+@end
--- /dev/null
+//
+// SPRenderingGLES.m
+// Sparrow
+//
+// Created by Daniel Sperl on 16.03.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 "SPMacros.h"
+#import "SPDisplayObjectContainer.h"
+#import "SPQuad.h"
+#import "SPStage.h"
+#import "SPImage.h"
+#import "SPTextField.h"
+#import "SPTexture.h"
+#import "SPRenderSupport.h"
+
+#import <OpenGLES/EAGL.h>
+#import <OpenGLES/ES1/gl.h>
+#import <OpenGLES/ES1/glext.h>
+
+@implementation SPStage (Rendering)
+
+- (void)render:(SPRenderSupport *)support;
+{
+ [SPRenderSupport clearWithColor:0x0 alpha:1.0f];
+ [SPRenderSupport setupOrthographicRenderingWithLeft:0 right:mWidth bottom:mHeight top:0];
+
+ [super render:support];
+
+ #if DEBUG
+ [SPRenderSupport checkForOpenGLError];
+ #endif
+}
+
+@end
+
+@implementation SPDisplayObjectContainer (Rendering)
+
+- (void)render:(SPRenderSupport *)support;
+{
+ float alpha = self.alpha;
+
+ for (SPDisplayObject *child in mChildren)
+ {
+ float childAlpha = child.alpha;
+ if (childAlpha != 0.0f && child.visible)
+ {
+ glPushMatrix();
+
+ [SPRenderSupport transformMatrixForObject:child];
+
+ child.alpha *= alpha;
+ [child render:support];
+ child.alpha = childAlpha;
+
+ glPopMatrix();
+ }
+ }
+}
+
+@end
+
+@implementation SPQuad (Rendering)
+
+- (void)render:(SPRenderSupport *)support
+{
+ static uint colors[4];
+ float alpha = self.alpha;
+
+ [support bindTexture:nil];
+
+ for (int i=0; i<4; ++i)
+ colors[i] = [support convertColor:mVertexColors[i] alpha:alpha];
+
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_COLOR_ARRAY);
+
+ glVertexPointer(2, GL_FLOAT, 0, mVertexCoords);
+ glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors);
+
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+ glDisableClientState(GL_VERTEX_ARRAY);
+ glDisableClientState(GL_COLOR_ARRAY);
+}
+
+@end
+
+@implementation SPImage (Rendering)
+
+- (void)render:(SPRenderSupport *)support;
+{
+ static float texCoords[8];
+ static uint colors[4];
+ float alpha = self.alpha;
+
+ [support bindTexture:mTexture];
+ [mTexture adjustTextureCoordinates:mTexCoords saveAtTarget:texCoords numVertices:4];
+
+ for (int i=0; i<4; ++i)
+ colors[i] = [support convertColor:mVertexColors[i] alpha:alpha];
+
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_COLOR_ARRAY);
+
+ glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
+ glVertexPointer(2, GL_FLOAT, 0, mVertexCoords);
+ glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors);
+
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+ glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+ glDisableClientState(GL_VERTEX_ARRAY);
+ glDisableClientState(GL_COLOR_ARRAY);
+
+ // Rendering was tested with vertex buffers, too -- but for simple quads and images like these,
+ // the overhead seems to outweigh the benefit. The "glDrawArrays"-approach is faster here.
+}
+
+@end
--- /dev/null
+//
+// SPSound.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>
+
+@class SPSoundChannel;
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPSound class contains audio data that is ready for playback.
+
+ Just as SPTexture contains image data, SPSound contains audio data. It loads audio files in
+ different formats and keeps them in memory (or streams them if the format supports it).
+
+ You can use SPSound to play a sound directly, but this won't give you much control.
+ If you want to control the playback and volume of the sound, you you have to create an
+ SPSoundChannel first (via `createChannel`).
+
+ If you need to play a sound repeatedly, create the SPSound object only once and keep it in
+ memory. Then call play or create a channel when you want to play the sound.
+
+ Your sounds will automatically be paused when the application
+ is disrupted (e.g. by a phone call), and will continue playback where they stopped.
+
+ Behind the scenes, the SPSound class will choose the appropriate technology for playback:
+ uncompressed files will use OpenAL, compressed sound will be handled by Apple’s AVAudioPlayer.
+ You don’t have to care.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPSound : NSObject
+{
+ @private
+ NSMutableSet *mPlayingChannels;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a sound
+- (id)initWithContentsOfFile:(NSString *)path;
+
+/// Factory method.
++ (SPSound *)soundWithContentsOfFile:(NSString *)path;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Starts playback of the sound.
+- (void)play;
+
+/// Creates an audio channel that gives you more control over playback. Don't forget to retain it!
+- (SPSoundChannel *)createChannel;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The duration of the sound in seconds.
+@property (nonatomic, readonly) double duration;
+
+@end
--- /dev/null
+//
+// SPSound.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 "SPSound.h"
+#import "SPSoundChannel.h"
+#import "SPMacros.h"
+#import "SPEvent.h"
+#import "SPALSound.h"
+#import "SPAVSound.h"
+
+#import <AudioToolbox/AudioToolbox.h>
+
+@implementation SPSound
+
+- (id)init
+{
+ if ([self isMemberOfClass:[SPSound class]])
+ {
+ [self release];
+ [NSException raise:SP_EXC_ABSTRACT_CLASS
+ format:@"Attempting to initialize abstract class SPSound."];
+ return nil;
+ }
+
+ return [super init];
+}
+
+- (id)initWithContentsOfFile:(NSString *)path
+{
+ // SPSound is a class factory! We'll return a subclass, thus we don't need 'self' anymore.
+ [self release];
+
+ NSString *fullPath = [path isAbsolutePath] ?
+ path : [[NSBundle mainBundle] pathForResource:path ofType:nil];
+
+ if (![[NSFileManager defaultManager] fileExistsAtPath:fullPath])
+ {
+ [self release];
+ [NSException raise:SP_EXC_FILE_NOT_FOUND format:@"file %@ not found", fullPath];
+ }
+
+ NSString *error = nil;
+
+ AudioFileID fileID = 0;
+ void *soundBuffer = NULL;
+ int soundSize = 0;
+ int soundChannels = 0;
+ int soundFrequency = 0;
+ double soundDuration = 0.0;
+
+ do
+ {
+ OSStatus result = noErr;
+
+ result = AudioFileOpenURL((CFURLRef) [NSURL fileURLWithPath:fullPath],
+ kAudioFileReadPermission, 0, &fileID);
+ if (result != noErr)
+ {
+ error = [NSString stringWithFormat:@"could not read audio file (%x)", result];
+ break;
+ }
+
+ AudioStreamBasicDescription fileFormat;
+ UInt32 propertySize = sizeof(fileFormat);
+ result = AudioFileGetProperty(fileID, kAudioFilePropertyDataFormat, &propertySize, &fileFormat);
+ if (result != noErr)
+ {
+ error = [NSString stringWithFormat:@"could not read file format info (%x)", result];
+ break;
+ }
+
+ propertySize = sizeof(soundDuration);
+ result = AudioFileGetProperty(fileID, kAudioFilePropertyEstimatedDuration,
+ &propertySize, &soundDuration);
+ if (result != noErr)
+ {
+ error = [NSString stringWithFormat:@"could not read sound duration (%x)", result];
+ break;
+ }
+
+ if (fileFormat.mFormatID != kAudioFormatLinearPCM)
+ {
+ error = @"sound file not linear PCM";
+ break;
+ }
+
+ if (fileFormat.mChannelsPerFrame > 2)
+ {
+ error = @"more than two channels in sound file";
+ break;
+ }
+
+ if (!TestAudioFormatNativeEndian(fileFormat))
+ {
+ error = @"sounds must be little-endian";
+ break;
+ }
+
+ if (!(fileFormat.mBitsPerChannel == 8 || fileFormat.mBitsPerChannel == 16))
+ {
+ error = @"only files with 8 or 16 bits per channel supported";
+ break;
+ }
+
+ UInt64 fileSize = 0;
+ propertySize = sizeof(fileSize);
+ result = AudioFileGetProperty(fileID, kAudioFilePropertyAudioDataByteCount,
+ &propertySize, &fileSize);
+ if (result != noErr)
+ {
+ error = [NSString stringWithFormat:@"could not read sound file size (%x)", result];
+ break;
+ }
+
+ UInt32 dataSize = (UInt32)fileSize;
+ soundBuffer = malloc(dataSize);
+ if (!soundBuffer)
+ {
+ error = @"could not allocate memory for sound data";
+ break;
+ }
+
+ result = AudioFileReadBytes(fileID, false, 0, &dataSize, soundBuffer);
+ if (result == noErr)
+ {
+ soundSize = (int) dataSize;
+ soundChannels = fileFormat.mChannelsPerFrame;
+ soundFrequency = fileFormat.mSampleRate;
+ }
+ else
+ {
+ error = [NSString stringWithFormat:@"could not read sound data (%x)", result];
+ break;
+ }
+ }
+ while (NO);
+
+ if (fileID) AudioFileClose(fileID);
+
+ if (!error)
+ {
+ self = [[SPALSound alloc] initWithData:soundBuffer size:soundSize channels:soundChannels
+ frequency:soundFrequency duration:soundDuration];
+ }
+ else
+ {
+ NSLog(@"Sound '%@' will be played with AVAudioPlayer [Reason: %@]", path, error);
+ self = [[SPAVSound alloc] initWithContentsOfFile:fullPath duration:soundDuration];
+ }
+
+ free(soundBuffer);
+ return self;
+}
+
+- (void)play
+{
+ SPSoundChannel *channel = [self createChannel];
+ [channel addEventListener:@selector(onSoundCompleted:) atObject:self
+ forType:SP_EVENT_TYPE_SOUND_COMPLETED];
+ [channel play];
+
+ if (!mPlayingChannels) mPlayingChannels = [[NSMutableSet alloc] init];
+ [mPlayingChannels addObject:channel];
+}
+
+- (void)onSoundCompleted:(SPEvent *)event
+{
+ SPSoundChannel *channel = (SPSoundChannel *)event.target;
+ [channel stop];
+ [mPlayingChannels removeObject:channel];
+}
+
+- (SPSoundChannel *)createChannel
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+ return nil;
+}
+
+- (double)duration
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+ return 0.0;
+}
+
++ (SPSound *)soundWithContentsOfFile:(NSString *)path
+{
+ return [[[SPSound alloc] initWithContentsOfFile:path] autorelease];
+}
+
+- (void)dealloc
+{
+ [mPlayingChannels release];
+ [super dealloc];
+}
+
+@end
--- /dev/null
+//
+// SPSoundChannel.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>
+#import "SPSound.h"
+#import "SPEventDispatcher.h"
+
+#define SP_EVENT_TYPE_SOUND_COMPLETED @"soundCompleted"
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPSoundChannel represents an audio source. Use this class to control sound playback.
+
+ Sound channels are created with the method [SPSound createChannel]. They allow control over
+ playback (`play`, `pause`, `stop`) and properties as the volume or if the sound should loop.
+
+ Furthermore, it will dispatch events of type `SP_EVENT_TYPE_SOUND_COMPLETED` when the sound
+ is finished.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPSoundChannel : SPEventDispatcher
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Starts playback.
+- (void)play;
+
+/// Stops playback. Sound will start from the beginning on `play`.
+- (void)stop;
+
+/// Pauses the sound. Call `play` again to continue.
+- (void)pause;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// Indicates if the sound is currently playing.
+@property (nonatomic, readonly) BOOL isPlaying;
+
+/// Indicates if the sound is currently paused.
+@property (nonatomic, readonly) BOOL isPaused;
+
+/// Indicates if the sound was stopped.
+@property (nonatomic, readonly) BOOL isStopped;
+
+/// The duration of the sound in seconds.
+@property (nonatomic, readonly) double duration;
+
+/// The volume of the sound. Range: [0.0 - 1.0]
+@property (nonatomic, assign) float volume;
+
+/// Indicates if the sound should loop. Looping sounds don't dispatch SOUND_COMPLETED events.
+@property (nonatomic, assign) BOOL loop;
+
+@end
--- /dev/null
+//
+// SPSoundChannel.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 "SPSoundChannel.h"
+#import "SPMacros.h"
+
+@implementation SPSoundChannel
+
+- (id)init
+{
+ if ([self isMemberOfClass:[SPSoundChannel class]])
+ {
+ [self release];
+ [NSException raise:SP_EXC_ABSTRACT_CLASS
+ format:@"Attempting to initialize abstract class SPSoundChannel."];
+ return nil;
+ }
+
+ return [super init];
+}
+
+- (void)play
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+}
+
+- (void)pause
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+}
+
+- (void)stop
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+}
+
+- (BOOL)isPlaying
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+ return NO;
+}
+
+- (BOOL)isPaused
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+ return NO;
+}
+
+- (BOOL)isStopped
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+ return NO;
+}
+
+- (BOOL)loop
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+ return NO;
+}
+
+- (void)setLoop:(BOOL)value
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+}
+
+- (float)volume
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+ return 1.0f;
+}
+
+- (void)setVolume:(float)value
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+}
+
+- (double)duration
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+ return 0.0;
+}
+
+@end
--- /dev/null
+//
+// SPSprite.h
+// Sparrow
+//
+// Created by Daniel Sperl on 21.03.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"
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPSprite is the most lightweight, non-abstract container class.
+
+ Use it as a simple means of grouping objects together in one coordinate system.
+
+ SPSprite *sprite = [SPSprite sprite];
+
+ // create children
+ SPImage *venus = [SPImage imageWithContentsOfFile:@"venus.png"];
+ SPImage *mars = [SPImage imageWithContentsOfFile:@"mars.png"];
+
+ // move children to some relative positions
+ venus.x = 50;
+ mars.x = -20;
+
+ // add children to the sprite
+ [sprite addChild:venus];
+ [sprite addChild:mars];
+
+ // calculate total width of all children
+ float totalWidth = sprite.width;
+
+ // rotate the whole group
+ sprite.rotation = PI;
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPSprite : SPDisplayObjectContainer
+
+/// Create a new, empty sprite.
++ (SPSprite*)sprite;
+
+@end
--- /dev/null
+//
+// SPSprite.m
+// Sparrow
+//
+// Created by Daniel Sperl on 21.03.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 "SPSprite.h"
+
+
+@implementation SPSprite
+
++ (SPSprite*)sprite
+{
+ return [[[SPSprite alloc] init] autorelease];
+}
+
+@end
--- /dev/null
+//
+// SPStage.h
+// Sparrow
+//
+// Created by Daniel Sperl on 15.03.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 SPTouchProcessor;
+@class SPJuggler;
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPStage is the root of the display tree. It represents the rendering area of the application.
+
+ To create a Sparrow application, you create a class that inherits from SPStage and populates
+ the display tree.
+
+ The stage allows you to access the native view object it is drawing its content to. (Currently,
+ this is always an SPView). Furthermore, you can change the framerate in which the contents is
+ rendered.
+
+ A stage also contains a default juggler which you can use for your animations. It is advanced
+ automatically once per frame. You can access this juggler from any display object by calling
+
+ self.stage.juggler
+
+ You have to take care, however, that the display object you are making this call from is already
+ connected to the stage - otherwise, the method will return `nil`, and you won't have access to
+ the juggler.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPStage : SPDisplayObjectContainer
+{
+ @private
+ float mWidth;
+ float mHeight;
+
+ // helpers
+ SPTouchProcessor *mTouchProcessor;
+ SPJuggler *mJuggler;
+
+ id mNativeView;
+}
+
+/// --------------------
+/// @name Initialization
+/// --------------------
+
+/// Initializes a stage with a certain size in points.
+- (id)initWithWidth:(float)width height:(float)height;
+
+/// Dispatches an enter frame event on all children and advances the juggler.
+- (void)advanceTime:(double)seconds;
+
+/// Process a new set up touches. Dispatches touch events on affected children.
+- (void)processTouches:(NSSet*)touches;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The requested number of frames per second. Must be a divisor of 60 (like 30, 20, 15, 12, 10, etc.).
+/// The actual frame rate might be lower if there is too much to process.
+@property (nonatomic, assign) float frameRate;
+
+/// A juggler that is automatically advanced once per frame.
+@property (nonatomic, readonly) SPJuggler *juggler;
+
+/// The native view the stage is connected to. Normally an SPView.
+@property (nonatomic, readonly) id nativeView;
+
+@end
+
+
+@interface SPStage (HDSupport)
+
+/// Enables support for high resolutions (aka retina displays).
++ (void)setSupportHighResolutions:(BOOL)value;
+
+/// Determines if high resolution support is activated.
++ (BOOL)supportHighResolutions;
+
+/// Sets the content scale factor, which determines the relationship between points and pixels.
++ (void)setContentScaleFactor:(float)value;
+
+/// The current content scale factor.
++ (float)contentScaleFactor;
+
+@end
\ No newline at end of file
--- /dev/null
+//
+// SPStage.m
+// Sparrow
+//
+// Created by Daniel Sperl on 15.03.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 "SPStage.h"
+#import "SPStage_Internal.h"
+#import "SPDisplayObject_Internal.h"
+#import "SPMacros.h"
+#import "SPEnterFrameEvent.h"
+#import "SPTouchProcessor.h"
+#import "SPJuggler.h"
+
+#import <UIKit/UIKit.h>
+
+// --- static members ------------------------------------------------------------------------------
+
+static BOOL supportHighResolutions = NO;
+static float contentScaleFactor = -1;
+static NSMutableArray *stages = NULL;
+
+// --- class implementation ------------------------------------------------------------------------
+
+@implementation SPStage
+
+@synthesize width = mWidth;
+@synthesize height = mHeight;
+@synthesize juggler = mJuggler;
+@synthesize nativeView = mNativeView;
+
+- (id)initWithWidth:(float)width height:(float)height
+{
+ if (self = [super init])
+ {
+ // Save existing stages to have access to them in "SPStage setSupportHighResolutions:".
+ // We use a CFArray to avoid that 'self' is retained -> that would cause a memory leak!
+ if (!stages) stages = (NSMutableArray *)CFArrayCreateMutable(NULL, 0, NULL);
+ [stages addObject:self];
+
+ mWidth = width;
+ mHeight = height;
+ mTouchProcessor = [[SPTouchProcessor alloc] initWithRoot:self];
+ mJuggler = [[SPJuggler alloc] init];
+ }
+ return self;
+}
+
+- (id)init
+{
+ CGSize screenSize = [UIScreen mainScreen].bounds.size;
+ return [self initWithWidth:screenSize.width height:screenSize.height];
+}
+
+- (void)advanceTime:(double)seconds
+{
+ // advance juggler
+ [mJuggler advanceTime:seconds];
+
+ // dispatch EnterFrameEvent
+ SPEnterFrameEvent *enterFrameEvent = [[SPEnterFrameEvent alloc]
+ initWithType:SP_EVENT_TYPE_ENTER_FRAME passedTime:seconds];
+ [self dispatchEventOnChildren:enterFrameEvent];
+ [enterFrameEvent release];
+}
+
+- (void)processTouches:(NSSet*)touches
+{
+ [mTouchProcessor processTouches:touches];
+}
+
+- (SPDisplayObject*)hitTestPoint:(SPPoint*)localPoint forTouch:(BOOL)isTouch;
+{
+ if (isTouch && (!self.visible || !self.touchable))
+ return nil;
+
+ SPDisplayObject *target = [super hitTestPoint:localPoint forTouch:isTouch];
+
+ // different to other containers, the stage should acknowledge touches even in empty parts.
+ if (!target)
+ {
+ SPRectangle *bounds = [SPRectangle rectangleWithX:self.x y:self.y
+ width:self.width height:self.height];
+ if ([bounds containsPoint:localPoint])
+ target = self;
+ }
+ return target;
+}
+
+- (float)width
+{
+ return mWidth;
+}
+
+- (void)setWidth:(float)width
+{
+ [NSException raise:SP_EXC_INVALID_OPERATION format:@"cannot set width of stage"];
+}
+
+- (float)height
+{
+ return mHeight;
+}
+
+- (void)setHeight:(float)height
+{
+ [NSException raise:SP_EXC_INVALID_OPERATION format:@"cannot set height of stage"];
+}
+
+- (void)setX:(float)value
+{
+ [NSException raise:SP_EXC_INVALID_OPERATION format:@"cannot set x-coordinate of stage"];
+}
+
+- (void)setY:(float)value
+{
+ [NSException raise:SP_EXC_INVALID_OPERATION format:@"cannot set y-coordinate of stage"];
+}
+
+- (void)setScaleX:(float)value
+{
+ [NSException raise:SP_EXC_INVALID_OPERATION format:@"cannot scale stage"];
+}
+
+- (void)setScaleY:(float)value
+{
+ [NSException raise:SP_EXC_INVALID_OPERATION format:@"cannot scale stage"];
+}
+
+- (void)setRotation:(float)value
+{
+ [NSException raise:SP_EXC_INVALID_OPERATION format:@"cannot rotate stage"];
+}
+
+- (void)setFrameRate:(float)value
+{
+ [mNativeView setFrameRate:value];
+}
+
+- (float)frameRate
+{
+ return [mNativeView frameRate];
+}
+
+- (void)dealloc
+{
+ [SPPoint purgePool];
+ [SPRectangle purgePool];
+ [SPMatrix purgePool];
+
+ [mTouchProcessor release];
+ [mJuggler release];
+
+ [stages removeObject:self];
+ if (stages.count == 0) { [stages release]; stages = NULL; }
+
+ [super dealloc];
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPStage (HDSupport)
+
++ (void)updateNativeViews
+{
+ for (SPStage *stage in stages)
+ {
+ if ([stage.nativeView respondsToSelector:@selector(contentScaleFactor)])
+ {
+ [stage.nativeView setContentScaleFactor:[SPStage contentScaleFactor]];
+ [stage.nativeView layoutSubviews];
+ }
+ }
+}
+
++ (void)setSupportHighResolutions:(BOOL)value
+{
+ if (value != supportHighResolutions)
+ {
+ supportHighResolutions = value;
+ [SPStage updateNativeViews];
+ }
+}
+
++ (BOOL)supportHighResolutions
+{
+ return supportHighResolutions;
+}
+
++ (void)setContentScaleFactor:(float)value
+{
+ if (value != contentScaleFactor)
+ {
+ contentScaleFactor = value;
+ [SPStage updateNativeViews];
+ }
+}
+
++ (float)contentScaleFactor
+{
+ if (supportHighResolutions &&
+ [UIScreen instancesRespondToSelector:@selector(scale)] &&
+ [UIImage instancesRespondToSelector:@selector(scale)])
+ {
+ return contentScaleFactor == -1 ? [[UIScreen mainScreen] scale] : contentScaleFactor;
+ }
+ else
+ {
+ return 1.0f;
+ }
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPStage (Internal)
+
+- (void)setNativeView:(id)nativeView
+{
+ if ([nativeView respondsToSelector:@selector(setContentScaleFactor:)])
+ [nativeView setContentScaleFactor:[SPStage contentScaleFactor]];
+
+ mNativeView = nativeView;
+}
+
+@end
--- /dev/null
+//
+// SPStage_Internal.h
+// Sparrow
+//
+// Created by Daniel Sperl on 30.08.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 "SPStage.h"
+
+@interface SPStage (Internal)
+
+- (void)setNativeView:(id)nativeView;
+
+@end
--- /dev/null
+//
+// SPSubTexture.h
+// Sparrow
+//
+// Created by Daniel Sperl on 27.06.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 "SPTexture.h"
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPSubTexture represents a section of another texture. This is achived solely by
+ manipulation of texture coordinates, making the class very efficient.
+
+ It is allowed to create subtextures of subtextures.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPSubTexture : SPTexture
+{
+ @private
+ SPTexture *mBaseTexture;
+ SPRectangle *mClipping;
+ SPRectangle *mRootClipping;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a subtexture with a region (in points) of another texture.
+- (id)initWithRegion:(SPRectangle*)region ofTexture:(SPTexture*)texture;
+
+/// Factory method.
++ (SPSubTexture*)textureWithRegion:(SPRectangle*)region ofTexture:(SPTexture*)texture;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The texture which the subtexture is based on.
+@property (nonatomic, readonly) SPTexture *baseTexture;
+
+/// The clipping rectangle, which is the region provided on initialization, scaled into [0.0, 1.0].
+@property (nonatomic, copy) SPRectangle *clipping;
+
+@end
--- /dev/null
+//
+// SPSubTexture.m
+// Sparrow
+//
+// Created by Daniel Sperl on 27.06.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 "SPSubTexture.h"
+#import "SPRectangle.h"
+
+@implementation SPSubTexture
+
+@synthesize baseTexture = mBaseTexture;
+@synthesize clipping = mClipping;
+
+- (id)initWithRegion:(SPRectangle*)region ofTexture:(SPTexture*)texture
+{
+ if (self = [super init])
+ {
+ mBaseTexture = [texture retain];
+
+ // convert region to clipping rectangle (which has values between 0 and 1)
+ self.clipping = [SPRectangle rectangleWithX:region.x/texture.width
+ y:region.y/texture.height
+ width:region.width/texture.width
+ height:region.height/texture.height];
+ }
+ return self;
+}
+
+- (id)init
+{
+ [self release];
+ return nil;
+}
+
+- (void)setClipping:(SPRectangle *)clipping
+{
+ [mClipping release];
+ mClipping = [clipping copy];
+
+ // if the base texture is a sub texture as well, calculate clipping
+ // in reference to the root texture
+ [mRootClipping release];
+ mRootClipping = [mClipping copy];
+ SPTexture *baseTexture = mBaseTexture;
+ while ([baseTexture isKindOfClass:[SPSubTexture class]])
+ {
+ SPSubTexture *baseSubTexture = (SPSubTexture *)baseTexture;
+ SPRectangle *baseClipping = baseSubTexture->mClipping;
+
+ mRootClipping.x = baseClipping.x + mRootClipping.x * baseClipping.width;
+ mRootClipping.y = baseClipping.y + mRootClipping.y * baseClipping.height;
+ mRootClipping.width *= baseClipping.width;
+ mRootClipping.height *= baseClipping.height;
+
+ baseTexture = baseSubTexture.baseTexture;
+ }
+}
+
+- (void)adjustTextureCoordinates:(const float *)texCoords saveAtTarget:(float *)targetTexCoords
+ numVertices:(int)numVertices
+{
+ float clipX = mRootClipping.x;
+ float clipY = mRootClipping.y;
+ float clipWidth = mRootClipping.width;
+ float clipHeight = mRootClipping.height;
+
+ for (int i=0; i<numVertices; ++i)
+ {
+ targetTexCoords[2*i] = clipX + texCoords[2*i] * clipWidth;
+ targetTexCoords[2*i+1] = clipY + texCoords[2*i+1] * clipHeight;
+ }
+}
+
+- (float)width
+{
+ return mBaseTexture.width * mClipping.width;
+}
+
+- (float)height
+{
+ return mBaseTexture.height * mClipping.height;
+}
+
+- (uint)textureID
+{
+ return mBaseTexture.textureID;
+}
+
+- (BOOL)hasPremultipliedAlpha
+{
+ return mBaseTexture.hasPremultipliedAlpha;
+}
+
+- (float)scale
+{
+ return mBaseTexture.scale;
+}
+
++ (SPSubTexture*)textureWithRegion:(SPRectangle*)region ofTexture:(SPTexture*)texture
+{
+ return [[[SPSubTexture alloc] initWithRegion:region ofTexture:texture] autorelease];
+}
+
+- (void)dealloc
+{
+ [mClipping release];
+ [mRootClipping release];
+ [mBaseTexture release];
+ [super dealloc];
+}
+
+@end
--- /dev/null
+//
+// SPTextField.h
+// Sparrow
+//
+// Created by Daniel Sperl on 29.06.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"
+#import "SPMacros.h"
+
+@class SPTexture;
+@class SPQuad;
+
+#define SP_DEFAULT_FONT_NAME @"Helvetica"
+#define SP_DEFAULT_FONT_SIZE 14.0f
+#define SP_DEFAULT_FONT_COLOR SP_BLACK
+
+#define SP_NATIVE_FONT_SIZE -1.0f
+
+/// Horizontal Alignment
+typedef enum
+{
+ SPHAlignLeft = 0,
+ SPHAlignCenter,
+ SPHAlignRight
+} SPHAlign;
+
+/// Vertical Alignment
+typedef enum
+{
+ SPVAlignTop = 0,
+ SPVAlignCenter,
+ SPVAlignBottom
+} SPVAlign;
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPTextField displays text, either using standard iOS fonts or a custom bitmap font.
+
+ You can set all properties you are used to, like the font name and size, a color, the horizontal
+ and vertical alignment, etc. The border property is helpful during development, because it lets
+ you see the bounds of the textfield.
+
+ There are two types of fonts that can be displayed:
+
+ * Standard iOS fonts. This renders the text with standard iOS fonts like Verdana or Arial. Use this
+ method if you want to keep it simple, and if the text changes not too often. Simply pass the
+ font name to the corresponding property.
+ * Bitmap fonts. If you need speed or fancy font effects, use a bitmap font instead. That is a
+ font that has its glyphs rendered to a texture atlas. To use it, first register the font with
+ the method registerBitmapFontFromFile:, and then pass the font name to the corresponding
+ property of the text field.
+
+ For the latter, we recommend the Angel Code Bitmap Font Generator. Export the font data as an XML
+ file and the texture as a png with white characters on a transparent background (32 bit).
+
+ -> http://www.angelcode.com/products/bmfont/
+
+ Here is a sample with a standard font:
+
+ SPTextField *textField = [SPTextField textFieldWithWidth:300 height:100 text:@"Hello world!"];
+ textField.hAlign = SPHAlignCenter;
+ textField.vAlign = SPVAlignCenter;
+ textField.fontSize = 18;
+ textField.fontName = @"Georgia-Bold";
+
+ And now we use a bitmap font:
+
+ // Register the font; the returned font name is the one that is defined in the font XML.
+ NSString *fontName = [SPTextField registerBitmapFontFromFile:@"bitmap_font.fnt"];
+
+ SPTextField *textField = [SPTextField textFieldWithWidth:300 height:100 text:@"Hello world!"];
+ textField.fontName = fontName;
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPTextField : SPDisplayObjectContainer
+{
+ @private
+ float mFontSize;
+ uint mColor;
+ NSString *mText;
+ NSString *mFontName;
+ SPHAlign mHAlign;
+ SPVAlign mVAlign;
+ BOOL mBorder;
+ BOOL mRequiresRedraw;
+ BOOL mIsRenderedText;
+
+ SPQuad *mHitArea;
+ SPQuad *mTextArea;
+ SPDisplayObject *mContents;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initialize a text field with all important font properties. _Designated Initializer_.
+- (id)initWithWidth:(float)width height:(float)height text:(NSString*)text fontName:(NSString*)name
+ fontSize:(float)size color:(uint)color;
+
+/// Initialize a text field with default settings (Helvetica, 14pt, black).
+- (id)initWithWidth:(float)width height:(float)height text:(NSString*)text;
+
+/// Initialize a 128x128 textField (Helvetica, 14pt, black).
+- (id)initWithText:(NSString *)text;
+
+/// Factory method.
++ (SPTextField *)textFieldWithWidth:(float)width height:(float)height text:(NSString*)text
+ fontName:(NSString*)name fontSize:(float)size color:(uint)color;
+
+/// Factory method.
++ (SPTextField *)textFieldWithWidth:(float)width height:(float)height text:(NSString*)text;
+
+/// Factory method.
++ (SPTextField *)textFieldWithText:(NSString *)text;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Makes a bitmap font available at any text field, manually providing the texture.
+///
+/// @return The name of the font as defined in the font XML.
++ (NSString *)registerBitmapFontFromFile:(NSString*)path texture:(SPTexture *)texture;
+
+/// Makes a bitmap font available at any text field, using the texture defined in the file.
+///
+/// @return The name of the font as defined in the font XML.
++ (NSString *)registerBitmapFontFromFile:(NSString*)path;
+
+/// Releases the bitmap font.
++ (void)unregisterBitmapFont:(NSString *)name;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The displayed text.
+@property (nonatomic, copy) NSString *text;
+
+/// The name of the font.
+@property (nonatomic, copy) NSString *fontName;
+
+/// The size of the font. For bitmap fonts, use `SP_NATIVE_FONT_SIZE` for the original size.
+@property (nonatomic, assign) float fontSize;
+
+/// The horizontal alignment of the text.
+@property (nonatomic, assign) SPHAlign hAlign;
+
+/// The vertical alignment of the text.
+@property (nonatomic, assign) SPVAlign vAlign;
+
+/// Allows displaying a border around the edges of the text field. Useful for visual debugging.
+@property (nonatomic, assign) BOOL border;
+
+/// The color of the text.
+@property (nonatomic, assign) uint color;
+
+/// The bounds of the actual characters inside the text field.
+@property (nonatomic, readonly) SPRectangle *textBounds;
+
+@end
--- /dev/null
+//
+// SPTextField.m
+// Sparrow
+//
+// Created by Daniel Sperl on 29.06.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 "SPTextField.h"
+#import "SPImage.h"
+#import "SPTexture.h"
+#import "SPSubTexture.h"
+#import "SPGLTexture.h"
+#import "SPEnterFrameEvent.h"
+#import "SPQuad.h"
+#import "SPBitmapFont.h"
+#import "SPStage.h"
+
+#import <UIKit/UIKit.h>
+
+static NSMutableDictionary *bitmapFonts = nil;
+
+// --- private interface ---------------------------------------------------------------------------
+
+@interface SPTextField()
+
+- (void)redrawContents;
+- (SPDisplayObject *)createRenderedContents;
+- (SPDisplayObject *)createComposedContents;
+
+@end
+
+// --- class implementation ------------------------------------------------------------------------
+
+@implementation SPTextField
+
+@synthesize text = mText;
+@synthesize fontName = mFontName;
+@synthesize fontSize = mFontSize;
+@synthesize hAlign = mHAlign;
+@synthesize vAlign = mVAlign;
+@synthesize border = mBorder;
+@synthesize color = mColor;
+
+- (id)initWithWidth:(float)width height:(float)height text:(NSString*)text fontName:(NSString*)name
+ fontSize:(float)size color:(uint)color
+{
+ if (self = [super init])
+ {
+ mText = [text copy];
+ mFontSize = size;
+ mColor = color;
+ mHAlign = SPHAlignCenter;
+ mVAlign = SPVAlignCenter;
+ mBorder = NO;
+ self.fontName = name;
+
+ mHitArea = [[SPQuad alloc] initWithWidth:width height:height];
+ mHitArea.alpha = 0.0f;
+ [self addChild:mHitArea];
+ [mHitArea release];
+
+ mTextArea = [[SPQuad alloc] initWithWidth:width height:height];
+ mTextArea.visible = NO;
+ [self addChild:mTextArea];
+ [mTextArea release];
+
+ mRequiresRedraw = YES;
+ }
+ return self;
+}
+
+- (id)initWithWidth:(float)width height:(float)height text:(NSString*)text;
+{
+ return [self initWithWidth:width height:height text:text fontName:SP_DEFAULT_FONT_NAME
+ fontSize:SP_DEFAULT_FONT_SIZE color:SP_DEFAULT_FONT_COLOR];
+}
+
+- (id)initWithWidth:(float)width height:(float)height
+{
+ return [self initWithWidth:width height:height text:@""];
+}
+
+- (id)initWithText:(NSString *)text
+{
+ return [self initWithWidth:128 height:128 text:text];
+}
+
+- (id)init
+{
+ return [self initWithText:@""];
+}
+
+- (void)render:(SPRenderSupport *)support
+{
+ if (mRequiresRedraw) [self redrawContents];
+ [super render:support];
+}
+
+- (void)redrawContents
+{
+ [mContents removeFromParent];
+
+ mContents = mIsRenderedText ? [self createRenderedContents] : [self createComposedContents];
+ mContents.touchable = NO;
+ mRequiresRedraw = NO;
+
+ [self addChild:mContents];
+}
+
+- (SPDisplayObject *)createRenderedContents
+{
+ float width = mHitArea.width;
+ float height = mHitArea.height;
+ float fontSize = mFontSize == SP_NATIVE_FONT_SIZE ? SP_DEFAULT_FONT_SIZE : mFontSize;
+
+ UILineBreakMode lbm = UILineBreakModeTailTruncation;
+ CGSize textSize = [mText sizeWithFont:[UIFont fontWithName:mFontName size:fontSize]
+ constrainedToSize:CGSizeMake(width, height) lineBreakMode:lbm];
+
+ float xOffset = 0;
+ if (mHAlign == SPHAlignCenter) xOffset = (width - textSize.width) / 2.0f;
+ else if (mHAlign == SPHAlignRight) xOffset = width - textSize.width;
+
+ float yOffset = 0;
+ if (mVAlign == SPVAlignCenter) yOffset = (height - textSize.height) / 2.0f;
+ else if (mVAlign == SPVAlignBottom) yOffset = height - textSize.height;
+
+ mTextArea.x = xOffset;
+ mTextArea.y = yOffset;
+ mTextArea.width = textSize.width;
+ mTextArea.height = textSize.height;
+
+ SPTexture *texture = [[SPTexture alloc] initWithWidth:width height:height
+ scale:[SPStage contentScaleFactor]
+ colorSpace:SPColorSpaceAlpha
+ draw:^(CGContextRef context)
+ {
+ if (mBorder)
+ {
+ CGContextSetGrayStrokeColor(context, 1.0f, 1.0f);
+ CGContextSetLineWidth(context, 1.0f);
+ CGContextStrokeRect(context, CGRectMake(0.5f, 0.5f, width-1, height-1));
+ }
+
+ CGContextSetGrayFillColor(context, 1.0f, 1.0f);
+
+ [mText drawInRect:CGRectMake(0, yOffset, width, height)
+ withFont:[UIFont fontWithName:mFontName size:fontSize]
+ lineBreakMode:lbm alignment:mHAlign];
+ }];
+
+ SPImage *image = [SPImage imageWithTexture:texture];
+ image.color = mColor;
+ [texture release];
+
+ return image;
+}
+
+- (SPDisplayObject *)createComposedContents
+{
+ SPBitmapFont *bitmapFont = [bitmapFonts objectForKey:mFontName];
+ if (!bitmapFont)
+ [NSException raise:SP_EXC_INVALID_OPERATION
+ format:@"bitmap font %@ not registered!", mFontName];
+
+ SPDisplayObject *contents = [bitmapFont createDisplayObjectWithWidth:mHitArea.width
+ height:mHitArea.height text:mText fontSize:mFontSize color:mColor
+ hAlign:mHAlign vAlign:mVAlign border:mBorder];
+
+ SPRectangle *textBounds = [(SPDisplayObjectContainer *)contents childAtIndex:0].bounds;
+ mTextArea.x = textBounds.x; mTextArea.y = textBounds.y;
+ mTextArea.width = textBounds.width; mTextArea.height = textBounds.height;
+
+ return contents;
+}
+
+- (SPRectangle *)textBounds
+{
+ if (mRequiresRedraw) [self redrawContents];
+ return [mTextArea boundsInSpace:self.parent];
+}
+
+- (SPRectangle*)boundsInSpace:(SPDisplayObject*)targetCoordinateSpace
+{
+ return [mHitArea boundsInSpace:targetCoordinateSpace];
+}
+
+- (void)setWidth:(float)width
+{
+ // other than in SPDisplayObject, changing the size of the object should not change the scaling;
+ // changing the size should just make the texture bigger/smaller,
+ // keeping the size of the text/font unchanged. (this applies to setHeight:, as well.)
+
+ mHitArea.width = width;
+ mRequiresRedraw = YES;
+}
+
+- (void)setHeight:(float)height
+{
+ mHitArea.height = height;
+ mRequiresRedraw = YES;
+}
+
+- (void)setText:(NSString *)text
+{
+ if (![text isEqualToString:mText])
+ {
+ [mText release];
+ mText = [text copy];
+ mRequiresRedraw = YES;
+ }
+}
+
+- (void)setFontName:(NSString *)fontName
+{
+ if (![fontName isEqualToString:mFontName])
+ {
+ [mFontName release];
+ mFontName = [fontName copy];
+ mRequiresRedraw = YES;
+ mIsRenderedText = ![bitmapFonts objectForKey:mFontName];
+ }
+}
+
+- (void)setFontSize:(float)fontSize
+{
+ if (fontSize != mFontSize)
+ {
+ mFontSize = fontSize;
+ mRequiresRedraw = YES;
+ }
+}
+
+- (void)setBorder:(BOOL)border
+{
+ if (border != mBorder)
+ {
+ mBorder = border;
+ mRequiresRedraw = YES;
+ }
+}
+
+- (void)setHAlign:(SPHAlign)hAlign
+{
+ if (hAlign != mHAlign)
+ {
+ mHAlign = hAlign;
+ mRequiresRedraw = YES;
+ }
+}
+
+- (void)setVAlign:(SPVAlign)vAlign
+{
+ if (vAlign != mVAlign)
+ {
+ mVAlign = vAlign;
+ mRequiresRedraw = YES;
+ }
+}
+
+- (void)setColor:(uint)color
+{
+ if (color != mColor)
+ {
+ mColor = color;
+ if (mIsRenderedText)
+ [(SPImage *)mContents setColor:color];
+ else
+ mRequiresRedraw = YES;
+ }
+}
+
++ (SPTextField*)textFieldWithWidth:(float)width height:(float)height text:(NSString*)text
+ fontName:(NSString*)name fontSize:(float)size color:(uint)color
+{
+ return [[[SPTextField alloc] initWithWidth:width height:height text:text fontName:name
+ fontSize:size color:color] autorelease];
+}
+
++ (SPTextField*)textFieldWithWidth:(float)width height:(float)height text:(NSString*)text
+{
+ return [[[SPTextField alloc] initWithWidth:width height:height text:text] autorelease];
+}
+
++ (SPTextField*)textFieldWithText:(NSString*)text
+{
+ return [[[SPTextField alloc] initWithText:text] autorelease];
+}
+
++ (NSString *)registerBitmapFontFromFile:(NSString*)path texture:(SPTexture *)texture
+{
+ if (!bitmapFonts) bitmapFonts = [[NSMutableDictionary alloc] init];
+
+ SPBitmapFont *bitmapFont = [[SPBitmapFont alloc] initWithContentsOfFile:path texture:texture];
+ NSString *fontName = bitmapFont.name;
+ [bitmapFonts setObject:bitmapFont forKey:fontName];
+ [bitmapFont release];
+
+ return fontName;
+}
+
++ (NSString *)registerBitmapFontFromFile:(NSString *)path
+{
+ return [SPTextField registerBitmapFontFromFile:path texture:nil];
+}
+
++ (void)unregisterBitmapFont:(NSString *)name
+{
+ [bitmapFonts removeObjectForKey:name];
+
+ if (bitmapFonts.count == 0)
+ {
+ [bitmapFonts release];
+ bitmapFonts = nil;
+ }
+}
+
+- (void)dealloc
+{
+ [mText release];
+ [mFontName release];
+ [super dealloc];
+}
+
+@end
--- /dev/null
+//
+// SPTexture.h
+// Sparrow
+//
+// Created by Daniel Sperl on 19.06.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 <UIKit/UIKit.h>
+#import <QuartzCore/QuartzCore.h>
+
+@class SPRectangle;
+
+typedef enum
+{
+ SPColorSpaceRGBA,
+ SPColorSpaceAlpha
+} SPColorSpace;
+
+typedef void (^SPTextureDrawingBlock)(CGContextRef context);
+
+/** ------------------------------------------------------------------------------------------------
+
+ A texture stores the information that represents an image. It cannot be displayed directly,
+ but has to be mapped onto a display object. In Sparrow, that display object is `SPImage`.
+
+ *Image formats*
+
+ Sparrow supports different file formats for textures. The most common formats are `PNG`, which
+ contains an alpha channel, and `JPG` (without an alpha channel). You can also load files in
+ the `PVR` format (compressed or uncompressed). That's a special format of the graphics chip of
+ iOS devices that is very efficient.
+
+ *HD textures*
+
+ Furthermore, Sparrow supports development in multiple resolutions, i.e. creating a game
+ simultaneously for normal and retina displays. If HD texture support is activated (via
+ `[SPStage setSupportHighResolutions:]`) and you load a texture like this:
+
+ SPTexture *texture = [[SPTexture alloc] initWithContentsOfFile:@"image.png"];
+
+ Sparrow will check if it finds the file `"image@2x.png"`, and will load that instead (provided that
+ it is available and the application is running on a HD device). The texture object will then
+ return values for width and height that are the original number of pixels divided by 2
+ (by setting their scale-factor to `2.0`). That way, you will always work with the same values
+ for width and height - regardless of the device type.
+
+ *Drawing API*
+
+ Sparrow lets you create custom graphics directly at run-time by using the `Core Graphics` API.
+ You access the drawing API with a special init-method of SPTexture, which takes a `block`-parameter
+ you can fill with your drawing code.
+
+ SPTexture *customTexture = [[SPTexture alloc] initWithWidth:200 height:100
+ draw:^(CGContextRef context)
+ {
+ // draw a string
+ CGContextSetGrayFillColor(context, 1.0f, 1.0f);
+ NSString *string = @"Hello Core Graphics";
+ [string drawAtPoint:CGPointMake(20.0f, 20.0f)
+ withFont:[UIFont fontWithName:@"Arial" size:25]];
+ }];
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPTexture : NSObject
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a texture with a certain size (in points) and a block containing Core Graphics commands.
+/// The texture will have the current scale factor of the stage and an RGBA color space.
+- (id)initWithWidth:(float)width height:(float)height draw:(SPTextureDrawingBlock)drawingBlock;
+
+/// Initializes a texture with size and color properties, as well as a block containing
+/// Core Graphics commands.
+- (id)initWithWidth:(float)width height:(float)height scale:(float)scale
+ colorSpace:(SPColorSpace)colorSpace draw:(SPTextureDrawingBlock)drawingBlock;
+
+/// Initializes a texture with the contents of a file. Recommended formats: png, jpg, pvr.
+- (id)initWithContentsOfFile:(NSString *)path;
+
+/// Initializes a texture with the contents of a UIImage.
+- (id)initWithContentsOfImage:(UIImage *)image;
+
+/// Initializes a texture with a region (in points) of another texture.
+- (id)initWithRegion:(SPRectangle*)region ofTexture:(SPTexture*)texture;
+
+/// Factory method.
++ (SPTexture *)textureWithContentsOfFile:(NSString*)path;
+
+/// Factory method.
++ (SPTexture *)textureWithRegion:(SPRectangle *)region ofTexture:(SPTexture *)texture;
+
+/// Factory method.
++ (SPTexture *)textureWithWidth:(float)width height:(float)height draw:(SPTextureDrawingBlock)drawingBlock;
+
+/// Factory method. Creates an empty (white) texture.
++ (SPTexture *)emptyTexture;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Convertes texture coordinates in the format required for rendering.
+- (void)adjustTextureCoordinates:(const float *)texCoords saveAtTarget:(float *)targetTexCoords
+ numVertices:(int)numVertices;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The width of the image in points.
+@property (nonatomic, readonly) float width;
+
+/// The height of the image in points.
+@property (nonatomic, readonly) float height;
+
+/// The OpenGL texture ID.
+@property (nonatomic, readonly) uint textureID;
+
+/// Indicates if the alpha values are premultiplied into the RGB values.
+@property (nonatomic, readonly) BOOL hasPremultipliedAlpha;
+
+/// The scale factor, which influences `width` and `height` properties.
+@property (nonatomic, readonly) float scale;
+
+@end
--- /dev/null
+//
+// SPTexture.m
+// Sparrow
+//
+// Created by Daniel Sperl on 19.06.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 "SPTexture.h"
+#import "SPMacros.h"
+#import "SPUtils.h"
+#import "SPRectangle.h"
+#import "SPGLTexture.h"
+#import "SPSubTexture.h"
+#import "SPNSExtensions.h"
+#import "SPStage.h"
+
+// --- PVRTC structs & enums -----------------------------------------------------------------------
+
+typedef struct
+{
+ uint headerSize; // size of the structure
+ uint height; // height of surface to be created
+ uint width; // width of input surface
+ uint numMipmaps; // number of mip-map levels requested
+ uint pfFlags; // pixel format flags
+ uint textureDataSize; // total size in bytes
+ uint bitCount; // number of bits per pixel
+ uint rBitMask; // mask for red bit
+ uint gBitMask; // mask for green bits
+ uint bBitMask; // mask for blue bits
+ uint alphaBitMask; // mask for alpha channel
+ uint pvr; // magic number identifying pvr file
+ uint numSurfs; // number of surfaces present in the pvr
+} PVRTextureHeader;
+
+enum PVRPixelType
+{
+ OGL_RGBA_4444 = 0x10,
+ OGL_RGBA_5551,
+ OGL_RGBA_8888,
+ OGL_RGB_565,
+ OGL_RGB_555,
+ OGL_RGB_888,
+ OGL_I_8,
+ OGL_AI_88,
+ OGL_PVRTC2,
+ OGL_PVRTC4
+};
+
+// --- private interface ---------------------------------------------------------------------------
+
+@interface SPTexture ()
+
+- (id)initWithContentsOfPvrFile:(NSString *)path;
+
+@end
+
+// --- class implementation ------------------------------------------------------------------------
+
+@implementation SPTexture
+
+- (id)init
+{
+ if ([self isMemberOfClass:[SPTexture class]])
+ {
+ [self release];
+ [NSException raise:SP_EXC_ABSTRACT_CLASS
+ format:@"Attempting to initialize abstract class SPTexture."];
+ return nil;
+ }
+
+ return [super init];
+}
+
+- (id)initWithContentsOfFile:(NSString *)path
+{
+ float contentScaleFactor = [SPStage contentScaleFactor];
+
+ NSString *fullPath = [path isAbsolutePath] ?
+ path : [[NSBundle mainBundle] pathForResource:path withScaleFactor:contentScaleFactor];
+
+ if (![[NSFileManager defaultManager] fileExistsAtPath:fullPath])
+ {
+ [self release];
+ [NSException raise:SP_EXC_FILE_NOT_FOUND format:@"file '%@' not found", path];
+ }
+
+ NSString *imgType = [[path pathExtension] lowercaseString];
+ if ([imgType rangeOfString:@"pvr"].location == 0)
+ return [self initWithContentsOfPvrFile:fullPath];
+ else
+ return [self initWithContentsOfImage:[UIImage imageWithContentsOfFile:fullPath]];
+}
+
+- (id)initWithWidth:(float)width height:(float)height draw:(SPTextureDrawingBlock)drawingBlock
+{
+ return [self initWithWidth:width height:height scale:[SPStage contentScaleFactor]
+ colorSpace:SPColorSpaceRGBA draw:drawingBlock];
+}
+
+- (id)initWithWidth:(float)width height:(float)height scale:(float)scale
+ colorSpace:(SPColorSpace)colorSpace draw:(SPTextureDrawingBlock)drawingBlock
+{
+ [self release]; // class factory - we'll return a subclass!
+
+ // only textures with sides that are powers of 2 are allowed by OpenGL ES.
+ int legalWidth = [SPUtils nextPowerOfTwo:width * scale];
+ int legalHeight = [SPUtils nextPowerOfTwo:height * scale];
+
+ SPTextureFormat textureFormat;
+ CGColorSpaceRef cgColorSpace;
+ CGBitmapInfo bitmapInfo;
+ BOOL premultipliedAlpha;
+ int bytesPerPixel;
+
+ if (colorSpace == SPColorSpaceRGBA)
+ {
+ bytesPerPixel = 4;
+ textureFormat = SPTextureFormatRGBA;
+ cgColorSpace = CGColorSpaceCreateDeviceRGB();
+ bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
+ premultipliedAlpha = YES;
+ }
+ else
+ {
+ bytesPerPixel = 1;
+ textureFormat = SPTextureFormatAlpha;
+ cgColorSpace = CGColorSpaceCreateDeviceGray();
+ bitmapInfo = kCGImageAlphaNone;
+ premultipliedAlpha = NO;
+ }
+
+ void *imageData = calloc(legalWidth * legalHeight * bytesPerPixel, 1);
+ CGContextRef context = CGBitmapContextCreate(imageData, legalWidth, legalHeight, 8,
+ bytesPerPixel * legalWidth, cgColorSpace,
+ bitmapInfo);
+ CGColorSpaceRelease(cgColorSpace);
+
+ // UIKit referential is upside down - we flip it and apply the scale factor
+ CGContextTranslateCTM(context, 0.0f, legalHeight);
+ CGContextScaleCTM(context, scale, -scale);
+
+ if (drawingBlock)
+ {
+ UIGraphicsPushContext(context);
+ drawingBlock(context);
+ UIGraphicsPopContext();
+ }
+
+ SPTextureProperties properties = {
+ .format = textureFormat,
+ .width = legalWidth,
+ .height = legalHeight,
+ .generateMipmaps = YES,
+ .premultipliedAlpha = premultipliedAlpha
+ };
+
+ SPGLTexture *glTexture = [[SPGLTexture alloc] initWithData:imageData properties:properties];
+ glTexture.scale = scale;
+
+ CGContextRelease(context);
+ free(imageData);
+
+ SPRectangle *region = [SPRectangle rectangleWithX:0 y:0 width:width height:height];
+ SPTexture *subTexture = [[SPTexture alloc] initWithRegion:region ofTexture:glTexture];
+ [glTexture release];
+ return subTexture;
+}
+
+- (id)initWithContentsOfImage:(UIImage *)image
+{
+ float scale = [image respondsToSelector:@selector(scale)] ? [image scale] : 1.0f;
+
+ return [self initWithWidth:image.size.width height:image.size.height
+ scale:scale colorSpace:SPColorSpaceRGBA draw:^(CGContextRef context)
+ {
+ [image drawAtPoint:CGPointMake(0, 0)];
+ }];
+}
+
+- (id)initWithContentsOfPvrFile:(NSString*)path
+{
+ [self release]; // class factory - we'll return a subclass!
+
+ NSData *fileData = [[NSData alloc] initWithContentsOfFile:path];
+ PVRTextureHeader *header = (PVRTextureHeader *)[fileData bytes];
+ bool hasAlpha = header->alphaBitMask ? YES : NO;
+
+ SPTextureProperties properties = {
+ .width = header->width,
+ .height = header->height,
+ .numMipmaps = header->numMipmaps,
+ .premultipliedAlpha = NO
+ };
+
+ switch (header->pfFlags & 0xff)
+ {
+ case OGL_RGB_565:
+ properties.format = SPTextureFormat565;
+ break;
+ case OGL_RGBA_5551:
+ properties.format = SPTextureFormat5551;
+ break;
+ case OGL_RGBA_4444:
+ properties.format = SPTextureFormat4444;
+ break;
+ case OGL_PVRTC2:
+ properties.format = hasAlpha ? SPTextureFormatPvrtcRGBA2 : SPTextureFormatPvrtcRGB2;
+ break;
+ case OGL_PVRTC4:
+ properties.format = hasAlpha ? SPTextureFormatPvrtcRGBA4 : SPTextureFormatPvrtcRGB4;
+ break;
+ default:
+ [fileData release];
+ [NSException raise:SP_EXC_INVALID_OPERATION format:@"Unsupported PRV image format"];
+ return nil;
+ }
+
+ void *imageData = (unsigned char *)header + header->headerSize;
+
+ SPGLTexture *glTexture = [[SPGLTexture alloc] initWithData:imageData properties:properties];
+ [fileData release];
+
+ NSString *baseFilename = [[path lastPathComponent] stringByDeletingPathExtension];
+ if ([baseFilename rangeOfString:@"@2x"].location == baseFilename.length - 3)
+ glTexture.scale = 2.0f;
+
+ return glTexture;
+}
+
+- (id)initWithRegion:(SPRectangle*)region ofTexture:(SPTexture*)texture
+{
+ [self release]; // class factory - we'll return a subclass!
+
+ if (region.x == 0.0f && region.y == 0.0f &&
+ region.width == texture.width && region.height == texture.height)
+ {
+ return [texture retain];
+ }
+ else
+ {
+ return [[SPSubTexture alloc] initWithRegion:region ofTexture:texture];
+ }
+}
+
++ (SPTexture *)emptyTexture
+{
+ return [[[SPGLTexture alloc] init] autorelease];
+}
+
++ (SPTexture *)textureWithContentsOfFile:(NSString *)path
+{
+ return [[[SPTexture alloc] initWithContentsOfFile:path] autorelease];
+}
+
++ (SPTexture *)textureWithRegion:(SPRectangle *)region ofTexture:(SPTexture *)texture
+{
+ return [[[SPTexture alloc] initWithRegion:region ofTexture:texture] autorelease];
+}
+
++ (SPTexture *)textureWithWidth:(float)width height:(float)height draw:(SPTextureDrawingBlock)drawingBlock
+{
+ return [[[SPTexture alloc] initWithWidth:width height:height draw:drawingBlock] autorelease];
+}
+
+- (void)adjustTextureCoordinates:(const float *)texCoords saveAtTarget:(float *)targetTexCoords
+ numVertices:(int)numVertices
+{
+ memcpy(targetTexCoords, texCoords, numVertices * 2 * sizeof(float));
+}
+
+- (float)width
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+ return 0;
+}
+
+- (float)height
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+ return 0;
+}
+
+- (uint)textureID
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+ return 0;
+}
+
+- (BOOL)hasPremultipliedAlpha
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+ return NO;
+}
+
+- (float)scale
+{
+ [NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override this method in subclasses."];
+ return 1.0f;
+}
+
+@end
--- /dev/null
+//
+// SPTextureAtlas.h
+// Sparrow
+//
+// Created by Daniel Sperl on 27.06.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>
+
+@class SPTexture;
+@class SPRectangle;
+
+/** ------------------------------------------------------------------------------------------------
+
+ A texture atlas is a collection of many smaller textures in one big image. The class
+ `SPTextureAtlas` is used to access textures from such an atlas.
+
+ Using a texture atlas for your textures has two main advantages:
+
+ * In OpenGL, there’s always one texture active at a given moment. Whenever you change the active
+ texture, a "texture-switch" has to be executed, and that switch takes time.
+ * To use a texture in OpenGL, its height and width must each be a power of 2. Sparrow hides this
+ limitation from you, but you will nevertheless use more memory if you do not follow that rule.
+
+ By using a texture atlas, you avoid both texture switches and the power-of-two limitation. All
+ textures are within one big "super-texture", and Sparrow takes care that the correct part of this
+ texture is displayed.
+
+ There are several ways to create a texture atlas. One is to use the atlas generator script that
+ is provided with Sparrow. Here is a sample on how to use it:
+
+ # creates "atlas.xml" and "atlas.png" from the provided images
+ ./generate_atlas.rb input/*.png output/atlas.xml
+
+ The atlas generator can be found in the 'utils' directory in the Sparrow package. A README file
+ shows you how to install and use it. If you want to have more control over your atlas, you will
+ find great alternative tools on the Internet (e.g. "Texture Packer").
+
+ Whatever tool you use, Sparrow expects the following file format:
+
+ <TextureAtlas imagePath='atlas.png'>
+ <SubTexture name='texture_1' x='0' y='0' height='50' width='50'/>
+ <SubTexture name='texture_2' x='50' y='0' height='30' width='20'/>
+ </TextureAtlas>
+
+------------------------------------------------------------------------------------------------- */
+
+#ifdef __IPHONE_4_0
+@interface SPTextureAtlas : NSObject <NSXMLParserDelegate>
+#else
+@interface SPTextureAtlas : NSObject
+#endif
+{
+ @private
+ SPTexture *mAtlasTexture;
+ NSMutableDictionary *mTextureRegions;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a texture atlas from an XML file and a custom texture. _Designated Initializer_.
+- (id)initWithContentsOfFile:(NSString *)path texture:(SPTexture *)texture;
+
+/// Initializes a texture atlas from an XML file, loading the texture that is specified in the XML.
+- (id)initWithContentsOfFile:(NSString *)path;
+
+/// Initializes a teture atlas from a texture. Add the regions manually with `addName:forRegion:`.
+- (id)initWithTexture:(SPTexture *)texture;
+
+/// Factory Method.
++ (SPTextureAtlas *)atlasWithContentsOfFile:(NSString *)path;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Retrieve a subtexture by name. Returns `nil` if it is not found.
+- (SPTexture *)textureByName:(NSString *)name;
+
+/// Returns all textures that start with a certain string, sorted alphabetically
+/// (especially useful for `SPMovieClip`).
+- (NSArray *)texturesStartingWith:(NSString *)name;
+
+/// Creates a region for a subtexture and gives it a name.
+- (void)addRegion:(SPRectangle *)region withName:(NSString *)name;
+
+/// Removes a region with a certain name.
+- (void)removeRegion:(NSString *)name;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The number of available subtextures.
+@property (nonatomic, readonly) int count;
+
+@end
--- /dev/null
+//
+// SPTextureAtlas.m
+// Sparrow
+//
+// Created by Daniel Sperl on 27.06.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 "SPTextureAtlas.h"
+#import "SPMacros.h"
+#import "SPTexture.h"
+#import "SPSubTexture.h"
+#import "SPGLTexture.h"
+#import "SPRectangle.h"
+#import "SPNSExtensions.h"
+#import "SPStage.h"
+
+// --- private interface ---------------------------------------------------------------------------
+
+@interface SPTextureAtlas()
+
+- (void)parseAtlasXml:(NSString*)path;
+
+@end
+
+// --- class implementation ------------------------------------------------------------------------
+
+@implementation SPTextureAtlas
+
+- (id)initWithContentsOfFile:(NSString *)path texture:(SPTexture *)texture
+{
+ if (self = [super init])
+ {
+ mTextureRegions = [[NSMutableDictionary alloc] init];
+ mAtlasTexture = [texture retain];
+ [self parseAtlasXml:path];
+ }
+ return self;
+}
+
+- (id)initWithContentsOfFile:(NSString *)path
+{
+ return [self initWithContentsOfFile:path texture:nil];
+}
+
+- (id)initWithTexture:(SPTexture *)texture
+{
+ return [self initWithContentsOfFile:nil texture:(SPTexture *)texture];
+}
+
+- (id)init
+{
+ return [self initWithContentsOfFile:nil texture:nil];
+}
+
+- (void)parseAtlasXml:(NSString *)path
+{
+ SP_CREATE_POOL(pool);
+
+ 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 texture atlas %@. 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:@"SubTexture"])
+ {
+ float scale = mAtlasTexture.scale;
+
+ NSString *name = [attributeDict valueForKey:@"name"];
+ float x = [[attributeDict valueForKey:@"x"] floatValue] / scale;
+ float y = [[attributeDict valueForKey:@"y"] floatValue] / scale;
+ float width = [[attributeDict valueForKey:@"width"] floatValue] / scale;
+ float height = [[attributeDict valueForKey:@"height"] floatValue] / scale;
+
+ [self addRegion:[SPRectangle rectangleWithX:x y:y width:width height:height] withName:name];
+ }
+ else if ([elementName isEqualToString:@"TextureAtlas"] && !mAtlasTexture)
+ {
+ // load atlas texture
+ NSString *imagePath = [attributeDict valueForKey:@"imagePath"];
+ mAtlasTexture = [[SPTexture alloc] initWithContentsOfFile:imagePath];
+ }
+}
+
+- (int)count
+{
+ return [mTextureRegions count];
+}
+
+- (SPTexture *)textureByName:(NSString *)name
+{
+ SPRectangle *region = [mTextureRegions objectForKey:name];
+ if (!region) return nil;
+ return [SPSubTexture textureWithRegion:region ofTexture:mAtlasTexture];
+}
+
+- (NSArray *)texturesStartingWith:(NSString *)name
+{
+ NSMutableArray *textureNames = [[NSMutableArray alloc] init];
+
+ for (NSString *textureName in mTextureRegions)
+ if ([textureName rangeOfString:name].location == 0)
+ [textureNames addObject:textureName];
+
+ // note: when switching to iOS 4, 'localizedStandardCompare:' would be preferable
+ [textureNames sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
+
+ NSMutableArray *textures = [NSMutableArray arrayWithCapacity:textureNames.count];
+ for (NSString *textureName in textureNames)
+ [textures addObject:[self textureByName:textureName]];
+
+ [textureNames release];
+ return textures;
+}
+
+- (void)addRegion:(SPRectangle *)region withName:(NSString *)name
+{
+ [mTextureRegions setObject:region forKey:name];
+}
+
+- (void)removeRegion:(NSString *)name
+{
+ [mTextureRegions removeObjectForKey:name];
+}
+
++ (SPTextureAtlas *)atlasWithContentsOfFile:(NSString *)path
+{
+ return [[[SPTextureAtlas alloc] initWithContentsOfFile:path] autorelease];
+}
+
+- (void)dealloc
+{
+ [mAtlasTexture release];
+ [mTextureRegions release];
+ [super dealloc];
+}
+
+@end
--- /dev/null
+//
+// SPTouch.h
+// Sparrow
+//
+// Created by Daniel Sperl on 01.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>
+
+@class SPDisplayObject;
+@class SPPoint;
+
+/// SPTouchPhase describes the phases in the life-cycle of a touch.
+typedef enum
+{
+ SPTouchPhaseBegan, /// The finger just touched the screen.
+ SPTouchPhaseMoved, /// The finger moves around.
+ SPTouchPhaseStationary, /// The finger has not moved since the last frame.
+ SPTouchPhaseEnded, /// The finger was lifted from the screen.
+ SPTouchPhaseCancelled /// The touch was aborted by the system (e.g. because of an AlertBox popping up)
+} SPTouchPhase;
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPTouch contains information about the presence or movement of a finger on the screen.
+
+ You receive objects of this type via an SPTouchEvent. When such an event is triggered, you can
+ query it for all touches that are currently present on the screen. One SPTouch object contains
+ information about a single touch.
+
+ *The phase of a touch*
+
+ Each touch normally moves through the following phases in its life:
+
+ `Began -> Moved -> Ended`
+
+ Furthermore, a touch can enter a `Stationary` phase. That phase will not trigger an event itself,
+ but you might receive it when another touch does so. Finally, there's the `Cancelled` phase,
+ which happens when the system aborts a touch (e.g. because of an AlertBox that pops up).
+
+ *The position of a touch*
+
+ You can get the current and last position on the screen with corresponding properties. However,
+ you'll want to have the position in a different coordinate system most of the time.
+ For this reason, there are methods that convert the current and previous touches into the local
+ coordinate system of any object.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPTouch : NSObject
+{
+ @private
+ double mTimestamp;
+ float mGlobalX;
+ float mGlobalY;
+ float mPreviousGlobalX;
+ float mPreviousGlobalY;
+ int mTapCount;
+ SPTouchPhase mPhase;
+ SPDisplayObject *mTarget;
+}
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Converts the current location of a touch to the local coordinate system of a display object.
+- (SPPoint*)locationInSpace:(SPDisplayObject*)space;
+
+/// Converts the previous location of a touch to the local coordinate system of a display object.
+- (SPPoint*)previousLocationInSpace:(SPDisplayObject*)space;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The moment the event occurred (in seconds since application start).
+@property (nonatomic, readonly) double timestamp;
+
+/// The x-position of the touch in screen coordinates
+@property (nonatomic, readonly) float globalX;
+
+/// The y-position of the touch in screen coordinates
+@property (nonatomic, readonly) float globalY;
+
+/// The previous x-position of the touch in screen coordinates
+@property (nonatomic, readonly) float previousGlobalX;
+
+/// The previous y-position of the touch in screen coordinates
+@property (nonatomic, readonly) float previousGlobalY;
+
+/// The number of taps the finger made in a short amount of time. Use this to detect double-taps, etc.
+@property (nonatomic, readonly) int tapCount;
+
+/// The current phase the touch is in.
+@property (nonatomic, readonly) SPTouchPhase phase;
+
+/// The display object at which the touch occurred.
+@property (nonatomic, readonly) SPDisplayObject *target;
+
+@end
--- /dev/null
+//
+// SPTouch.m
+// Sparrow
+//
+// Created by Daniel Sperl on 01.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 "SPTouch.h"
+#import "SPTouch_Internal.h"
+#import "SPDisplayObject.h"
+#import "SPPoint.h"
+
+@implementation SPTouch
+
+@synthesize timestamp = mTimestamp;
+@synthesize globalX = mGlobalX;
+@synthesize globalY = mGlobalY;
+@synthesize previousGlobalX = mPreviousGlobalX;
+@synthesize previousGlobalY = mPreviousGlobalY;
+@synthesize tapCount = mTapCount;
+@synthesize phase = mPhase;
+@synthesize target = mTarget;
+
+- (id)init
+{
+ return [super init];
+}
+
+- (SPPoint*)locationInSpace:(SPDisplayObject*)space
+{
+ SPPoint *point = [SPPoint pointWithX:mGlobalX y:mGlobalY];
+ SPMatrix *transformationMatrix = [mTarget.root transformationMatrixToSpace:space];
+ return [transformationMatrix transformPoint:point];
+}
+
+- (SPPoint*)previousLocationInSpace:(SPDisplayObject*)space
+{
+ SPPoint *point = [SPPoint pointWithX:mPreviousGlobalX y:mPreviousGlobalY];
+ SPMatrix *transformationMatrix = [mTarget.root transformationMatrixToSpace:space];
+ return [transformationMatrix transformPoint:point];
+}
+
+- (void)dealloc
+{
+ [mTarget release];
+ [super dealloc];
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPTouch (Internal)
+
+- (void)setTimestamp:(double)timestamp
+{
+ mTimestamp = timestamp;
+}
+
+- (void)setGlobalX:(float)x
+{
+ mGlobalX = x;
+}
+
+- (void)setGlobalY:(float)y
+{
+ mGlobalY = y;
+}
+
+- (void)setPreviousGlobalX:(float)x
+{
+ mPreviousGlobalX = x;
+}
+
+- (void)setPreviousGlobalY:(float)y
+{
+ mPreviousGlobalY = y;
+}
+
+- (void)setTapCount:(int)tapCount
+{
+ mTapCount = tapCount;
+}
+
+- (void)setPhase:(SPTouchPhase)phase
+{
+ mPhase = phase;
+}
+
+- (void)setTarget:(SPDisplayObject*)target
+{
+ if (target != mTarget)
+ {
+ [mTarget release];
+ mTarget = [target retain];
+ }
+}
+
++ (SPTouch*)touch
+{
+ return [[[SPTouch alloc] init] autorelease];
+}
+
+@end
+
--- /dev/null
+//
+// SPTouchEvent.h
+// Sparrow
+//
+// Created by Daniel Sperl on 02.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>
+#import "SPEvent.h"
+#import "SPTouch.h"
+
+@class SPDisplayObject;
+
+#define SP_EVENT_TYPE_TOUCH @"touch"
+
+/** ------------------------------------------------------------------------------------------------
+
+ When one or more fingers touch the screen, move around or are raised, an SPTouchEvent is triggered.
+
+ The event contains a list of all touches that are currently present. Since you are normally only
+ interested in those touches that occurred on top of certain objects, you can query the event for
+ touches with certain targets.
+
+ In this context, the target of a touch is not only the object that was touched (e.g. an SPImage),
+ but also each of its parents - e.g. the container that holds that image.
+
+ Here is an example of how to react on touch events at 'self', which could be a subclass of SPSprite:
+
+ // e.g. in 'init'
+ [self addEventListener:@selector(onTouch:) atObject:self forType:SP_EVENT_TYPE_TOUCH];
+
+ // the corresponding listener:
+ - (void)onTouch:(SPTouchEvent*)event
+ {
+ // query all touches that are currently moving on top of 'self'
+ NSArray *touches = [[event touchesWithTarget:self andPhase:SPTouchPhaseMoved] allObjects];
+
+ if (touches.count == 1)
+ {
+ // one finger touching
+ SPTouch *touch = [touches objectAtIndex:0];
+ SPPoint *currentPos = [touch locationInSpace:self.parent];
+ SPPoint *previousPos = [touch previousLocationInSpace:self.parent];
+ // ...
+ }
+ else if (touches.count >= 2)
+ {
+ // two fingers touching
+ // ...
+ }
+ }
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPTouchEvent : SPEvent
+{
+ @private
+ NSSet *mTouches;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Creates a touch with a set of touches. _Designated Initializer_.
+- (id)initWithType:(NSString*)type bubbles:(BOOL)bubbles touches:(NSSet*)touches;
+
+/// Creates a touch with a set of touches.
+- (id)initWithType:(NSString*)type touches:(NSSet*)touches;
+
+/// Factory method.
++ (SPTouchEvent*)eventWithType:(NSString*)type touches:(NSSet*)touches;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Gets a set of SPTouch objects that originated over a certain target.
+- (NSSet*)touchesWithTarget:(SPDisplayObject*)target;
+
+/// Gets a set of SPTouch objects that originated over a certain target and are in a certain phase.
+- (NSSet*)touchesWithTarget:(SPDisplayObject*)target andPhase:(SPTouchPhase)phase;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// All touches that are currently happening.
+@property (nonatomic, readonly) NSSet *touches;
+
+/// The time the event occurred (in seconds since application launch).
+@property (nonatomic, readonly) double timestamp;
+
+@end
--- /dev/null
+//
+// SPTouchEvent.m
+// Sparrow
+//
+// Created by Daniel Sperl on 02.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 "SPTouchEvent.h"
+#import "SPDisplayObject.h"
+#import "SPDisplayObjectContainer.h"
+#import "SPEvent_Internal.h"
+
+@implementation SPTouchEvent
+
+@synthesize touches = mTouches;
+
+- (id)initWithType:(NSString*)type bubbles:(BOOL)bubbles touches:(NSSet*)touches
+{
+ if (self = [super initWithType:type bubbles:bubbles])
+ {
+ mTouches = [touches retain];
+ }
+ return self;
+}
+
+- (id)initWithType:(NSString*)type touches:(NSSet*)touches
+{
+ return [self initWithType:type bubbles:YES touches:touches];
+}
+
+- (id)initWithType:(NSString*)type bubbles:(BOOL)bubbles
+{
+ return [self initWithType:type bubbles:bubbles touches:[NSSet set]];
+}
+
+- (SPEvent*)clone
+{
+ return [SPTouchEvent eventWithType:self.type touches:self.touches];
+}
+
+- (double)timestamp
+{
+ return [[mTouches anyObject] timestamp];
+}
+
+- (NSSet*)touchesWithTarget:(SPDisplayObject*)target
+{
+ NSMutableSet *touchesFound = [NSMutableSet set];
+ for (SPTouch *touch in mTouches)
+ {
+ if ([target isEqual:touch.target] ||
+ ([target isKindOfClass:[SPDisplayObjectContainer class]] &&
+ [(SPDisplayObjectContainer*)target containsChild:touch.target]))
+ {
+ [touchesFound addObject: touch];
+ }
+ }
+ return touchesFound;
+}
+
+- (NSSet*)touchesWithTarget:(SPDisplayObject*)target andPhase:(SPTouchPhase)phase
+{
+ NSMutableSet *touchesFound = [NSMutableSet set];
+ for (SPTouch *touch in mTouches)
+ {
+ if (touch.phase == phase &&
+ ([target isEqual:touch.target] ||
+ ([target isKindOfClass:[SPDisplayObjectContainer class]] &&
+ [(SPDisplayObjectContainer*)target containsChild:touch.target])))
+ {
+ [touchesFound addObject: touch];
+ }
+ }
+ return touchesFound;
+}
+
+- (void)dealloc
+{
+ [mTouches release];
+ [super dealloc];
+}
+
++ (SPTouchEvent*)eventWithType:(NSString*)type touches:(NSSet*)touches
+{
+ return [[[SPTouchEvent alloc] initWithType:type touches:touches] autorelease];
+}
+
+@end
--- /dev/null
+//
+// SPTouchProcessor.h
+// Sparrow
+//
+// Created by Daniel Sperl on 03.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>
+
+@class SPDisplayObjectContainer;
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPTouchProcesser processes raw touch information and dispatches it on display objects.
+
+ _This is an internal class. You do not have to use it manually._
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPTouchProcessor : NSObject
+{
+ @private
+ SPDisplayObjectContainer *mRoot;
+ NSMutableSet *mCurrentTouches;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a touch processor with a certain root object.
+- (id)initWithRoot:(SPDisplayObjectContainer*)root;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// @name Processes raw touches and dispatches events on the touched display objects.
+- (void)processTouches:(NSSet*)touches;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The root display container to check for touched targets.
+@property (nonatomic, assign) SPDisplayObjectContainer *root;
+
+@end
--- /dev/null
+//
+// SPTouchProcessor.m
+// Sparrow
+//
+// Created by Daniel Sperl on 03.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 "SPTouchProcessor.h"
+#import "SPMacros.h"
+#import "SPTouchEvent.h"
+#import "SPTouch.h"
+#import "SPTouch_Internal.h"
+#import "SPPoint.h"
+#import "SPMatrix.h"
+#import "SPDisplayObjectContainer.h"
+
+#define MULTITAP_TIME 0.25f
+#define MULTITAP_DIST 25
+
+@implementation SPTouchProcessor
+
+@synthesize root = mRoot;
+
+- (id)initWithRoot:(SPDisplayObjectContainer*)root
+{
+ if (self = [super init])
+ {
+ mRoot = root;
+ mCurrentTouches = [[NSMutableSet alloc] initWithCapacity:2];
+ }
+ return self;
+}
+
+- (id)init
+{
+ return [self initWithRoot:nil];
+}
+
+- (void)processTouches:(NSSet*)touches
+{
+ SP_CREATE_POOL(pool);
+
+ NSMutableSet *processedTouches = [[NSMutableSet alloc] init];
+
+ // process new touches
+ for (SPTouch *touch in touches)
+ {
+ SPTouch *currentTouch = nil;
+
+ for (SPTouch *existingTouch in mCurrentTouches)
+ {
+ if ((existingTouch.globalX == touch.previousGlobalX &&
+ existingTouch.globalY == touch.previousGlobalY) ||
+ (existingTouch.globalX == touch.globalX &&
+ existingTouch.globalY == touch.globalY))
+ {
+ // existing touch; update values
+ existingTouch.timestamp = touch.timestamp;
+ existingTouch.previousGlobalX = touch.previousGlobalX;
+ existingTouch.previousGlobalY = touch.previousGlobalY;
+ existingTouch.globalX = touch.globalX;
+ existingTouch.globalY = touch.globalY;
+ existingTouch.phase = touch.phase;
+ existingTouch.tapCount = touch.tapCount;
+
+ if (!existingTouch.target.stage)
+ {
+ // target could have been removed from stage -> find new target in that case
+ SPPoint *touchPosition = [SPPoint pointWithX:touch.globalX y:touch.globalY];
+ existingTouch.target = [mRoot hitTestPoint:touchPosition forTouch:YES];
+ }
+
+ currentTouch = existingTouch;
+ break;
+ }
+ }
+
+ if (!currentTouch)
+ {
+ // new touch!
+ currentTouch = [SPTouch touch];
+ currentTouch.timestamp = touch.timestamp;
+ currentTouch.globalX = touch.globalX;
+ currentTouch.globalY = touch.globalY;
+ currentTouch.previousGlobalX = touch.previousGlobalX;
+ currentTouch.previousGlobalY = touch.previousGlobalY;
+ currentTouch.phase = touch.phase;
+ currentTouch.tapCount = touch.tapCount;
+ SPPoint *touchPosition = [SPPoint pointWithX:touch.globalX y:touch.globalY];
+ currentTouch.target = [mRoot hitTestPoint:touchPosition forTouch:YES];
+ }
+
+ [processedTouches addObject:currentTouch];
+ }
+
+ // dispatch events
+ for (SPTouch *touch in processedTouches)
+ {
+ SPTouchEvent *touchEvent = [[SPTouchEvent alloc] initWithType:SP_EVENT_TYPE_TOUCH
+ touches:processedTouches];
+ [touch.target dispatchEvent:touchEvent];
+ [touchEvent release];
+ }
+
+ [mCurrentTouches release];
+ mCurrentTouches = processedTouches;
+
+ SP_RELEASE_POOL(pool);
+}
+
+- (void) dealloc
+{
+ [mCurrentTouches release];
+ [super dealloc];
+}
+
+@end
--- /dev/null
+//
+// SPTouch_Internal.h
+// Sparrow
+//
+// Created by Daniel Sperl on 03.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>
+#import "SPTouch.h"
+
+@interface SPTouch (Internal)
+
+- (void)setTimestamp:(double)timestamp;
+- (void)setGlobalX:(float)x;
+- (void)setGlobalY:(float)y;
+- (void)setPreviousGlobalX:(float)x;
+- (void)setPreviousGlobalY:(float)y;
+- (void)setTapCount:(int)tapCount;
+- (void)setPhase:(SPTouchPhase)phase;
+- (void)setTarget:(SPDisplayObject*)target;
+
++ (SPTouch*)touch;
+
+@end
--- /dev/null
+//
+// SPTransitions.h
+// Sparrow
+//
+// Created by Daniel Sperl on 11.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>
+
+#define SP_TRANSITION_LINEAR @"linear"
+#define SP_TRANSITION_RANDOMIZE @"randomize"
+
+#define SP_TRANSITION_EASE_IN @"easeIn"
+#define SP_TRANSITION_EASE_OUT @"easeOut"
+#define SP_TRANSITION_EASE_IN_OUT @"easeInOut"
+#define SP_TRANSITION_EASE_OUT_IN @"easeOutIn"
+
+#define SP_TRANSITION_EASE_IN_BACK @"easeInBack"
+#define SP_TRANSITION_EASE_OUT_BACK @"easeOutBack"
+#define SP_TRANSITION_EASE_IN_OUT_BACK @"easeInOutBack"
+#define SP_TRANSITION_EASE_OUT_IN_BACK @"easeOutInBack"
+
+#define SP_TRANSITION_EASE_IN_ELASTIC @"easeInElastic"
+#define SP_TRANSITION_EASE_OUT_ELASTIC @"easeOutElastic"
+#define SP_TRANSITION_EASE_IN_OUT_ELASTIC @"easeInOutElastic"
+#define SP_TRANSITION_EASE_OUT_IN_ELASTIC @"easeOutInElastic"
+
+#define SP_TRANSITION_EASE_IN_BOUNCE @"easeInBounce"
+#define SP_TRANSITION_EASE_OUT_BOUNCE @"easeOutBounce"
+#define SP_TRANSITION_EASE_IN_OUT_BOUNCE @"easeInOutBounce"
+#define SP_TRANSITION_EASE_OUT_IN_BOUNCE @"easeOutInBounce"
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPTransitions class contains static methods that define easing functions. Those functions
+ will be used by SPTween to execute animations.
+
+ Find a visual representation of the transitions at this URL: <br/>
+ http://www.sparrow-framework.org/wp-content/uploads/2010/06/transitions.png
+
+ You can define your own transitions by extending this class. The name of the method you declare
+ acts as the key that is used to identify the transition when you create the tween.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPTransitions : NSObject
+
++ (float)linear:(float)ratio;
++ (float)randomize:(float)ratio;
+
++ (float)easeIn:(float)ratio;
++ (float)easeOut:(float)ratio;
++ (float)easeInOut:(float)ratio;
++ (float)easeOutIn:(float)ratio;
+
++ (float)easeInBack:(float)ratio;
++ (float)easeOutBack:(float)ratio;
++ (float)easeInOutBack:(float)ratio;
++ (float)easeOutInBack:(float)ratio;
+
++ (float)easeInElastic:(float)ratio;
++ (float)easeOutElastic:(float)ratio;
++ (float)easeInOutElastic:(float)ratio;
++ (float)easeOutInElastic:(float)ratio;
+
++ (float)easeInBounce:(float)ratio;
++ (float)easeOutBounce:(float)ratio;
++ (float)easeInOutBounce:(float)ratio;
++ (float)easeOutInBounce:(float)ratio;
+
+@end
--- /dev/null
+//
+// SPTransitions.m
+// Sparrow
+//
+// Created by Daniel Sperl on 11.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.
+//
+// The easing functions were thankfully taken from http://dojotoolkit.org
+// and http://www.robertpenner.com/easing
+//
+
+#import "SPTransitions.h"
+#import "SPMacros.h"
+#import "SPUtils.h"
+
+@implementation SPTransitions
+
+- (id)init
+{
+ [self release];
+ [NSException raise:NSGenericException format:@"Static class - do not initialize!"];
+ return nil;
+}
+
++ (float)linear:(float)ratio
+{
+ return ratio;
+}
+
++ (float)randomize:(float)ratio
+{
+ return [SPUtils randomFloat];
+}
+
++ (float)easeIn:(float)ratio
+{
+ return ratio * ratio * ratio;
+}
+
++ (float)easeOut:(float)ratio
+{
+ float invRatio = ratio - 1.0f;
+ return invRatio * invRatio * invRatio + 1.0f;
+}
+
++ (float)easeInOut:(float)ratio
+{
+ if (ratio < 0.5f) return 0.5f * [SPTransitions easeIn:ratio*2.0f];
+ else return 0.5f * [SPTransitions easeOut:(ratio-0.5f)*2.0f] + 0.5f;
+}
+
++ (float)easeOutIn:(float)ratio
+{
+ if (ratio < 0.5f) return 0.5f * [SPTransitions easeOut:ratio*2.0f];
+ else return 0.5f * [SPTransitions easeIn:(ratio-0.5f)*2.0f] + 0.5f;
+}
+
++ (float)easeInBack:(float)ratio
+{
+ float s = 1.70158f;
+ return powf(ratio, 2.0f) * ((s + 1.0f)*ratio - s);
+}
+
++ (float)easeOutBack:(float)ratio
+{
+ float invRatio = ratio - 1.0f;
+ float s = 1.70158f;
+ return powf(invRatio, 2.0f) * ((s + 1.0f)*invRatio + s) + 1.0f;
+}
+
++ (float)easeInOutBack:(float)ratio
+{
+ if (ratio < 0.5f) return 0.5f * [SPTransitions easeInBack:ratio*2.0f];
+ else return 0.5f * [SPTransitions easeOutBack:(ratio-0.5f)*2.0f] + 0.5f;
+}
+
++ (float)easeOutInBack:(float)ratio
+{
+ if (ratio < 0.5f) return 0.5f * [SPTransitions easeOutBack:ratio*2.0f];
+ else return 0.5f * [SPTransitions easeInBack:(ratio-0.5f)*2.0f] + 0.5f;
+}
+
++ (float)easeInElastic:(float)ratio
+{
+ if (ratio == 0.0f || ratio == 1.0f) return ratio;
+ else
+ {
+ float p = 0.3f;
+ float s = p / 4.0f;
+ float invRatio = ratio - 1.0f;
+ return -1.0f * powf(2.0f, 10.0f*invRatio) * sinf((invRatio-s)*TWO_PI/p);
+ }
+}
+
++ (float)easeOutElastic:(float)ratio
+{
+ if (ratio == 0.0f || ratio == 1.0f) return ratio;
+ else
+ {
+ float p = 0.3f;
+ float s = p / 4.0f;
+ return powf(2.0f, -10.0f*ratio) * sinf((ratio-s)*TWO_PI/p) + 1.0f;
+ }
+}
+
++ (float)easeInOutElastic:(float)ratio
+{
+ if (ratio < 0.5f) return 0.5f * [SPTransitions easeInElastic:ratio*2.0f];
+ else return 0.5f * [SPTransitions easeOutElastic:(ratio-0.5f)*2.0f] + 0.5f;
+}
+
++ (float)easeOutInElastic:(float)ratio
+{
+ if (ratio < 0.5f) return 0.5f * [SPTransitions easeOutElastic:ratio*2.0f];
+ else return 0.5f * [SPTransitions easeInElastic:(ratio-0.5f)*2.0f] + 0.5f;
+}
+
++ (float)easeInBounce:(float)ratio
+{
+ return 1.0f - [SPTransitions easeOutBounce:1.0f - ratio];
+}
+
++ (float)easeOutBounce:(float)ratio
+{
+ float s = 7.5625f;
+ float p = 2.75f;
+ float l;
+ if (ratio < (1.0f/p))
+ {
+ l = s * powf(ratio, 2.0f);
+ }
+ else
+ {
+ if (ratio < (2.0f/p))
+ {
+ ratio -= 1.5f/p;
+ l = s * powf(ratio, 2.0f) + 0.75f;
+ }
+ else
+ {
+ if (ratio < 2.5f/p)
+ {
+ ratio -= 2.25f/p;
+ l = s * powf(ratio, 2.0f) + 0.9375f;
+ }
+ else
+ {
+ ratio -= 2.625f/p;
+ l = s * powf(ratio, 2.0f) + 0.984375f;
+ }
+ }
+ }
+ return l;
+}
+
++ (float)easeInOutBounce:(float)ratio
+{
+ if (ratio < 0.5f) return 0.5f * [SPTransitions easeInBounce:ratio*2.0f];
+ else return 0.5f * [SPTransitions easeOutBounce:(ratio-0.5f)*2.0f] + 0.5f;
+}
+
++ (float)easeOutInBounce:(float)ratio
+{
+ if (ratio < 0.5f) return 0.5f * [SPTransitions easeOutBounce:ratio*2.0f];
+ else return 0.5f * [SPTransitions easeInBounce:(ratio-0.5f)*2.0f] + 0.5f;
+}
+
+@end
--- /dev/null
+//
+// SPTween.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>
+#import "SPEventDispatcher.h"
+#import "SPAnimatable.h"
+#import "SPTransitions.h"
+
+typedef enum
+{
+ SPLoopTypeNone,
+ SPLoopTypeRepeat,
+ SPLoopTypeReverse
+} SPLoopType;
+
+#define SP_EVENT_TYPE_TWEEN_STARTED @"tweenStarted"
+#define SP_EVENT_TYPE_TWEEN_UPDATED @"tweenUpdated"
+#define SP_EVENT_TYPE_TWEEN_COMPLETED @"tweenCompleted"
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPTween animates numeric properties of objects. It uses different transition functions to give
+ the animations various styles.
+
+ The primary use of this class is to do standard animations like movement, fading, rotation, etc.
+ But there are no limits on what to animate; as long as the property you want to animate is numeric
+ (`int`, `float`, `double`), the tween can handle it. For a list of available Transition types,
+ see `SPTransitions`.
+
+ Here is an example of a tween that moves an object to the right, rotates it, and fades it out:
+
+ SPTween *tween = [SPTween tweenWithTarget:object time:2.0 transition:SP_TRANSITION_EASE_IN_OUT];
+ [tween animateProperty:@"x" targetValue:object.x + 50];
+ [tween animateProperty:@"rotation" targetValue:object.rotation + SP_D2R(45)];
+ [tween animateProperty:@"alpha" targetValue:0.0f];
+ [self.stage.juggler addObject:tween];
+
+ Note that the object is added to a juggler at the end. A tween will only be executed if its
+ `advanceTime:` method is executed regularly - the juggler will do that for us, and will release
+ the tween when it is finished.
+
+ Tweens dispatch events in certain phases of their life time:
+
+ * `SP_EVENT_TYPE_TWEEN_STARTED`: Dispatched once when the tween starts
+ * `SP_EVENT_TYPE_TWEEN_UPDATED`: Dispatched every time it is advanced
+ * `SP_EVENT_TYPE_TWEEN_COMPLETED`: Dispatched when it reaches its target value (except when it loops).
+
+ Tweens can loop in two ways:
+
+ * `SPLoopTypeRepeat`: Starts the animation from the beginning when it's finished.
+ * `SPLoopTypeReverse`: Reverses the animation when it's finished, tweening back to the start value.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPTween : SPEventDispatcher <SPAnimatable>
+{
+ @private
+ id mTarget;
+ SEL mTransition;
+ IMP mTransitionFunc;
+ NSMutableArray *mProperties;
+
+ double mTotalTime;
+ double mCurrentTime;
+ double mDelay;
+
+ SPLoopType mLoop;
+ BOOL mInvertTransition;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a tween with a target, duration (in seconds) and a transition function.
+/// _Designated Initializer_.
+- (id)initWithTarget:(id)target time:(double)time transition:(NSString*)transition;
+
+/// Initializes a tween with a target, a time (in seconds) and a linear transition
+/// (`SP_TRANSITION_LINEAR`).
+- (id)initWithTarget:(id)target time:(double)time;
+
+/// Factory method.
++ (SPTween *)tweenWithTarget:(id)target time:(double)time transition:(NSString *)transition;
+
+/// Factory method.
++ (SPTween *)tweenWithTarget:(id)target time:(double)time;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Animates the property of an object to a target value. You can call this method multiple times
+/// on one tween.
+- (void)animateProperty:(NSString*)property targetValue:(float)value;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The target object that is animated.
+@property (nonatomic, readonly) id target;
+
+/// The transition method used for the animation.
+@property (nonatomic, readonly) NSString *transition;
+
+/// The total time the tween will take (in seconds).
+@property (nonatomic, readonly) double time;
+
+/// The delay before the tween is started.
+@property (nonatomic, assign) double delay;
+
+/// The type of loop. (Default: SPLoopTypeNone)
+@property (nonatomic, assign) SPLoopType loop;
+
+@end
--- /dev/null
+//
+// SPTween.m
+// 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 "SPTween.h"
+#import "SPTransitions.h"
+#import "SPTweenedProperty.h"
+#import "SPMacros.h"
+
+#define TRANS_SUFFIX @":"
+
+typedef float (*FnPtrTransition) (id, SEL, float);
+
+@implementation SPTween
+
+@synthesize time = mTotalTime;
+@synthesize delay = mDelay;
+@synthesize target = mTarget;
+@synthesize loop = mLoop;
+
+- (id)initWithTarget:(id)target time:(double)time transition:(NSString*)transition
+{
+ if (self = [super init])
+ {
+ mTarget = [target retain];
+ mTotalTime = MAX(0.0001, time); // zero is not allowed
+ mCurrentTime = 0;
+ mDelay = 0;
+ mProperties = [[NSMutableArray alloc] init];
+ mLoop = SPLoopTypeNone;
+ mInvertTransition = NO;
+
+ // create function pointer for transition
+ NSString *transMethod = [transition stringByAppendingString:TRANS_SUFFIX];
+ mTransition = NSSelectorFromString(transMethod);
+ if (![SPTransitions respondsToSelector:mTransition])
+ [NSException raise:SP_EXC_INVALID_OPERATION
+ format:@"transition not found: '%@'", transition];
+ mTransitionFunc = [SPTransitions methodForSelector:mTransition];
+ }
+ return self;
+}
+
+- (id)initWithTarget:(id)target time:(double)time
+{
+ return [self initWithTarget:target time:time transition:SP_TRANSITION_LINEAR];
+}
+
+- (void)animateProperty:(NSString*)property targetValue:(float)value
+{
+ if (!mTarget) return; // tweening nil just does nothing.
+
+ SPTweenedProperty *tweenedProp = [[SPTweenedProperty alloc]
+ initWithTarget:mTarget name:property endValue:value];
+ [mProperties addObject:tweenedProp];
+ [tweenedProp release];
+}
+
+- (void)advanceTime:(double)seconds
+{
+ if (seconds == 0.0) return; // nothing to do
+
+ double previousTime = mCurrentTime;
+ mCurrentTime = MIN(mTotalTime, mCurrentTime + seconds);
+
+ if (mCurrentTime < 0 || previousTime >= mTotalTime) return;
+
+ if (previousTime <= 0 && mCurrentTime >= 0 &&
+ [self hasEventListenerForType:SP_EVENT_TYPE_TWEEN_STARTED])
+ {
+ SPEvent *event = [[SPEvent alloc] initWithType:SP_EVENT_TYPE_TWEEN_STARTED];
+ [self dispatchEvent:event];
+ [event release];
+ }
+
+ float ratio = mCurrentTime / mTotalTime;
+ FnPtrTransition transFunc = (FnPtrTransition) mTransitionFunc;
+ Class transClass = [SPTransitions class];
+
+ for (SPTweenedProperty *prop in mProperties)
+ {
+ if (previousTime <= 0 && mCurrentTime >= 0)
+ prop.startValue = prop.currentValue;
+
+ float transitionValue = mInvertTransition ?
+ 1.0f - transFunc(transClass, mTransition, 1.0f - ratio) :
+ transFunc(transClass, mTransition, ratio);
+
+ prop.currentValue = prop.startValue + prop.delta * transitionValue;
+ }
+
+ if ([self hasEventListenerForType:SP_EVENT_TYPE_TWEEN_UPDATED])
+ {
+ SPEvent *event = [[SPEvent alloc] initWithType:SP_EVENT_TYPE_TWEEN_UPDATED];
+ [self dispatchEvent:event];
+ [event release];
+ }
+
+ if (previousTime < mTotalTime && mCurrentTime >= mTotalTime)
+ {
+ if (mLoop == SPLoopTypeRepeat)
+ {
+ for (SPTweenedProperty *prop in mProperties)
+ prop.currentValue = prop.startValue;
+
+ mCurrentTime = 0;
+ }
+ else if (mLoop == SPLoopTypeReverse)
+ {
+ for (SPTweenedProperty *prop in mProperties)
+ {
+ prop.currentValue = prop.endValue; // since tweens not necessarily end with endValue
+ prop.endValue = prop.startValue;
+ mInvertTransition = !mInvertTransition;
+ }
+
+ mCurrentTime = 0;
+ }
+ else if ([self hasEventListenerForType:SP_EVENT_TYPE_TWEEN_COMPLETED])
+ {
+ SPEvent *event = [[SPEvent alloc] initWithType:SP_EVENT_TYPE_TWEEN_COMPLETED];
+ [self dispatchEvent:event];
+ [event release];
+ }
+ }
+}
+
+- (NSString*)transition
+{
+ NSString *selectorName = NSStringFromSelector(mTransition);
+ return [selectorName substringToIndex:selectorName.length - [TRANS_SUFFIX length]];
+}
+
+- (BOOL)isComplete
+{
+ return mCurrentTime >= mTotalTime && mLoop == SPLoopTypeNone;
+}
+
+- (void)setDelay:(double)delay
+{
+ mCurrentTime = mCurrentTime + mDelay - delay;
+ mDelay = delay;
+}
+
++ (SPTween*)tweenWithTarget:(id)target time:(double)time transition:(NSString*)transition
+{
+ return [[[SPTween alloc] initWithTarget:target time:time transition:transition] autorelease];
+}
+
++ (SPTween*)tweenWithTarget:(id)target time:(double)time
+{
+ return [[[SPTween alloc] initWithTarget:target time:time] autorelease];
+}
+
+- (void)dealloc
+{
+ [mTarget release];
+ [mProperties release];
+ [super dealloc];
+}
+
+@end
--- /dev/null
+//
+// SPTweenedProperty.h
+// Sparrow
+//
+// Created by Daniel Sperl on 17.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>
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPTweenedProperty stores the information about the tweening of a single property of an object.
+ Its `currentValue` property updates the specified property of the target object.
+
+ _This is an internal class. You do not have to use it manually._
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPTweenedProperty : NSObject
+{
+ @private
+ id mTarget;
+
+ SEL mGetter;
+ IMP mGetterFunc;
+ SEL mSetter;
+ IMP mSetterFunc;
+
+ float mStartValue;
+ float mEndValue;
+ char mNumericType;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a tween property on a certain target. The start value will be zero.
+- (id)initWithTarget:(id)target name:(NSString *)name endValue:(float)endValue;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The start value of the tween.
+@property (nonatomic, assign) float startValue;
+
+/// The current value of the tween. Setting this property updates the target property.
+@property (nonatomic, assign) float currentValue;
+
+/// The end value of the tween.
+@property (nonatomic, assign) float endValue;
+
+/// The animation delta (endValue - startValue)
+@property (nonatomic, readonly) float delta;
+
+
+@end
\ No newline at end of file
--- /dev/null
+//
+// SPTweenedProperty.m
+// Sparrow
+//
+// Created by Daniel Sperl on 17.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 "SPTweenedProperty.h"
+#import "SPMacros.h"
+
+typedef float (*FnPtrGetterF) (id, SEL);
+typedef double (*FnPtrGetterD) (id, SEL);
+typedef int (*FnPtrGetterI) (id, SEL);
+
+typedef void (*FnPtrSetterF) (id, SEL, float);
+typedef void (*FnPtrSetterD) (id, SEL, double);
+typedef void (*FnPtrSetterI) (id, SEL, int);
+
+@implementation SPTweenedProperty
+
+@synthesize startValue = mStartValue;
+@synthesize endValue = mEndValue;
+
+- (id)initWithTarget:(id)target name:(NSString *)name endValue:(float)endValue;
+{
+ if (self = [super init])
+ {
+ mTarget = [target retain];
+ mEndValue = endValue;
+
+ mGetter = NSSelectorFromString(name);
+ mSetter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
+ [[name substringToIndex:1] uppercaseString],
+ [name substringFromIndex:1]]);
+
+ if (![mTarget respondsToSelector:mGetter] || ![mTarget respondsToSelector:mSetter])
+ [NSException raise:SP_EXC_INVALID_OPERATION format:@"property not found or readonly: '%@'",
+ name];
+
+ // query argument type
+ NSMethodSignature *sig = [mTarget methodSignatureForSelector:mGetter];
+ mNumericType = *[sig methodReturnType];
+ if (mNumericType != 'f' && mNumericType != 'i' && mNumericType != 'd')
+ [NSException raise:SP_EXC_INVALID_OPERATION format:@"property not numeric: '%@'", name];
+
+ mGetterFunc = [mTarget methodForSelector:mGetter];
+ mSetterFunc = [mTarget methodForSelector:mSetter];
+ }
+ return self;
+}
+
+- (id)init
+{
+ return [self initWithTarget:nil name:nil endValue:0.0f];
+}
+
+- (void)setCurrentValue:(float)value
+{
+ if (mNumericType == 'f')
+ {
+ FnPtrSetterF func = (FnPtrSetterF)mSetterFunc;
+ func(mTarget, mSetter, value);
+ }
+ else if (mNumericType == 'd')
+ {
+ FnPtrSetterD func = (FnPtrSetterD)mSetterFunc;
+ func(mTarget, mSetter, (double)value);
+ }
+ else
+ {
+ FnPtrSetterI func = (FnPtrSetterI)mSetterFunc;
+ func(mTarget, mSetter, (int)(value+0.5f));
+ }
+}
+
+- (float)currentValue
+{
+ if (mNumericType == 'f')
+ {
+ FnPtrGetterF func = (FnPtrGetterF)mGetterFunc;
+ return func(mTarget, mGetter);
+ }
+ else if (mNumericType == 'd')
+ {
+ FnPtrGetterD func = (FnPtrGetterD)mGetterFunc;
+ return func(mTarget, mGetter);
+ }
+ else
+ {
+ FnPtrGetterI func = (FnPtrGetterI)mGetterFunc;
+ return func(mTarget, mGetter);
+ }
+}
+
+- (float)delta
+{
+ return mEndValue - mStartValue;
+}
+
+- (void)dealloc
+{
+ [mTarget release];
+ [super dealloc];
+}
+
+@end
--- /dev/null
+//
+// SPUtils.h
+// Sparrow
+//
+// Created by Daniel Sperl on 04.01.11.
+// Copyright 2011 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 SPUtils class contains utility methods for different purposes.
+
+@interface SPUtils : NSObject
+
+/// Finds the next power of two equal to or above the specified number.
++ (int)nextPowerOfTwo:(int)number;
+
+/// Returns a random integer number between `minValue` (inclusive) and `maxValue` (exclusive).
++ (int)randomIntBetween:(int)minValue and:(int)maxValue;
+
+/// Returns a random float number between 0.0 and 1.0
++ (float)randomFloat;
+
+@end
--- /dev/null
+//
+// SPUtils.m
+// Sparrow
+//
+// Created by Daniel Sperl on 04.01.11.
+// Copyright 2011 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 "SPUtils.h"
+
+
+@implementation SPUtils
+
++ (int)nextPowerOfTwo:(int)number
+{
+ int result = 1;
+ while (result < number) result *= 2;
+ return result;
+}
+
++ (int)randomIntBetween:(int)minValue and:(int)maxValue
+{
+ return (int)(minValue + [self randomFloat] * (maxValue - minValue));
+}
+
++ (float)randomFloat
+{
+ return (float) arc4random() / UINT_MAX;
+}
+
+@end
--- /dev/null
+//
+// EAGLView.h
+// Sparrow
+//
+// Created by Daniel Sperl on 13.03.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 <UIKit/UIKit.h>
+#import <OpenGLES/EAGL.h>
+#import <OpenGLES/ES1/gl.h>
+#import <OpenGLES/ES1/glext.h>
+
+@class SPStage;
+@class SPRenderSupport;
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPView is the UIView object that Sparrow renders its content into.
+
+ Add it to the UIKit display list like any other view. Beware that Sparrow will only receive
+ multitouch events if the multitouchEnabled property of the view is enabled.
+
+ To start Sparrow, connect this class to your stage subclass and call the start method. When
+ the application ends or moves into the background, you should call the stop method.
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPView : UIView
+{
+ @private
+ int mWidth;
+ int mHeight;
+
+ SPStage *mStage;
+ SPRenderSupport *mRenderSupport;
+
+ EAGLContext *mContext;
+ GLuint mRenderbuffer;
+ GLuint mFramebuffer;
+
+ float mFrameRate;
+ NSTimer *mTimer;
+ id mDisplayLink;
+ BOOL mDisplayLinkSupported;
+
+ double mLastFrameTimestamp;
+ double mLastTouchTimestamp;
+}
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// Indicates if start was called.
+@property (nonatomic, readonly) BOOL isStarted;
+
+/// Assigns the desired framerate. Only dividers of 60 are allowed (60, 30, 20, 15, 12, 10, etc.)
+@property (nonatomic, assign) float frameRate;
+
+/// The stage object that will be processed.
+@property (nonatomic, retain) SPStage *stage;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Starts rendering and event handling.
+- (void)start;
+
+/// Stops rendering and event handling. Call this when the application moves into the background.
+- (void)stop;
+
+@end
--- /dev/null
+//
+// EAGLView.m
+// Sparrow
+//
+// Created by Daniel Sperl on 13.03.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 <QuartzCore/QuartzCore.h>
+#import <OpenGLES/EAGLDrawable.h>
+
+#import "SPStage.h"
+#import "SPStage_Internal.h"
+#import "SPView.h"
+#import "SPMacros.h"
+#import "SPTouch.h"
+#import "SPTouch_Internal.h"
+#import "SPRenderSupport.h"
+
+// --- private interface ---------------------------------------------------------------------------
+
+@interface SPView ()
+
+@property (nonatomic, retain) NSTimer *timer;
+@property (nonatomic, retain) id displayLink;
+
+- (void)setup;
+- (void)createFramebuffer;
+- (void)destroyFramebuffer;
+
+- (void)renderStage;
+- (void)processTouchEvent:(UIEvent*)event;
+
+@end
+
+// --- class implementation ------------------------------------------------------------------------
+
+@implementation SPView
+
+#define REFRESH_RATE 60
+
+@synthesize stage = mStage;
+@synthesize timer = mTimer;
+@synthesize displayLink = mDisplayLink;
+@synthesize frameRate = mFrameRate;
+
+- (id)initWithFrame:(CGRect)frame
+{
+ if ([super initWithFrame:frame])
+ {
+ [self setup];
+ }
+ return self;
+}
+
+- (id)initWithCoder:(NSCoder *)decoder
+{
+ if ([super initWithCoder:decoder])
+ {
+ [self setup];
+ }
+ return self;
+}
+
+- (void)setup
+{
+ if (mContext) return; // already initialized!
+
+ // A system version of 3.1 or greater is required to use CADisplayLink.
+ NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
+ if ([currSysVer compare:@"3.1" options:NSNumericSearch] != NSOrderedAscending)
+ mDisplayLinkSupported = YES;
+
+ self.frameRate = 30.0f;
+
+ // get the layer
+ CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
+
+ eaglLayer.opaque = YES;
+ eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,
+ kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
+
+ mContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
+
+ if (!mContext || ![EAGLContext setCurrentContext:mContext])
+ NSLog(@"Could not create render context");
+
+ mRenderSupport = [[SPRenderSupport alloc] init];
+}
+
+- (void)layoutSubviews
+{
+ [self destroyFramebuffer]; // reset framebuffer (scale factor could have changed)
+ [self createFramebuffer];
+ [self renderStage]; // fill buffer immediately to avoid flickering
+}
+
+- (void)createFramebuffer
+{
+ glGenFramebuffersOES(1, &mFramebuffer);
+ glGenRenderbuffersOES(1, &mRenderbuffer);
+
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, mFramebuffer);
+ glBindRenderbufferOES(GL_RENDERBUFFER_OES, mRenderbuffer);
+ [mContext renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];
+ glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, mRenderbuffer);
+
+ glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &mWidth);
+ glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &mHeight);
+
+ if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES)
+ NSLog(@"failed to create framebuffer: %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
+}
+
+- (void)destroyFramebuffer
+{
+ glDeleteFramebuffersOES(1, &mFramebuffer);
+ mFramebuffer = 0;
+ glDeleteRenderbuffersOES(1, &mRenderbuffer);
+ mRenderbuffer = 0;
+}
+
+- (void)renderStage
+{
+ if (mFramebuffer == 0 || mRenderbuffer == 0)
+ return; // buffers not yet initialized
+
+ SP_CREATE_POOL(pool);
+
+ double now = CACurrentMediaTime();
+ double timePassed = now - mLastFrameTimestamp;
+ [mStage advanceTime:timePassed];
+ mLastFrameTimestamp = now;
+
+ [EAGLContext setCurrentContext:mContext];
+
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, mFramebuffer);
+ glViewport(0, 0, mWidth, mHeight);
+
+ [mRenderSupport bindTexture:nil]; // old textures could have become invalid
+ [mStage render:mRenderSupport];
+
+ glBindRenderbufferOES(GL_RENDERBUFFER_OES, mRenderbuffer);
+ [mContext presentRenderbuffer:GL_RENDERBUFFER_OES];
+
+ SP_RELEASE_POOL(pool);
+}
+
+- (void)setTimer:(NSTimer *)newTimer
+{
+ if (mTimer != newTimer)
+ {
+ [mTimer invalidate];
+ mTimer = newTimer;
+ }
+}
+
+- (void)setDisplayLink:(id)newDisplayLink
+{
+ if (mDisplayLink != newDisplayLink)
+ {
+ [mDisplayLink invalidate];
+ mDisplayLink = newDisplayLink;
+ }
+}
+
+- (void)setFrameRate:(float)value
+{
+ if (mDisplayLinkSupported)
+ {
+ int frameInterval = 1;
+ while (REFRESH_RATE / frameInterval > value)
+ ++frameInterval;
+ mFrameRate = REFRESH_RATE / frameInterval;
+ }
+ else
+ mFrameRate = value;
+
+ if (self.isStarted)
+ {
+ [self stop];
+ [self start];
+ }
+}
+
+- (BOOL)isStarted
+{
+ return mTimer || mDisplayLink;
+}
+
+- (void)start
+{
+ if (self.isStarted) return;
+ if (mFrameRate > 0.0f)
+ {
+ mLastFrameTimestamp = CACurrentMediaTime();
+
+ if (mDisplayLinkSupported)
+ {
+ mDisplayLink = [NSClassFromString(@"CADisplayLink")
+ displayLinkWithTarget:self selector:@selector(renderStage)];
+
+ [mDisplayLink setFrameInterval: (int)(REFRESH_RATE / mFrameRate)];
+ [mDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+ }
+ else
+ {
+ // timer used as a fallback
+ self.timer = [NSTimer scheduledTimerWithTimeInterval:(1.0f / mFrameRate)
+ target:self selector:@selector(renderStage) userInfo:nil repeats:YES];
+ }
+ }
+}
+
+- (void)stop
+{
+ [self renderStage]; // draw last-moment changes
+
+ self.timer = nil;
+ self.displayLink = nil;
+}
+
+- (void)setStage:(SPStage*)stage
+{
+ if (mStage != stage)
+ {
+ mStage.nativeView = nil;
+ [mStage release];
+ mStage = [stage retain];
+ mStage.nativeView = self;
+ }
+}
+
++ (Class)layerClass
+{
+ return [CAEAGLLayer class];
+}
+
+- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
+{
+ [self processTouchEvent:event];
+}
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ [self processTouchEvent:event];
+}
+
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ [self processTouchEvent:event];
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ mLastTouchTimestamp -= 0.0001f; // cancelled touch events have an old timestamp -> workaround
+ [self processTouchEvent:event];
+}
+
+- (void)processTouchEvent:(UIEvent*)event
+{
+ if (self.isStarted && mLastTouchTimestamp != event.timestamp)
+ {
+ SP_CREATE_POOL(pool);
+
+ CGSize viewSize = self.bounds.size;
+ float xConversion = mStage.width / viewSize.width;
+ float yConversion = mStage.height / viewSize.height;
+
+ // convert to SPTouches and forward to stage
+ NSMutableSet *touches = [NSMutableSet set];
+ double now = CACurrentMediaTime();
+ for (UITouch *uiTouch in [event touchesForView:self])
+ {
+ CGPoint location = [uiTouch locationInView:self];
+ CGPoint previousLocation = [uiTouch previousLocationInView:self];
+ SPTouch *touch = [SPTouch touch];
+ touch.timestamp = now; // timestamp of uiTouch not compatible to Sparrow timestamp
+ touch.globalX = location.x * xConversion;
+ touch.globalY = location.y * yConversion;
+ touch.previousGlobalX = previousLocation.x * xConversion;
+ touch.previousGlobalY = previousLocation.y * yConversion;
+ touch.tapCount = uiTouch.tapCount;
+ touch.phase = (SPTouchPhase) uiTouch.phase;
+ [touches addObject:touch];
+ }
+ [mStage processTouches:touches];
+ mLastTouchTimestamp = event.timestamp;
+
+ SP_RELEASE_POOL(pool);
+ }
+}
+
+- (void)dealloc
+{
+ if ([EAGLContext currentContext] == mContext)
+ [EAGLContext setCurrentContext:nil];
+
+ [mContext release];
+ [mStage release];
+ [mRenderSupport release];
+ [self destroyFramebuffer];
+
+ self.timer = nil; // invalidates timer
+ self.displayLink = nil; // invalidates displayLink
+
+ [super dealloc];
+}
+
+@end
--- /dev/null
+//
+// Sparrow.h
+// Sparrow
+//
+// Created by Daniel Sperl on 21.03.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.
+//
+
+#define SPARROW_VERSION @"1.1.0"
+
+#import "SPNSExtensions.h"
+#import "SPEventDispatcher.h"
+#import "SPDisplayObject.h"
+#import "SPDisplayObjectContainer.h"
+#import "SPQuad.h"
+#import "SPImage.h"
+#import "SPTextField.h"
+#import "SPBitmapFont.h"
+#import "SPButton.h"
+#import "SPStage.h"
+#import "SPSprite.h"
+#import "SPMovieClip.h"
+#import "SPTexture.h"
+#import "SPSubTexture.h"
+#import "SPRenderTexture.h"
+#import "SPGLTexture.h"
+#import "SPTextureAtlas.h"
+#import "SPEvent.h"
+#import "SPTouchEvent.h"
+#import "SPEnterFrameEvent.h"
+#import "SPJuggler.h"
+#import "SPTransitions.h"
+#import "SPTween.h"
+#import "SPDelayedInvocation.h"
+#import "SPRectangle.h"
+#import "SPMacros.h"
+#import "SPUtils.h"
+#import "SPView.h"
+#import "SPRenderSupport.h"
+#import "SPAudioEngine.h"
+#import "SPSound.h"
+#import "SPSoundChannel.h"
--- /dev/null
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 45;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ DE019C391026360B00ECB0AC /* SPTween.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7044760FB62080007F5ECC /* SPTween.m */; };
+ DE019C3A1026361200ECB0AC /* SPDelayedInvocation.m in Sources */ = {isa = PBXBuildFile; fileRef = DEFB1B94100926260022C117 /* SPDelayedInvocation.m */; };
+ DE019C3B1026363D00ECB0AC /* SPNSExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = DE68EA160FBB5660004DBC95 /* SPNSExtensions.m */; };
+ DE019C3C1026364800ECB0AC /* SPJuggler.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7044270FB61506007F5ECC /* SPJuggler.m */; };
+ DE13D18B12AADBF6000C77E6 /* SPRenderTexture.h in Headers */ = {isa = PBXBuildFile; fileRef = DE13D18912AADBF6000C77E6 /* SPRenderTexture.h */; };
+ DE13D18C12AADBF6000C77E6 /* SPRenderTexture.m in Sources */ = {isa = PBXBuildFile; fileRef = DE13D18A12AADBF6000C77E6 /* SPRenderTexture.m */; };
+ DE14412E12421EA600FFA95A /* SPNSExtensionsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE05748611E915A900F3A8A4 /* SPNSExtensionsTest.m */; };
+ DE14413612421F4700FFA95A /* SPImageTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE0853A40FEC286900DAF53C /* SPImageTest.m */; };
+ DE14413712421F4800FFA95A /* SPDisplayObjectTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE469D6E0F938FAB00F56E91 /* SPDisplayObjectTest.m */; };
+ DE14413812421F4800FFA95A /* SPMovieClipTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC54F6211B7765500E439B0 /* SPMovieClipTest.m */; };
+ DE14413912421F4900FFA95A /* SPRectangleTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DED67F7C0FA359F00050E779 /* SPRectangleTest.m */; };
+ DE14413A12421F4900FFA95A /* SPDisplayObjectContainerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEB21CF80F93C9780080D5C2 /* SPDisplayObjectContainerTest.m */; };
+ DE14413B12421F4A00FFA95A /* SPEventDispatcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE594490FA63BA800E3AEFC /* SPEventDispatcherTest.m */; };
+ DE14413C12421F4B00FFA95A /* SPDelayedInvocationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5286BA11F77C6200F916E8 /* SPDelayedInvocationTest.m */; };
+ DE14413D12421F4B00FFA95A /* SPMatrixTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8F1E2D0F7C1F3A0085E9E4 /* SPMatrixTest.m */; };
+ DE14413E12421F4B00FFA95A /* SPPointTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEABCF5B0F7AE187003B6C9D /* SPPointTest.m */; };
+ DE14413F12421F4C00FFA95A /* SPQuadTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DED2B6F90FA0CF5900083578 /* SPQuadTest.m */; };
+ DE14414012421F4C00FFA95A /* SPJugglerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE1F9446104704440084D470 /* SPJugglerTest.m */; };
+ DE14414112421F4D00FFA95A /* SPTweenTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE75E8660FBDC57E00C64495 /* SPTweenTest.m */; };
+ DE14414212421F4E00FFA95A /* SPStageTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DED67F330FA3514C0050E779 /* SPStageTest.m */; };
+ DE18FE4511B083B200D01F04 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE18FE4411B083B200D01F04 /* AVFoundation.framework */; };
+ DE20D9CC10713B0C006658C9 /* SPRenderSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = DE20D9CA10713B0C006658C9 /* SPRenderSupport.m */; };
+ DE33072512D2EBCD009CC5E7 /* SPUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = DE33072312D2EBCD009CC5E7 /* SPUtils.h */; };
+ DE33072612D2EBCD009CC5E7 /* SPUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = DE33072412D2EBCD009CC5E7 /* SPUtils.m */; };
+ DE33072B12D2ED1A009CC5E7 /* SPUtilsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE33072812D2ECB1009CC5E7 /* SPUtilsTest.m */; };
+ DED8B48711B0827E00A1766C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; };
+ DED8B48811B0827E00A1766C /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE9CF1EB0FED3128008A32FF /* CoreGraphics.framework */; };
+ DED8B48911B0827E00A1766C /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28FD15070DC6FC5B0079059D /* QuartzCore.framework */; };
+ DED8B48A11B0827E00A1766C /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28FD14FF0DC6FC520079059D /* OpenGLES.framework */; };
+ DED8B48B11B0827E00A1766C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; };
+ DED8B48C11B0827E00A1766C /* OpenAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEE63A7111AED4FA00D60321 /* OpenAL.framework */; };
+ DED8B48D11B0827E00A1766C /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEE63A8511AED51400D60321 /* AudioToolbox.framework */; };
+ DED9B51E10629D9F00989853 /* SPPoolObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DED9B51C10629D9F00989853 /* SPPoolObject.m */; };
+ DEE09D7B108364A900ECC896 /* SPBitmapFont.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE09D79108364A900ECC896 /* SPBitmapFont.m */; };
+ DEE09D7F108369AE00ECC896 /* SPBitmapChar.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE09D7D108369AE00ECC896 /* SPBitmapChar.m */; };
+ DEE63A5211AED38100D60321 /* SPAudioEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE63A4C11AED38100D60321 /* SPAudioEngine.m */; };
+ DEE63A5411AED38100D60321 /* SPSound.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE63A4E11AED38100D60321 /* SPSound.m */; };
+ DEE63A5611AED38100D60321 /* SPSoundChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE63A5011AED38100D60321 /* SPSoundChannel.m */; };
+ DEE6516B11F9DF200065207E /* SPCompiledSprite.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2CC36011EF75B600439B43 /* SPCompiledSprite.m */; };
+ DEE94E8311B43DE60000FE20 /* SPMovieClip.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE94E8111B43DE60000FE20 /* SPMovieClip.m */; };
+ DEEA933A101E32E10071DD21 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE9CF1EB0FED3128008A32FF /* CoreGraphics.framework */; };
+ DEEA933B101E32E10071DD21 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; };
+ DEEA933C101E32E10071DD21 /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28FD14FF0DC6FC520079059D /* OpenGLES.framework */; };
+ DEEA933D101E32E10071DD21 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28FD15070DC6FC5B0079059D /* QuartzCore.framework */; };
+ DEEA933E101E32E10071DD21 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; };
+ DEEA934E101E33F00071DD21 /* libSparrow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DEFE4BC2101B317600E22471 /* libSparrow.a */; };
+ DEED173A108A50000071438F /* SPTweenedProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = DEED1738108A50000071438F /* SPTweenedProperty.m */; };
+ DEEDE3A111B29908009FD4C8 /* OpenAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEE63A7111AED4FA00D60321 /* OpenAL.framework */; };
+ DEEDE3A211B29908009FD4C8 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE18FE4411B083B200D01F04 /* AVFoundation.framework */; };
+ DEEDE3A311B29909009FD4C8 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEE63A8511AED51400D60321 /* AudioToolbox.framework */; };
+ DEF1730F11B0645A00A11DD7 /* SPALSound.m in Sources */ = {isa = PBXBuildFile; fileRef = DEF1730D11B0645A00A11DD7 /* SPALSound.m */; };
+ DEF1731311B0648B00A11DD7 /* SPALSoundChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = DEF1731111B0648B00A11DD7 /* SPALSoundChannel.m */; };
+ DEF1735D11B075B000A11DD7 /* SPAVSound.m in Sources */ = {isa = PBXBuildFile; fileRef = DEF1735B11B075B000A11DD7 /* SPAVSound.m */; };
+ DEF1736111B075BC00A11DD7 /* SPAVSoundChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = DEF1735F11B075BC00A11DD7 /* SPAVSoundChannel.m */; };
+ DEFE4BCE101B31DF00E22471 /* SPTransitions.m in Sources */ = {isa = PBXBuildFile; fileRef = DED859440FB883EE00D3D7D2 /* SPTransitions.m */; };
+ DEFE4BD1101B31DF00E22471 /* SPView.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC2B67A0F6D4CE90063AB1D /* SPView.m */; };
+ DEFE4BD2101B31DF00E22471 /* SPEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE593D20FA6116B00E3AEFC /* SPEvent.m */; };
+ DEFE4BD3101B31DF00E22471 /* SPEventDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC2B6810F6D51430063AB1D /* SPEventDispatcher.m */; };
+ DEFE4BD4101B31DF00E22471 /* SPTouch.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE4449D0FABA7860085D36D /* SPTouch.m */; };
+ DEFE4BD5101B31DF00E22471 /* SPTouchEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE444A90FABABBB0085D36D /* SPTouchEvent.m */; };
+ DEFE4BD6101B31DF00E22471 /* SPEnterFrameEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = DEEEB8C90FAA2C300059D72B /* SPEnterFrameEvent.m */; };
+ DEFE4BD7101B31DF00E22471 /* SPDisplayObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2ED8050F6D52080012B6BA /* SPDisplayObject.m */; };
+ DEFE4BD8101B31DF00E22471 /* SPDisplayObjectContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2ED8090F6D53020012B6BA /* SPDisplayObjectContainer.m */; };
+ DEFE4BD9101B31DF00E22471 /* SPSprite.m in Sources */ = {isa = PBXBuildFile; fileRef = DE4D6AEB0F75913D0045CBF7 /* SPSprite.m */; };
+ DEFE4BDA101B31DF00E22471 /* SPStage.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2ED8590F6D54AC0012B6BA /* SPStage.m */; };
+ DEFE4BDB101B31DF00E22471 /* SPQuad.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2ED8560F6D54900012B6BA /* SPQuad.m */; };
+ DEFE4BDC101B31DF00E22471 /* SPImage.m in Sources */ = {isa = PBXBuildFile; fileRef = DE08535D0FEC21F500DAF53C /* SPImage.m */; };
+ DEFE4BDD101B31DF00E22471 /* SPTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = DED4EFCA0FF9439D0093AD29 /* SPTextField.m */; };
+ DEFE4BDE101B31DF00E22471 /* SPTexture.m in Sources */ = {isa = PBXBuildFile; fileRef = DE0853F90FEC2CFF00DAF53C /* SPTexture.m */; };
+ DEFE4BDF101B31DF00E22471 /* SPGLTexture.m in Sources */ = {isa = PBXBuildFile; fileRef = DECF84320FF649D50026A4ED /* SPGLTexture.m */; };
+ DEFE4BE0101B31DF00E22471 /* SPSubTexture.m in Sources */ = {isa = PBXBuildFile; fileRef = DECF84BA0FF681BA0026A4ED /* SPSubTexture.m */; };
+ DEFE4BE1101B31DF00E22471 /* SPButton.m in Sources */ = {isa = PBXBuildFile; fileRef = DED85312100BC9DA0014F729 /* SPButton.m */; };
+ DEFE4BE2101B31DF00E22471 /* SPMatrix.m in Sources */ = {isa = PBXBuildFile; fileRef = DE469D260F9386FD00F56E91 /* SPMatrix.m */; };
+ DEFE4BE3101B31DF00E22471 /* SPPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = DE469D280F9386FD00F56E91 /* SPPoint.m */; };
+ DEFE4BE4101B31DF00E22471 /* SPRectangle.m in Sources */ = {isa = PBXBuildFile; fileRef = DE469D2A0F9386FD00F56E91 /* SPRectangle.m */; };
+ DEFE4BE6101B31DF00E22471 /* SPTextureAtlas.m in Sources */ = {isa = PBXBuildFile; fileRef = DECF84270FF619150026A4ED /* SPTextureAtlas.m */; };
+ DEFE4C38101B5FB100E22471 /* SPRendering.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9CF1BC0FED2678008A32FF /* SPRendering.m */; };
+ DEFE4C3A101B5FB100E22471 /* SPTouchProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = DEDCD3AD0FADEE280022011C /* SPTouchProcessor.m */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ DEEA934C101E33ED0071DD21 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DEFE4BC1101B317600E22471;
+ remoteInfo = SparrowLib;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+ 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
+ 28FD14FF0DC6FC520079059D /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; };
+ 28FD15070DC6FC5B0079059D /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
+ DE05748611E915A900F3A8A4 /* SPNSExtensionsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPNSExtensionsTest.m; sourceTree = "<group>"; };
+ DE08535C0FEC21F500DAF53C /* SPImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPImage.h; sourceTree = "<group>"; };
+ DE08535D0FEC21F500DAF53C /* SPImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPImage.m; sourceTree = "<group>"; };
+ DE0853A40FEC286900DAF53C /* SPImageTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPImageTest.m; sourceTree = "<group>"; };
+ DE0853F80FEC2CFF00DAF53C /* SPTexture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTexture.h; sourceTree = "<group>"; };
+ DE0853F90FEC2CFF00DAF53C /* SPTexture.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTexture.m; sourceTree = "<group>"; };
+ DE13D18912AADBF6000C77E6 /* SPRenderTexture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPRenderTexture.h; sourceTree = "<group>"; };
+ DE13D18A12AADBF6000C77E6 /* SPRenderTexture.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRenderTexture.m; sourceTree = "<group>"; };
+ DE18FE4411B083B200D01F04 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
+ DE1F9446104704440084D470 /* SPJugglerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPJugglerTest.m; sourceTree = "<group>"; };
+ DE20D9C910713B0C006658C9 /* SPRenderSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPRenderSupport.h; sourceTree = "<group>"; };
+ DE20D9CA10713B0C006658C9 /* SPRenderSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRenderSupport.m; sourceTree = "<group>"; };
+ DE2AD849104A84F1001AF0A0 /* SPStage_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPStage_Internal.h; sourceTree = "<group>"; };
+ DE2CC35F11EF75B600439B43 /* SPCompiledSprite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPCompiledSprite.h; sourceTree = "<group>"; };
+ DE2CC36011EF75B600439B43 /* SPCompiledSprite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPCompiledSprite.m; sourceTree = "<group>"; };
+ DE2ED8040F6D52080012B6BA /* SPDisplayObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDisplayObject.h; sourceTree = "<group>"; };
+ DE2ED8050F6D52080012B6BA /* SPDisplayObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDisplayObject.m; sourceTree = "<group>"; };
+ DE2ED8080F6D53020012B6BA /* SPDisplayObjectContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDisplayObjectContainer.h; sourceTree = "<group>"; };
+ DE2ED8090F6D53020012B6BA /* SPDisplayObjectContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDisplayObjectContainer.m; sourceTree = "<group>"; };
+ DE2ED8550F6D54900012B6BA /* SPQuad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPQuad.h; sourceTree = "<group>"; };
+ DE2ED8560F6D54900012B6BA /* SPQuad.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPQuad.m; sourceTree = "<group>"; };
+ DE2ED8580F6D54AC0012B6BA /* SPStage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPStage.h; sourceTree = "<group>"; };
+ DE2ED8590F6D54AC0012B6BA /* SPStage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPStage.m; sourceTree = "<group>"; };
+ DE33072312D2EBCD009CC5E7 /* SPUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPUtils.h; sourceTree = "<group>"; };
+ DE33072412D2EBCD009CC5E7 /* SPUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPUtils.m; sourceTree = "<group>"; };
+ DE33072812D2ECB1009CC5E7 /* SPUtilsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPUtilsTest.m; sourceTree = "<group>"; };
+ DE469D240F9386FD00F56E91 /* SPMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMacros.h; sourceTree = "<group>"; };
+ DE469D250F9386FD00F56E91 /* SPMatrix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMatrix.h; sourceTree = "<group>"; };
+ DE469D260F9386FD00F56E91 /* SPMatrix.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMatrix.m; sourceTree = "<group>"; };
+ DE469D270F9386FD00F56E91 /* SPPoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPoint.h; sourceTree = "<group>"; };
+ DE469D280F9386FD00F56E91 /* SPPoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPoint.m; sourceTree = "<group>"; };
+ DE469D290F9386FD00F56E91 /* SPRectangle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPRectangle.h; sourceTree = "<group>"; };
+ DE469D2A0F9386FD00F56E91 /* SPRectangle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRectangle.m; sourceTree = "<group>"; };
+ DE469D6E0F938FAB00F56E91 /* SPDisplayObjectTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDisplayObjectTest.m; sourceTree = "<group>"; };
+ DE4D6AEA0F75913D0045CBF7 /* SPSprite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSprite.h; sourceTree = "<group>"; };
+ DE4D6AEB0F75913D0045CBF7 /* SPSprite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSprite.m; sourceTree = "<group>"; };
+ DE5286BA11F77C6200F916E8 /* SPDelayedInvocationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDelayedInvocationTest.m; sourceTree = "<group>"; };
+ DE68EA150FBB5660004DBC95 /* SPNSExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPNSExtensions.h; sourceTree = "<group>"; };
+ DE68EA160FBB5660004DBC95 /* SPNSExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPNSExtensions.m; sourceTree = "<group>"; };
+ DE7044260FB61506007F5ECC /* SPJuggler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPJuggler.h; sourceTree = "<group>"; };
+ DE7044270FB61506007F5ECC /* SPJuggler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPJuggler.m; sourceTree = "<group>"; };
+ DE70442A0FB618AE007F5ECC /* SPAnimatable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAnimatable.h; sourceTree = "<group>"; };
+ DE7044750FB62080007F5ECC /* SPTween.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTween.h; sourceTree = "<group>"; };
+ DE7044760FB62080007F5ECC /* SPTween.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTween.m; sourceTree = "<group>"; };
+ DE75E8660FBDC57E00C64495 /* SPTweenTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTweenTest.m; sourceTree = "<group>"; };
+ DE8F1E2D0F7C1F3A0085E9E4 /* SPMatrixTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMatrixTest.m; sourceTree = "<group>"; };
+ DE9CF1BC0FED2678008A32FF /* SPRendering.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRendering.m; sourceTree = "<group>"; };
+ DE9CF1EB0FED3128008A32FF /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
+ DEABCF5B0F7AE187003B6C9D /* SPPointTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPointTest.m; sourceTree = "<group>"; };
+ DEB21CF80F93C9780080D5C2 /* SPDisplayObjectContainerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDisplayObjectContainerTest.m; sourceTree = "<group>"; };
+ DEC2B6790F6D4CE90063AB1D /* SPView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPView.h; sourceTree = "<group>"; };
+ DEC2B67A0F6D4CE90063AB1D /* SPView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPView.m; sourceTree = "<group>"; };
+ DEC2B6800F6D51430063AB1D /* SPEventDispatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPEventDispatcher.h; sourceTree = "<group>"; };
+ DEC2B6810F6D51430063AB1D /* SPEventDispatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPEventDispatcher.m; sourceTree = "<group>"; };
+ DEC54F6211B7765500E439B0 /* SPMovieClipTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMovieClipTest.m; sourceTree = "<group>"; };
+ DECF84260FF619150026A4ED /* SPTextureAtlas.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTextureAtlas.h; sourceTree = "<group>"; };
+ DECF84270FF619150026A4ED /* SPTextureAtlas.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTextureAtlas.m; sourceTree = "<group>"; };
+ DECF84310FF649D50026A4ED /* SPGLTexture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPGLTexture.h; sourceTree = "<group>"; };
+ DECF84320FF649D50026A4ED /* SPGLTexture.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPGLTexture.m; sourceTree = "<group>"; };
+ DECF84B90FF681BA0026A4ED /* SPSubTexture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSubTexture.h; sourceTree = "<group>"; };
+ DECF84BA0FF681BA0026A4ED /* SPSubTexture.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSubTexture.m; sourceTree = "<group>"; };
+ DED2B6F90FA0CF5900083578 /* SPQuadTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPQuadTest.m; sourceTree = "<group>"; };
+ DED4EFC90FF9439D0093AD29 /* SPTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTextField.h; sourceTree = "<group>"; };
+ DED4EFCA0FF9439D0093AD29 /* SPTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTextField.m; sourceTree = "<group>"; };
+ DED67F330FA3514C0050E779 /* SPStageTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPStageTest.m; sourceTree = "<group>"; };
+ DED67F7C0FA359F00050E779 /* SPRectangleTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRectangleTest.m; sourceTree = "<group>"; };
+ DED85311100BC9DA0014F729 /* SPButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPButton.h; sourceTree = "<group>"; };
+ DED85312100BC9DA0014F729 /* SPButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPButton.m; sourceTree = "<group>"; };
+ DED859430FB883EE00D3D7D2 /* SPTransitions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTransitions.h; sourceTree = "<group>"; };
+ DED859440FB883EE00D3D7D2 /* SPTransitions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTransitions.m; sourceTree = "<group>"; };
+ DED9B51B10629D9F00989853 /* SPPoolObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPoolObject.h; sourceTree = "<group>"; };
+ DED9B51C10629D9F00989853 /* SPPoolObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPoolObject.m; sourceTree = "<group>"; };
+ DEDCD3AC0FADEE280022011C /* SPTouchProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTouchProcessor.h; sourceTree = "<group>"; };
+ DEDCD3AD0FADEE280022011C /* SPTouchProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTouchProcessor.m; sourceTree = "<group>"; };
+ DEDCD3CF0FADF52B0022011C /* SPTouch_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTouch_Internal.h; sourceTree = "<group>"; };
+ DEDCD44A0FADFF250022011C /* SPEvent_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPEvent_Internal.h; sourceTree = "<group>"; };
+ DEDCD44E0FADFFA40022011C /* SPDisplayObject_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDisplayObject_Internal.h; sourceTree = "<group>"; };
+ DEE09D78108364A900ECC896 /* SPBitmapFont.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPBitmapFont.h; sourceTree = "<group>"; };
+ DEE09D79108364A900ECC896 /* SPBitmapFont.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPBitmapFont.m; sourceTree = "<group>"; };
+ DEE09D7C108369AE00ECC896 /* SPBitmapChar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPBitmapChar.h; sourceTree = "<group>"; };
+ DEE09D7D108369AE00ECC896 /* SPBitmapChar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPBitmapChar.m; sourceTree = "<group>"; };
+ DEE4449C0FABA7860085D36D /* SPTouch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTouch.h; sourceTree = "<group>"; };
+ DEE4449D0FABA7860085D36D /* SPTouch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTouch.m; sourceTree = "<group>"; };
+ DEE444A80FABABBB0085D36D /* SPTouchEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTouchEvent.h; sourceTree = "<group>"; };
+ DEE444A90FABABBB0085D36D /* SPTouchEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTouchEvent.m; sourceTree = "<group>"; };
+ DEE593D10FA6116B00E3AEFC /* SPEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPEvent.h; sourceTree = "<group>"; };
+ DEE593D20FA6116B00E3AEFC /* SPEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPEvent.m; sourceTree = "<group>"; };
+ DEE594490FA63BA800E3AEFC /* SPEventDispatcherTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPEventDispatcherTest.m; sourceTree = "<group>"; };
+ DEE63A4B11AED38100D60321 /* SPAudioEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAudioEngine.h; sourceTree = "<group>"; };
+ DEE63A4C11AED38100D60321 /* SPAudioEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAudioEngine.m; sourceTree = "<group>"; };
+ DEE63A4D11AED38100D60321 /* SPSound.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSound.h; sourceTree = "<group>"; };
+ DEE63A4E11AED38100D60321 /* SPSound.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSound.m; sourceTree = "<group>"; };
+ DEE63A4F11AED38100D60321 /* SPSoundChannel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSoundChannel.h; sourceTree = "<group>"; };
+ DEE63A5011AED38100D60321 /* SPSoundChannel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSoundChannel.m; sourceTree = "<group>"; };
+ DEE63A7111AED4FA00D60321 /* OpenAL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenAL.framework; path = System/Library/Frameworks/OpenAL.framework; sourceTree = SDKROOT; };
+ DEE63A8511AED51400D60321 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
+ DEE94E8011B43DE60000FE20 /* SPMovieClip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMovieClip.h; sourceTree = "<group>"; };
+ DEE94E8111B43DE60000FE20 /* SPMovieClip.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMovieClip.m; sourceTree = "<group>"; };
+ DEEA9335101E32A30071DD21 /* UnitTest.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTest.octest; sourceTree = BUILT_PRODUCTS_DIR; };
+ DEEA937F101E3BA20071DD21 /* UnitTests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "UnitTests-Info.plist"; sourceTree = "<group>"; };
+ DEEA9402101E44F10071DD21 /* Sparrow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Sparrow.h; sourceTree = "<group>"; };
+ DEED1737108A50000071438F /* SPTweenedProperty.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTweenedProperty.h; sourceTree = "<group>"; };
+ DEED1738108A50000071438F /* SPTweenedProperty.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTweenedProperty.m; sourceTree = "<group>"; };
+ DEEEB8C80FAA2C300059D72B /* SPEnterFrameEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPEnterFrameEvent.h; sourceTree = "<group>"; };
+ DEEEB8C90FAA2C300059D72B /* SPEnterFrameEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPEnterFrameEvent.m; sourceTree = "<group>"; };
+ DEF1730C11B0645A00A11DD7 /* SPALSound.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPALSound.h; sourceTree = "<group>"; };
+ DEF1730D11B0645A00A11DD7 /* SPALSound.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPALSound.m; sourceTree = "<group>"; };
+ DEF1731011B0648B00A11DD7 /* SPALSoundChannel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPALSoundChannel.h; sourceTree = "<group>"; };
+ DEF1731111B0648B00A11DD7 /* SPALSoundChannel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPALSoundChannel.m; sourceTree = "<group>"; };
+ DEF1735A11B075B000A11DD7 /* SPAVSound.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAVSound.h; sourceTree = "<group>"; };
+ DEF1735B11B075B000A11DD7 /* SPAVSound.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAVSound.m; sourceTree = "<group>"; };
+ DEF1735E11B075BC00A11DD7 /* SPAVSoundChannel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAVSoundChannel.h; sourceTree = "<group>"; };
+ DEF1735F11B075BC00A11DD7 /* SPAVSoundChannel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAVSoundChannel.m; sourceTree = "<group>"; };
+ DEFB1B93100926260022C117 /* SPDelayedInvocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDelayedInvocation.h; sourceTree = "<group>"; };
+ DEFB1B94100926260022C117 /* SPDelayedInvocation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDelayedInvocation.m; sourceTree = "<group>"; };
+ DEFE4BC2101B317600E22471 /* libSparrow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSparrow.a; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ DEEA9332101E32A30071DD21 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEEA934E101E33F00071DD21 /* libSparrow.a in Frameworks */,
+ DEEA933A101E32E10071DD21 /* CoreGraphics.framework in Frameworks */,
+ DEEA933B101E32E10071DD21 /* Foundation.framework in Frameworks */,
+ DEEA933C101E32E10071DD21 /* OpenGLES.framework in Frameworks */,
+ DEEA933D101E32E10071DD21 /* QuartzCore.framework in Frameworks */,
+ DEEA933E101E32E10071DD21 /* UIKit.framework in Frameworks */,
+ DEEDE3A111B29908009FD4C8 /* OpenAL.framework in Frameworks */,
+ DEEDE3A211B29908009FD4C8 /* AVFoundation.framework in Frameworks */,
+ DEEDE3A311B29909009FD4C8 /* AudioToolbox.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEFE4BC0101B317600E22471 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DED8B48711B0827E00A1766C /* Foundation.framework in Frameworks */,
+ DED8B48811B0827E00A1766C /* CoreGraphics.framework in Frameworks */,
+ DED8B48911B0827E00A1766C /* QuartzCore.framework in Frameworks */,
+ DED8B48A11B0827E00A1766C /* OpenGLES.framework in Frameworks */,
+ DED8B48B11B0827E00A1766C /* UIKit.framework in Frameworks */,
+ DED8B48C11B0827E00A1766C /* OpenAL.framework in Frameworks */,
+ DED8B48D11B0827E00A1766C /* AudioToolbox.framework in Frameworks */,
+ DE18FE4511B083B200D01F04 /* AVFoundation.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 080E96DDFE201D6D7F000001 /* Classes */ = {
+ isa = PBXGroup;
+ children = (
+ DEE63A4A11AED36700D60321 /* Audio */,
+ DEDCD3CE0FADF2100022011C /* System */,
+ DEDCD3C60FADF1870022011C /* Events */,
+ DEDCD3B00FADF1720022011C /* Display */,
+ DEE968F51086553D001D57E9 /* Textures */,
+ DEE202BD0FBDAD2900B8A467 /* Animation */,
+ DE469D310F93870A00F56E91 /* Utils */,
+ DEEA9402101E44F10071DD21 /* Sparrow.h */,
+ );
+ path = Classes;
+ sourceTree = "<group>";
+ };
+ 19C28FACFE9D520D11CA2CBB /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ DEFE4BC2101B317600E22471 /* libSparrow.a */,
+ DEEA9335101E32A30071DD21 /* UnitTest.octest */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = {
+ isa = PBXGroup;
+ children = (
+ DEABCDE80F7ABE71003B6C9D /* UnitTests */,
+ 080E96DDFE201D6D7F000001 /* Classes */,
+ 29B97317FDCFA39411CA2CEA /* Resources */,
+ 29B97323FDCFA39411CA2CEA /* Frameworks */,
+ 19C28FACFE9D520D11CA2CBB /* Products */,
+ );
+ name = CustomTemplate;
+ sourceTree = "<group>";
+ };
+ 29B97317FDCFA39411CA2CEA /* Resources */ = {
+ isa = PBXGroup;
+ children = (
+ DEEA937F101E3BA20071DD21 /* UnitTests-Info.plist */,
+ );
+ name = Resources;
+ sourceTree = "<group>";
+ };
+ 29B97323FDCFA39411CA2CEA /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 1D30AB110D05D00D00671497 /* Foundation.framework */,
+ DE9CF1EB0FED3128008A32FF /* CoreGraphics.framework */,
+ 28FD15070DC6FC5B0079059D /* QuartzCore.framework */,
+ 28FD14FF0DC6FC520079059D /* OpenGLES.framework */,
+ 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */,
+ DEE63A7111AED4FA00D60321 /* OpenAL.framework */,
+ DEE63A8511AED51400D60321 /* AudioToolbox.framework */,
+ DE18FE4411B083B200D01F04 /* AVFoundation.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ DE20D9C810713AD0006658C9 /* Rendering */ = {
+ isa = PBXGroup;
+ children = (
+ DE9CF1BC0FED2678008A32FF /* SPRendering.m */,
+ DE20D9C910713B0C006658C9 /* SPRenderSupport.h */,
+ DE20D9CA10713B0C006658C9 /* SPRenderSupport.m */,
+ );
+ name = Rendering;
+ sourceTree = "<group>";
+ };
+ DE469D310F93870A00F56E91 /* Utils */ = {
+ isa = PBXGroup;
+ children = (
+ DE469D240F9386FD00F56E91 /* SPMacros.h */,
+ DE469D250F9386FD00F56E91 /* SPMatrix.h */,
+ DE469D260F9386FD00F56E91 /* SPMatrix.m */,
+ DE469D270F9386FD00F56E91 /* SPPoint.h */,
+ DE469D280F9386FD00F56E91 /* SPPoint.m */,
+ DE469D290F9386FD00F56E91 /* SPRectangle.h */,
+ DE469D2A0F9386FD00F56E91 /* SPRectangle.m */,
+ DE68EA150FBB5660004DBC95 /* SPNSExtensions.h */,
+ DE68EA160FBB5660004DBC95 /* SPNSExtensions.m */,
+ DED9B51B10629D9F00989853 /* SPPoolObject.h */,
+ DED9B51C10629D9F00989853 /* SPPoolObject.m */,
+ DE33072312D2EBCD009CC5E7 /* SPUtils.h */,
+ DE33072412D2EBCD009CC5E7 /* SPUtils.m */,
+ );
+ name = Utils;
+ sourceTree = "<group>";
+ };
+ DEABCDE80F7ABE71003B6C9D /* UnitTests */ = {
+ isa = PBXGroup;
+ children = (
+ DE5286BA11F77C6200F916E8 /* SPDelayedInvocationTest.m */,
+ DEABCF5B0F7AE187003B6C9D /* SPPointTest.m */,
+ DE8F1E2D0F7C1F3A0085E9E4 /* SPMatrixTest.m */,
+ DE469D6E0F938FAB00F56E91 /* SPDisplayObjectTest.m */,
+ DEB21CF80F93C9780080D5C2 /* SPDisplayObjectContainerTest.m */,
+ DED2B6F90FA0CF5900083578 /* SPQuadTest.m */,
+ DED67F330FA3514C0050E779 /* SPStageTest.m */,
+ DED67F7C0FA359F00050E779 /* SPRectangleTest.m */,
+ DEE594490FA63BA800E3AEFC /* SPEventDispatcherTest.m */,
+ DE75E8660FBDC57E00C64495 /* SPTweenTest.m */,
+ DE0853A40FEC286900DAF53C /* SPImageTest.m */,
+ DE1F9446104704440084D470 /* SPJugglerTest.m */,
+ DEC54F6211B7765500E439B0 /* SPMovieClipTest.m */,
+ DE05748611E915A900F3A8A4 /* SPNSExtensionsTest.m */,
+ DE33072812D2ECB1009CC5E7 /* SPUtilsTest.m */,
+ );
+ path = UnitTests;
+ sourceTree = "<group>";
+ };
+ DED85A930FB49E6D000BAA95 /* Internal */ = {
+ isa = PBXGroup;
+ children = (
+ DEDCD3CF0FADF52B0022011C /* SPTouch_Internal.h */,
+ DEDCD44A0FADFF250022011C /* SPEvent_Internal.h */,
+ );
+ name = Internal;
+ sourceTree = "<group>";
+ };
+ DED85A940FB49E90000BAA95 /* Internal */ = {
+ isa = PBXGroup;
+ children = (
+ DEDCD3AC0FADEE280022011C /* SPTouchProcessor.h */,
+ DEDCD3AD0FADEE280022011C /* SPTouchProcessor.m */,
+ DEDCD44E0FADFFA40022011C /* SPDisplayObject_Internal.h */,
+ DE2AD849104A84F1001AF0A0 /* SPStage_Internal.h */,
+ );
+ name = Internal;
+ sourceTree = "<group>";
+ };
+ DEDCD3B00FADF1720022011C /* Display */ = {
+ isa = PBXGroup;
+ children = (
+ DE20D9C810713AD0006658C9 /* Rendering */,
+ DEE09D771083647D00ECC896 /* Text */,
+ DED85A940FB49E90000BAA95 /* Internal */,
+ DE2ED8040F6D52080012B6BA /* SPDisplayObject.h */,
+ DE2ED8050F6D52080012B6BA /* SPDisplayObject.m */,
+ DE2ED8080F6D53020012B6BA /* SPDisplayObjectContainer.h */,
+ DE2ED8090F6D53020012B6BA /* SPDisplayObjectContainer.m */,
+ DE4D6AEA0F75913D0045CBF7 /* SPSprite.h */,
+ DE4D6AEB0F75913D0045CBF7 /* SPSprite.m */,
+ DE2ED8580F6D54AC0012B6BA /* SPStage.h */,
+ DE2ED8590F6D54AC0012B6BA /* SPStage.m */,
+ DE2ED8550F6D54900012B6BA /* SPQuad.h */,
+ DE2ED8560F6D54900012B6BA /* SPQuad.m */,
+ DE08535C0FEC21F500DAF53C /* SPImage.h */,
+ DE08535D0FEC21F500DAF53C /* SPImage.m */,
+ DED85311100BC9DA0014F729 /* SPButton.h */,
+ DED85312100BC9DA0014F729 /* SPButton.m */,
+ DEE94E8011B43DE60000FE20 /* SPMovieClip.h */,
+ DEE94E8111B43DE60000FE20 /* SPMovieClip.m */,
+ DE2CC35F11EF75B600439B43 /* SPCompiledSprite.h */,
+ DE2CC36011EF75B600439B43 /* SPCompiledSprite.m */,
+ );
+ name = Display;
+ sourceTree = "<group>";
+ };
+ DEDCD3C60FADF1870022011C /* Events */ = {
+ isa = PBXGroup;
+ children = (
+ DED85A930FB49E6D000BAA95 /* Internal */,
+ DEE593D10FA6116B00E3AEFC /* SPEvent.h */,
+ DEE593D20FA6116B00E3AEFC /* SPEvent.m */,
+ DEC2B6800F6D51430063AB1D /* SPEventDispatcher.h */,
+ DEC2B6810F6D51430063AB1D /* SPEventDispatcher.m */,
+ DEE4449C0FABA7860085D36D /* SPTouch.h */,
+ DEE4449D0FABA7860085D36D /* SPTouch.m */,
+ DEE444A80FABABBB0085D36D /* SPTouchEvent.h */,
+ DEE444A90FABABBB0085D36D /* SPTouchEvent.m */,
+ DEEEB8C80FAA2C300059D72B /* SPEnterFrameEvent.h */,
+ DEEEB8C90FAA2C300059D72B /* SPEnterFrameEvent.m */,
+ );
+ name = Events;
+ sourceTree = "<group>";
+ };
+ DEDCD3CE0FADF2100022011C /* System */ = {
+ isa = PBXGroup;
+ children = (
+ DEC2B6790F6D4CE90063AB1D /* SPView.h */,
+ DEC2B67A0F6D4CE90063AB1D /* SPView.m */,
+ );
+ name = System;
+ sourceTree = "<group>";
+ };
+ DEE09D771083647D00ECC896 /* Text */ = {
+ isa = PBXGroup;
+ children = (
+ DED4EFC90FF9439D0093AD29 /* SPTextField.h */,
+ DED4EFCA0FF9439D0093AD29 /* SPTextField.m */,
+ DEE09D78108364A900ECC896 /* SPBitmapFont.h */,
+ DEE09D79108364A900ECC896 /* SPBitmapFont.m */,
+ DEE09D7C108369AE00ECC896 /* SPBitmapChar.h */,
+ DEE09D7D108369AE00ECC896 /* SPBitmapChar.m */,
+ );
+ name = Text;
+ sourceTree = "<group>";
+ };
+ DEE202BD0FBDAD2900B8A467 /* Animation */ = {
+ isa = PBXGroup;
+ children = (
+ DEED1736108A4FE30071438F /* Internal */,
+ DE70442A0FB618AE007F5ECC /* SPAnimatable.h */,
+ DE7044260FB61506007F5ECC /* SPJuggler.h */,
+ DE7044270FB61506007F5ECC /* SPJuggler.m */,
+ DED859430FB883EE00D3D7D2 /* SPTransitions.h */,
+ DED859440FB883EE00D3D7D2 /* SPTransitions.m */,
+ DE7044750FB62080007F5ECC /* SPTween.h */,
+ DE7044760FB62080007F5ECC /* SPTween.m */,
+ DEFB1B93100926260022C117 /* SPDelayedInvocation.h */,
+ DEFB1B94100926260022C117 /* SPDelayedInvocation.m */,
+ );
+ name = Animation;
+ sourceTree = "<group>";
+ };
+ DEE63A4A11AED36700D60321 /* Audio */ = {
+ isa = PBXGroup;
+ children = (
+ DEF1731511B064A300A11DD7 /* AVFoundation */,
+ DEF1731411B0649A00A11DD7 /* OpenAL */,
+ DEE63A4B11AED38100D60321 /* SPAudioEngine.h */,
+ DEE63A4C11AED38100D60321 /* SPAudioEngine.m */,
+ DEE63A4D11AED38100D60321 /* SPSound.h */,
+ DEE63A4E11AED38100D60321 /* SPSound.m */,
+ DEE63A4F11AED38100D60321 /* SPSoundChannel.h */,
+ DEE63A5011AED38100D60321 /* SPSoundChannel.m */,
+ );
+ name = Audio;
+ sourceTree = "<group>";
+ };
+ DEE968F51086553D001D57E9 /* Textures */ = {
+ isa = PBXGroup;
+ children = (
+ DE0853F80FEC2CFF00DAF53C /* SPTexture.h */,
+ DE0853F90FEC2CFF00DAF53C /* SPTexture.m */,
+ DECF84310FF649D50026A4ED /* SPGLTexture.h */,
+ DECF84320FF649D50026A4ED /* SPGLTexture.m */,
+ DECF84B90FF681BA0026A4ED /* SPSubTexture.h */,
+ DECF84BA0FF681BA0026A4ED /* SPSubTexture.m */,
+ DE13D18912AADBF6000C77E6 /* SPRenderTexture.h */,
+ DE13D18A12AADBF6000C77E6 /* SPRenderTexture.m */,
+ DECF84260FF619150026A4ED /* SPTextureAtlas.h */,
+ DECF84270FF619150026A4ED /* SPTextureAtlas.m */,
+ );
+ name = Textures;
+ sourceTree = "<group>";
+ };
+ DEED1736108A4FE30071438F /* Internal */ = {
+ isa = PBXGroup;
+ children = (
+ DEED1737108A50000071438F /* SPTweenedProperty.h */,
+ DEED1738108A50000071438F /* SPTweenedProperty.m */,
+ );
+ name = Internal;
+ sourceTree = "<group>";
+ };
+ DEF1731411B0649A00A11DD7 /* OpenAL */ = {
+ isa = PBXGroup;
+ children = (
+ DEF1730C11B0645A00A11DD7 /* SPALSound.h */,
+ DEF1730D11B0645A00A11DD7 /* SPALSound.m */,
+ DEF1731011B0648B00A11DD7 /* SPALSoundChannel.h */,
+ DEF1731111B0648B00A11DD7 /* SPALSoundChannel.m */,
+ );
+ name = OpenAL;
+ sourceTree = "<group>";
+ };
+ DEF1731511B064A300A11DD7 /* AVFoundation */ = {
+ isa = PBXGroup;
+ children = (
+ DEF1735A11B075B000A11DD7 /* SPAVSound.h */,
+ DEF1735B11B075B000A11DD7 /* SPAVSound.m */,
+ DEF1735E11B075BC00A11DD7 /* SPAVSoundChannel.h */,
+ DEF1735F11B075BC00A11DD7 /* SPAVSoundChannel.m */,
+ );
+ name = AVFoundation;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ DEFE4BBE101B317600E22471 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DE13D18B12AADBF6000C77E6 /* SPRenderTexture.h in Headers */,
+ DE33072512D2EBCD009CC5E7 /* SPUtils.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+ DEEA9334101E32A30071DD21 /* UnitTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DEEA9339101E32A40071DD21 /* Build configuration list for PBXNativeTarget "UnitTests" */;
+ buildPhases = (
+ DEEA9331101E32A30071DD21 /* Sources */,
+ DEEA9332101E32A30071DD21 /* Frameworks */,
+ DEEA9333101E32A30071DD21 /* ShellScript */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ DEEA934D101E33ED0071DD21 /* PBXTargetDependency */,
+ );
+ name = UnitTests;
+ productName = UnitTest;
+ productReference = DEEA9335101E32A30071DD21 /* UnitTest.octest */;
+ productType = "com.apple.product-type.bundle";
+ };
+ DEFE4BC1101B317600E22471 /* Sparrow */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DEFE4BCC101B317A00E22471 /* Build configuration list for PBXNativeTarget "Sparrow" */;
+ buildPhases = (
+ DEFE4BBE101B317600E22471 /* Headers */,
+ DEFE4BBF101B317600E22471 /* Sources */,
+ DEFE4BC0101B317600E22471 /* Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Sparrow;
+ productName = SparrowLib;
+ productReference = DEFE4BC2101B317600E22471 /* libSparrow.a */;
+ productType = "com.apple.product-type.library.static";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 29B97313FDCFA39411CA2CEA /* Project object */ = {
+ isa = PBXProject;
+ buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Sparrow" */;
+ compatibilityVersion = "Xcode 3.1";
+ developmentRegion = English;
+ hasScannedForEncodings = 1;
+ knownRegions = (
+ English,
+ Japanese,
+ French,
+ German,
+ );
+ mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ DEFE4BC1101B317600E22471 /* Sparrow */,
+ DEEA9334101E32A30071DD21 /* UnitTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ DEEA9333101E32A30071DD21 /* ShellScript */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n\n# in Xcode 3.2.4, use the following line instead:\n# \"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\" 1> /tmp/RunUnitTests.out \n";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ DEEA9331101E32A30071DD21 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DE14412E12421EA600FFA95A /* SPNSExtensionsTest.m in Sources */,
+ DE14413612421F4700FFA95A /* SPImageTest.m in Sources */,
+ DE14413712421F4800FFA95A /* SPDisplayObjectTest.m in Sources */,
+ DE14413812421F4800FFA95A /* SPMovieClipTest.m in Sources */,
+ DE14413912421F4900FFA95A /* SPRectangleTest.m in Sources */,
+ DE14413A12421F4900FFA95A /* SPDisplayObjectContainerTest.m in Sources */,
+ DE14413B12421F4A00FFA95A /* SPEventDispatcherTest.m in Sources */,
+ DE14413C12421F4B00FFA95A /* SPDelayedInvocationTest.m in Sources */,
+ DE14413D12421F4B00FFA95A /* SPMatrixTest.m in Sources */,
+ DE14413E12421F4B00FFA95A /* SPPointTest.m in Sources */,
+ DE14413F12421F4C00FFA95A /* SPQuadTest.m in Sources */,
+ DE14414012421F4C00FFA95A /* SPJugglerTest.m in Sources */,
+ DE14414112421F4D00FFA95A /* SPTweenTest.m in Sources */,
+ DE14414212421F4E00FFA95A /* SPStageTest.m in Sources */,
+ DE33072B12D2ED1A009CC5E7 /* SPUtilsTest.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEFE4BBF101B317600E22471 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEFE4BE6101B31DF00E22471 /* SPTextureAtlas.m in Sources */,
+ DEFE4C38101B5FB100E22471 /* SPRendering.m in Sources */,
+ DEFE4C3A101B5FB100E22471 /* SPTouchProcessor.m in Sources */,
+ DEFE4BE2101B31DF00E22471 /* SPMatrix.m in Sources */,
+ DEFE4BE3101B31DF00E22471 /* SPPoint.m in Sources */,
+ DEFE4BE4101B31DF00E22471 /* SPRectangle.m in Sources */,
+ DEFE4BD7101B31DF00E22471 /* SPDisplayObject.m in Sources */,
+ DEFE4BD8101B31DF00E22471 /* SPDisplayObjectContainer.m in Sources */,
+ DEFE4BD9101B31DF00E22471 /* SPSprite.m in Sources */,
+ DEFE4BDA101B31DF00E22471 /* SPStage.m in Sources */,
+ DEFE4BDB101B31DF00E22471 /* SPQuad.m in Sources */,
+ DEFE4BDC101B31DF00E22471 /* SPImage.m in Sources */,
+ DEFE4BDD101B31DF00E22471 /* SPTextField.m in Sources */,
+ DEFE4BDE101B31DF00E22471 /* SPTexture.m in Sources */,
+ DEFE4BDF101B31DF00E22471 /* SPGLTexture.m in Sources */,
+ DEFE4BE0101B31DF00E22471 /* SPSubTexture.m in Sources */,
+ DEFE4BE1101B31DF00E22471 /* SPButton.m in Sources */,
+ DEFE4BD2101B31DF00E22471 /* SPEvent.m in Sources */,
+ DEFE4BD3101B31DF00E22471 /* SPEventDispatcher.m in Sources */,
+ DEFE4BD4101B31DF00E22471 /* SPTouch.m in Sources */,
+ DEFE4BD5101B31DF00E22471 /* SPTouchEvent.m in Sources */,
+ DEFE4BD6101B31DF00E22471 /* SPEnterFrameEvent.m in Sources */,
+ DEFE4BD1101B31DF00E22471 /* SPView.m in Sources */,
+ DEFE4BCE101B31DF00E22471 /* SPTransitions.m in Sources */,
+ DE019C391026360B00ECB0AC /* SPTween.m in Sources */,
+ DE019C3A1026361200ECB0AC /* SPDelayedInvocation.m in Sources */,
+ DE019C3B1026363D00ECB0AC /* SPNSExtensions.m in Sources */,
+ DE019C3C1026364800ECB0AC /* SPJuggler.m in Sources */,
+ DED9B51E10629D9F00989853 /* SPPoolObject.m in Sources */,
+ DE20D9CC10713B0C006658C9 /* SPRenderSupport.m in Sources */,
+ DEE09D7B108364A900ECC896 /* SPBitmapFont.m in Sources */,
+ DEE09D7F108369AE00ECC896 /* SPBitmapChar.m in Sources */,
+ DEED173A108A50000071438F /* SPTweenedProperty.m in Sources */,
+ DEE63A5211AED38100D60321 /* SPAudioEngine.m in Sources */,
+ DEE63A5411AED38100D60321 /* SPSound.m in Sources */,
+ DEE63A5611AED38100D60321 /* SPSoundChannel.m in Sources */,
+ DEF1730F11B0645A00A11DD7 /* SPALSound.m in Sources */,
+ DEF1731311B0648B00A11DD7 /* SPALSoundChannel.m in Sources */,
+ DEF1735D11B075B000A11DD7 /* SPAVSound.m in Sources */,
+ DEF1736111B075BC00A11DD7 /* SPAVSoundChannel.m in Sources */,
+ DEE94E8311B43DE60000FE20 /* SPMovieClip.m in Sources */,
+ DEE6516B11F9DF200065207E /* SPCompiledSprite.m in Sources */,
+ DE13D18C12AADBF6000C77E6 /* SPRenderTexture.m in Sources */,
+ DE33072612D2EBCD009CC5E7 /* SPUtils.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ DEEA934D101E33ED0071DD21 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DEFE4BC1101B317600E22471 /* Sparrow */;
+ targetProxy = DEEA934C101E33ED0071DD21 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ C01FCF4F08A954540054247B /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ GCC_C_LANGUAGE_STANDARD = c99;
+ GCC_THUMB_SUPPORT = NO;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ PREBINDING = NO;
+ "PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
+ SDKROOT = iphoneos;
+ };
+ name = Debug;
+ };
+ C01FCF5008A954540054247B /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ GCC_C_LANGUAGE_STANDARD = c99;
+ GCC_THUMB_SUPPORT = NO;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ PREBINDING = NO;
+ SDKROOT = iphoneos;
+ };
+ name = Release;
+ };
+ DEEA9337101E32A40071DD21 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ COPY_PHASE_STRIP = NO;
+ FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks";
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_ENABLE_FIX_AND_CONTINUE = NO;
+ GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ INFOPLIST_FILE = "UnitTests-Info.plist";
+ OTHER_LDFLAGS = (
+ "-framework",
+ Foundation,
+ "-framework",
+ SenTestingKit,
+ "-framework",
+ UIKit,
+ "-ObjC",
+ "-all_load",
+ );
+ PREBINDING = NO;
+ PRODUCT_NAME = UnitTest;
+ WRAPPER_EXTENSION = octest;
+ };
+ name = Debug;
+ };
+ DEEA9338101E32A40071DD21 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ COPY_PHASE_STRIP = YES;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks";
+ GCC_ENABLE_FIX_AND_CONTINUE = NO;
+ GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+ INFOPLIST_FILE = "UnitTests-Info.plist";
+ OTHER_LDFLAGS = (
+ "-framework",
+ Foundation,
+ "-framework",
+ SenTestingKit,
+ "-framework",
+ UIKit,
+ "-ObjC",
+ "-all_load",
+ );
+ PREBINDING = NO;
+ PRODUCT_NAME = UnitTest;
+ WRAPPER_EXTENSION = octest;
+ ZERO_LINK = NO;
+ };
+ name = Release;
+ };
+ DEFE4BC3101B317700E22471 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = DEBUG;
+ GCC_THUMB_SUPPORT = NO;
+ GCC_WARN_PEDANTIC = NO;
+ OTHER_LDFLAGS = "";
+ PREBINDING = NO;
+ PRODUCT_NAME = Sparrow;
+ "PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
+ SKIP_INSTALL = YES;
+ };
+ name = Debug;
+ };
+ DEFE4BC4101B317700E22471 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = YES;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ GCC_ENABLE_FIX_AND_CONTINUE = NO;
+ GCC_PREPROCESSOR_DEFINITIONS = "";
+ GCC_THUMB_SUPPORT = NO;
+ GCC_WARN_PEDANTIC = NO;
+ OTHER_LDFLAGS = "";
+ PREBINDING = NO;
+ PRODUCT_NAME = Sparrow;
+ "PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
+ SKIP_INSTALL = YES;
+ ZERO_LINK = NO;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Sparrow" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ C01FCF4F08A954540054247B /* Debug */,
+ C01FCF5008A954540054247B /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DEEA9339101E32A40071DD21 /* Build configuration list for PBXNativeTarget "UnitTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DEEA9337101E32A40071DD21 /* Debug */,
+ DEEA9338101E32A40071DD21 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DEFE4BCC101B317A00E22471 /* Build configuration list for PBXNativeTarget "Sparrow" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DEFE4BC3101B317700E22471 /* Debug */,
+ DEFE4BC4101B317700E22471 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.incognitek.${PRODUCT_NAME:identifier}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+</dict>
+</plist>
--- /dev/null
+//
+// SPDelayedInvocationTest.m
+// Sparrow
+//
+// Created by Daniel Sperl on 10.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 <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "SPDelayedInvocation.h"
+#import "SPMacros.h"
+
+// -------------------------------------------------------------------------------------------------
+
+static int dummyDeallocCount = 0;
+
+@interface DummyClass : NSObject
+
+@end
+
+@implementation DummyClass
+
+- (void)dealloc
+{
+ ++dummyDeallocCount;
+ [super dealloc];
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPDelayedInvocationTest : SenTestCase
+{
+ int mCallCount;
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPDelayedInvocationTest
+
+- (void)setUp
+{
+ mCallCount = 0;
+ dummyDeallocCount = 0;
+}
+
+- (void)simpleMethod
+{
+ ++mCallCount;
+}
+
+- (void)methodWithArgument:(DummyClass *)dummy
+{
+ [dummy description]; // just to check that dummy has not been released
+ ++mCallCount;
+}
+
+- (void)testSimpleDelay
+{
+ id delayedInv = [[SPDelayedInvocation alloc] initWithTarget:self delay:1.0f];
+ [delayedInv simpleMethod];
+
+ STAssertEquals(0, mCallCount, @"Delayed Invocation triggered too soon");
+ [delayedInv advanceTime:0.5f];
+
+ STAssertEquals(0, mCallCount, @"Delayed Invocation triggered too soon");
+ [delayedInv advanceTime:0.49f];
+
+ STAssertEquals(0, mCallCount, @"Delayed Invocation triggered too soon");
+
+ [delayedInv advanceTime:0.1f];
+ STAssertEquals(1, mCallCount, @"Delayed Invocation did not trigger");
+
+ [delayedInv advanceTime:0.1f];
+ STAssertEquals(1, mCallCount, @"Delayed Invocation triggered too often");
+
+ [delayedInv release];
+}
+
+- (void)testDelayWithArguments
+{
+ SP_CREATE_POOL(pool);
+
+ // test dummy mechanism
+
+ DummyClass *dummy = [[DummyClass alloc] init];
+ [dummy release];
+ STAssertEquals(1, dummyDeallocCount, @"dummy release not counted");
+
+ // now test if retain/release cycle of delayed invocation works
+
+ dummy = [[DummyClass alloc] init];
+ id delayedInv = [[SPDelayedInvocation alloc] initWithTarget:self delay:1.0f];
+ [delayedInv methodWithArgument:dummy];
+
+ STAssertEquals(0, mCallCount, @"Delayed Invocation triggered too soon");
+
+ [dummy release];
+ STAssertEquals(1, dummyDeallocCount, @"Argument not retained");
+
+ [delayedInv advanceTime:1.0f];
+ STAssertEquals(1, mCallCount, @"Delayed Invocation did not trigger");
+
+ SP_RELEASE_POOL(pool);
+
+ [delayedInv release];
+ STAssertEquals(2, dummyDeallocCount, @"Argument not released");
+}
+
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+//
+// SPDisplayObjectContainerTest.m
+// Sparrow
+//
+// Created by Daniel Sperl on 13.04.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 <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPMatrix.h"
+#import "SPMacros.h"
+#import "SPPoint.h"
+#import "SPSprite.h"
+#import "SPQuad.h"
+#import "SPStage.h"
+#import "SPRectangle.h"
+#import "SPDisplayObject_Internal.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPDisplayObjectContainerTest : SenTestCase
+{
+ int mAdded;
+ int mAddedToStage;
+ int mRemoved;
+ int mRemovedFromStage;
+ int mEventCount;
+ SPSprite *mTestSprite;
+}
+
+- (void)addQuadToSprite:(SPSprite*)sprite;
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPDisplayObjectContainerTest
+
+#define E 0.0001f
+
+- (void) setUp
+{
+ mAdded = mAddedToStage = mRemoved = mRemovedFromStage = mEventCount = 0;
+ mTestSprite = [[SPSprite alloc] init];
+}
+
+- (void) tearDown
+{
+ [mTestSprite release];
+}
+
+- (void)testChildParentHandling
+{
+ SPSprite *parent = [[SPSprite alloc] init];
+ SPSprite *child1 = [[SPSprite alloc] init];
+ SPSprite *child2 = [[SPSprite alloc] init];
+
+ STAssertEquals(0, parent.numChildren, @"wrong number of children");
+ STAssertNil(child1.parent, @"parent not nil");
+
+ [parent addChild:child1];
+ STAssertEquals(1, parent.numChildren, @"wrong number of children");
+ STAssertEqualObjects(parent, child1.parent, @"invalid parent");
+
+ [parent addChild:child2];
+ STAssertEquals(2, parent.numChildren, @"wrong number of children");
+ STAssertEqualObjects(parent, child2.parent, @"invalid parent");
+ STAssertEqualObjects(child1, [parent childAtIndex:0], @"wrong child index");
+ STAssertEqualObjects(child2, [parent childAtIndex:1], @"wrong child index");
+
+ [parent removeChild:child1];
+ STAssertNil(child1.parent, @"parent not nil");
+ STAssertEqualObjects(child2, [parent childAtIndex:0], @"wrong child index");
+ STAssertNoThrow([child1 removeFromParent], @"exception raised");
+
+ [child2 addChild:child1];
+ STAssertTrue([parent containsChild:child1], @"child not found");
+ STAssertTrue([parent containsChild:child2], @"child not found");
+ STAssertEqualObjects(child2, child1.parent, @"invalid parent");
+
+ [parent addChild:child1 atIndex:0];
+ STAssertEqualObjects(parent, child1.parent, @"invalid parent");
+ STAssertFalse([child2 containsChild:child1], @"invalid connection");
+ STAssertEqualObjects(child1, [parent childAtIndex:0], @"wrong child");
+ STAssertEqualObjects(child2, [parent childAtIndex:1], @"wrong child");
+
+ [child2 release];
+ [child1 release];
+ [parent release];
+}
+
+- (void)testWidthAndHeight
+{
+ SPSprite *sprite = [[SPSprite alloc] init];
+
+ SPQuad *quad1 = [[SPQuad alloc] initWithWidth:10 height:20];
+ quad1.x = -10;
+ quad1.y = -15;
+
+ SPQuad *quad2 = [[SPQuad alloc] initWithWidth:15 height:25];
+ quad2.x = 30;
+ quad2.y = 25;
+
+ [sprite addChild:quad1];
+ [sprite addChild:quad2];
+
+ STAssertTrue(SP_IS_FLOAT_EQUAL(55.0f, sprite.width), @"wrong width: %f", sprite.width);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(65.0f, sprite.height), @"wrong height: %f", sprite.height);
+
+ quad1.rotation = PI_HALF;
+ STAssertTrue(SP_IS_FLOAT_EQUAL(75.0f, sprite.width), @"wrong width: %f", sprite.width);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(65.0f, sprite.height), @"wrong height: %f", sprite.height);
+
+ quad1.rotation = PI;
+ STAssertTrue(SP_IS_FLOAT_EQUAL(65.0f, sprite.width), @"wrong width: %f", sprite.width);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(85.0f, sprite.height), @"wrong height: %f", sprite.height);
+
+ [quad1 release];
+ [quad2 release];
+ [sprite release];
+}
+
+- (void)testBounds
+{
+ SPQuad *quad = [[SPQuad alloc] initWithWidth:10 height:20];
+ quad.x = -10;
+ quad.y = 10;
+ quad.rotation = PI_HALF;
+
+ SPSprite *sprite = [[SPSprite alloc] init];
+ [sprite addChild:quad];
+
+ SPRectangle *bounds = [sprite bounds];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(-30, bounds.x), @"wrong bounds.x: %f", bounds.x);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(10, bounds.y), @"wrong bounds.y: %f", bounds.y);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(20, bounds.width), @"wrong bounds.width: %f", bounds.width);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(10, bounds.height), @"wrong bounds.height: %f", bounds.height);
+
+ bounds = [sprite boundsInSpace:sprite];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(-30, bounds.x), @"wrong bounds.x: %f", bounds.x);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(10, bounds.y), @"wrong bounds.y: %f", bounds.y);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(20, bounds.width), @"wrong bounds.width: %f", bounds.width);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(10, bounds.height), @"wrong bounds.height: %f", bounds.height);
+
+ [sprite release];
+ [quad release];
+}
+
+- (void)testBoundsInSpace
+{
+ SPSprite *root = [[SPSprite alloc] init];
+
+ SPSprite *spriteA = [[SPSprite alloc] init];
+ spriteA.x = 50;
+ spriteA.y = 50;
+ [self addQuadToSprite:spriteA];
+ [root addChild:spriteA];
+ [spriteA release];
+
+ SPSprite *spriteA1 = [[SPSprite alloc] init];
+ spriteA1.x = 150;
+ spriteA1.y = 50;
+ spriteA1.scaleX = spriteA1.scaleY = 0.5;
+ [self addQuadToSprite:spriteA1];
+ [spriteA addChild:spriteA1];
+ [spriteA1 release];
+
+ SPSprite *spriteA11 = [[SPSprite alloc] init];
+ spriteA11.x = 25;
+ spriteA11.y = 50;
+ spriteA11.scaleX = spriteA11.scaleY = 0.5;
+ [self addQuadToSprite:spriteA11];
+ [spriteA1 addChild:spriteA11];
+ [spriteA11 release];
+
+ SPSprite *spriteA2 = [[SPSprite alloc] init];
+ spriteA2.x = 50;
+ spriteA2.y = 150;
+ spriteA2.scaleX = spriteA2.scaleY = 0.5;
+ [self addQuadToSprite:spriteA2];
+ [spriteA addChild:spriteA2];
+ [spriteA2 release];
+
+ SPSprite *spriteA21 = [[SPSprite alloc] init];
+ spriteA21.x = 50;
+ spriteA21.y = 25;
+ spriteA21.scaleX = spriteA21.scaleY = 0.5;
+ [self addQuadToSprite:spriteA21];
+ [spriteA2 addChild:spriteA21];
+ [spriteA21 release];
+
+ // ---
+
+ SPRectangle *bounds = [spriteA21 boundsInSpace:spriteA11];
+ SPRectangle *expectedBounds = [SPRectangle rectangleWithX:-350 y:350 width:100 height:100];
+ STAssertEqualObjects(expectedBounds, bounds, @"wrong bounds: %@", bounds);
+
+ // now rotate as well
+
+ spriteA11.rotation = PI/4.0f;
+ spriteA21.rotation = -PI/4.0f;
+
+ bounds = [spriteA21 boundsInSpace:spriteA11];
+ expectedBounds = [SPRectangle rectangleWithX:0 y:394.974762 width:100 height:100];
+ STAssertEqualObjects(expectedBounds, bounds, @"wrong bounds: %@", bounds);
+
+ [root release];
+}
+
+- (void)testSize
+{
+ SPQuad *quad1 = [SPQuad quadWithWidth:100 height:100];
+ SPQuad *quad2 = [SPQuad quadWithWidth:100 height:100];
+ quad2.x = quad2.y = 100;
+
+ SPSprite *sprite = [SPSprite sprite];
+ SPSprite *childSprite = [SPSprite sprite];
+
+ [sprite addChild:childSprite];
+ [childSprite addChild:quad1];
+ [childSprite addChild:quad2];
+
+
+ STAssertEqualsWithAccuracy(200.0f, sprite.width, E, @"wrong width: %f", sprite.width);
+ STAssertEqualsWithAccuracy(200.0f, sprite.height, E, @"wrong height: %f", sprite.height);
+
+ sprite.scaleX = 2;
+ sprite.scaleY = 2;
+ STAssertEqualsWithAccuracy(400.0f, sprite.width, E, @"wrong width: %f", sprite.width);
+ STAssertEqualsWithAccuracy(400.0f, sprite.height, E, @"wrong height: %f", sprite.height);
+}
+
+- (void)addQuadToSprite:(SPSprite*)sprite
+{
+ SPQuad *quad = [[SPQuad alloc] initWithWidth:100 height:100];
+ quad.alpha = 0.2f;
+ [sprite addChild:quad];
+ [quad release];
+ return;
+}
+
+- (void)testDisplayListEvents
+{
+ SPStage *stage = [[SPStage alloc] init];
+ SPSprite *sprite = [[SPSprite alloc] init];
+ SPQuad *quad = [[SPQuad alloc] initWithWidth:20 height:20];
+
+ [quad addEventListener:@selector(onAdded:) atObject:self forType:SP_EVENT_TYPE_ADDED];
+ [quad addEventListener:@selector(onAddedToStage:) atObject:self forType:SP_EVENT_TYPE_ADDED_TO_STAGE];
+ [quad addEventListener:@selector(onRemoved:) atObject:self forType:SP_EVENT_TYPE_REMOVED];
+ [quad addEventListener:@selector(onRemovedFromStage:) atObject:self forType:SP_EVENT_TYPE_REMOVED_FROM_STAGE];
+
+ [sprite addChild:quad];
+
+ STAssertEquals(1, mAdded, @"failure on event 'added'");
+ STAssertEquals(0, mRemoved, @"failure on event 'removed'");
+ STAssertEquals(0, mAddedToStage, @"failure on event 'addedToStage'");
+ STAssertEquals(0, mRemovedFromStage, @"failure on event 'removedFromStage'");
+
+ [stage addChild:sprite];
+
+ STAssertEquals(1, mAdded, @"failure on event 'added'");
+ STAssertEquals(0, mRemoved, @"failure on event 'removed'");
+ STAssertEquals(1, mAddedToStage, @"failure on event 'addedToStage'");
+ STAssertEquals(0, mRemovedFromStage, @"failure on event 'removedFromStage'");
+
+ [stage removeChild:sprite];
+
+ STAssertEquals(1, mAdded, @"failure on event 'added'");
+ STAssertEquals(0, mRemoved, @"failure on event 'removed'");
+ STAssertEquals(1, mAddedToStage, @"failure on event 'addedToStage'");
+ STAssertEquals(1, mRemovedFromStage, @"failure on event 'removedFromStage'");
+
+ [sprite removeChild:quad];
+
+ STAssertEquals(1, mAdded, @"failure on event 'added'");
+ STAssertEquals(1, mRemoved, @"failure on event 'removed'");
+ STAssertEquals(1, mAddedToStage, @"failure on event 'addedToStage'");
+ STAssertEquals(1, mRemovedFromStage, @"failure on event 'removedFromStage'");
+
+ [quad removeEventListenersAtObject:self forType:SP_EVENT_TYPE_ADDED];
+ [quad removeEventListenersAtObject:self forType:SP_EVENT_TYPE_ADDED_TO_STAGE];
+ [quad removeEventListenersAtObject:self forType:SP_EVENT_TYPE_REMOVED];
+ [quad removeEventListenersAtObject:self forType:SP_EVENT_TYPE_REMOVED_FROM_STAGE];
+
+ [quad release];
+ [sprite release];
+ [stage release];
+}
+
+- (void)onAdded:(SPEvent*)event { mAdded++; }
+- (void)onRemoved:(SPEvent*)event { mRemoved++; }
+- (void)onAddedToStage:(SPEvent*)event { mAddedToStage++; }
+- (void)onRemovedFromStage:(SPEvent*)event { mRemovedFromStage++; }
+
+- (void)testRemovedFromStage
+{
+ SPStage *stage = [[SPStage alloc] init];
+ [stage addChild:mTestSprite];
+ [mTestSprite addEventListener:@selector(onTestSpriteRemovedFromStage:) atObject:self
+ forType:SP_EVENT_TYPE_REMOVED_FROM_STAGE];
+ [mTestSprite removeFromParent];
+ [mTestSprite removeEventListenersAtObject:self forType:SP_EVENT_TYPE_REMOVED_FROM_STAGE];
+ [stage release];
+}
+
+- (void)onTestSpriteRemovedFromStage:(SPEvent *)event
+{
+ STAssertNotNil(mTestSprite.stage, @"stage not accessible in removed from stage event");
+}
+
+- (void)testAddExistingChild
+{
+ SPSprite *sprite = [SPSprite sprite];
+ SPQuad *quad = [SPQuad quadWithWidth:100 height:100];
+ [sprite addChild:quad];
+ STAssertNoThrow([sprite addChild:quad], @"Could not add child multiple times");
+}
+
+- (void)testRemoveAllChildren
+{
+ SPSprite *sprite = [SPSprite sprite];
+
+ STAssertEquals(0, sprite.numChildren, @"wrong number of children");
+ [sprite removeAllChildren];
+ STAssertEquals(0, sprite.numChildren, @"wrong number of children");
+
+ [sprite addChild:[SPQuad quadWithWidth:100 height:100]];
+ [sprite addChild:[SPQuad quadWithWidth:100 height:100]];
+
+ STAssertEquals(2, sprite.numChildren, @"wrong number of children");
+ [sprite removeAllChildren];
+ STAssertEquals(0, sprite.numChildren, @"remove all children did not work");
+}
+
+- (void)testChildByName
+{
+ SPSprite *parent = [SPSprite sprite];
+ SPSprite *child1 = [SPSprite sprite];
+ SPSprite *child2 = [SPSprite sprite];
+ SPSprite *child3 = [SPSprite sprite];
+
+ [parent addChild:child1];
+ [parent addChild:child2];
+ [parent addChild:child3];
+
+ child1.name = @"CHILD";
+ child3.name = @"child";
+
+ STAssertEqualObjects(child1, [parent childByName:@"CHILD"], @"wrong child returned");
+ STAssertEqualObjects(child3, [parent childByName:@"child"], @"wrong child returned");
+ STAssertNil([parent childByName:@"ChIlD"], @"return child on wrong name");
+}
+
+- (void)testDispatchEventOnChildren
+{
+ SPSprite *parent = [SPSprite sprite];
+
+ SPSprite *child1 = [[SPSprite alloc] init];
+ SPSprite *child2 = [[SPSprite alloc] init];
+ SPSprite *child3 = [[SPSprite alloc] init];
+
+ [parent addChild:child1];
+ [parent addChild:child2];
+ [parent addChild:child3];
+
+ child1.name = @"trigger";
+ [child1 addEventListener:@selector(onChildEvent:) atObject:self forType:@"dunno"];
+ [child2 addEventListener:@selector(onChildEvent:) atObject:self forType:@"dunno"];
+ [child3 addEventListener:@selector(onChildEvent:) atObject:self forType:@"dunno"];
+
+ [child1 release];
+ [child2 release];
+ [child3 release];
+
+ SPEvent *event = [SPEvent eventWithType:@"dunno"];
+ [parent dispatchEventOnChildren:event];
+
+ // event should have dispatched to all 3 children, even if the event listener
+ // removes the children from their parent when it reaches child1. Furthermore, it should
+ // not crash.
+
+ STAssertEquals(3, mEventCount, @"not all children received events!");
+}
+
+- (void)onChildEvent:(SPEvent *)event
+{
+ SPDisplayObject *target = (SPDisplayObject *)event.target;
+
+ if ([target.name isEqualToString:@"trigger"])
+ [target.parent removeAllChildren];
+
+ ++mEventCount;
+}
+
+// STAssertEquals(value, value, message, ...)
+// STAssertEqualObjects(object, object, message, ...)
+// STAssertNotNil(object, message, ...)
+// STAssertTrue(expression, message, ...)
+// STAssertFalse(expression, message, ...)
+// STAssertThrows(expression, message, ...)
+// STAssertThrowsSpecific(expression, exception, message, ...)
+// STAssertNoThrow(expression, message, ...)
+// STFail(message, ...)
+
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+//
+// SPDisplayObjectTest.h
+// Sparrow
+//
+// Created by Daniel Sperl on 13.04.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 <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPMatrix.h"
+#import "SPMacros.h"
+#import "SPPoint.h"
+#import "SPSprite.h"
+#import "SPQuad.h"
+
+#define E 0.0001f
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPDisplayObjectTest : SenTestCase
+{
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPDisplayObjectTest
+
+- (void)testRoot
+{
+ SPSprite *root = [[SPSprite alloc] init];
+ SPSprite *child = [[SPSprite alloc] init];
+ SPSprite *grandChild = [[SPSprite alloc] init];
+
+ [root addChild:child];
+ [child addChild:grandChild];
+
+ STAssertEqualObjects(root, grandChild.root, @"wrong root");
+
+ [grandChild release];
+ [child release];
+ [root release];
+}
+
+- (void)testTransformationMatrixToSpace
+{
+ SPSprite *sprite = [SPSprite sprite];
+ SPSprite *child = [SPSprite sprite];
+ child.x = 30;
+ child.y = 20;
+ child.scaleX = 1.2f;
+ child.scaleY = 1.5f;
+ child.rotation = PI/4.0f;
+ [sprite addChild:child];
+
+ SPMatrix *matrix = [sprite transformationMatrixToSpace:child];
+ SPMatrix *expectedMatrix = child.transformationMatrix;
+ [expectedMatrix invert];
+ STAssertEqualObjects(expectedMatrix, matrix, @"wrong matrix");
+
+ matrix = [child transformationMatrixToSpace:sprite];
+ STAssertEqualObjects(child.transformationMatrix, matrix, @"wrong matrix");
+
+ // more is tested indirectly via 'testBoundsInSpace' in DisplayObjectContainerTest
+}
+
+- (void)testTransformationMatrix
+{
+ SPSprite *sprite = [[SPSprite alloc] init];
+ sprite.x = 50;
+ sprite.y = 100;
+ sprite.rotation = PI / 4;
+ sprite.scaleX = 0.5;
+ sprite.scaleY = 1.5;
+
+ SPMatrix *matrix = [[SPMatrix alloc] init];
+ [matrix scaleXBy:sprite.scaleX yBy:sprite.scaleY];
+ [matrix rotateBy:sprite.rotation];
+ [matrix translateXBy:sprite.x yBy:sprite.y];
+
+ STAssertEqualObjects(sprite.transformationMatrix, matrix, @"wrong matrix");
+
+ [sprite release];
+ [matrix release];
+}
+
+- (void)testBounds
+{
+ SPQuad *quad = [[SPQuad alloc] initWithWidth:10 height:20];
+ quad.x = -10;
+ quad.y = 10;
+ quad.rotation = PI_HALF;
+ SPRectangle *bounds = quad.bounds;
+
+ STAssertTrue(SP_IS_FLOAT_EQUAL(-30, bounds.x), @"wrong bounds.x: %f", bounds.x);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(10, bounds.y), @"wrong bounds.y: %f", bounds.y);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(20, bounds.width), @"wrong bounds.width: %f", bounds.width);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(10, bounds.height), @"wrong bounds.height: %f", bounds.height);
+
+ bounds = [quad boundsInSpace:quad];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(0, bounds.x), @"wrong inner bounds.x: %f", bounds.x);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(0, bounds.y), @"wrong inner bounds.y: %f", bounds.y);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(10, bounds.width), @"wrong inner bounds.width: %f", bounds.width);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(20, bounds.height), @"wrong innter bounds.height: %f", bounds.height);
+
+ [quad release];
+}
+
+- (void)testZeroSize
+{
+ SPSprite *sprite = [SPSprite sprite];
+ STAssertEqualsWithAccuracy(1.0f, sprite.scaleX, E, @"wrong scaleX value");
+ STAssertEqualsWithAccuracy(1.0f, sprite.scaleY, E, @"wrong scaleY value");
+
+ // sprite is empty, scaling should thus have no effect!
+ sprite.width = 100;
+ sprite.height = 200;
+ STAssertEqualsWithAccuracy(1.0f, sprite.scaleX, E, @"wrong scaleX value");
+ STAssertEqualsWithAccuracy(1.0f, sprite.scaleY, E, @"wrong scaleY value");
+ STAssertEqualsWithAccuracy(0.0f, sprite.width, E, @"wrong width");
+ STAssertEqualsWithAccuracy(0.0f, sprite.height, E, @"wrong height");
+
+ // setting a value to zero should be no problem -- and the original size should be remembered.
+ SPQuad *quad = [SPQuad quadWithWidth:100 height:200];
+ quad.scaleX = 0.0f;
+ quad.scaleY = 0.0f;
+ STAssertEqualsWithAccuracy(0.0f, quad.width, E, @"wrong width");
+ STAssertEqualsWithAccuracy(0.0f, quad.height, E, @"wrong height");
+
+ quad.scaleX = 1.0f;
+ quad.scaleY = 1.0f;
+ STAssertEqualsWithAccuracy(100.0f, quad.width, E, @"wrong width");
+ STAssertEqualsWithAccuracy(200.0f, quad.height, E, @"wrong height");
+ STAssertEqualsWithAccuracy(1.0f, quad.scaleX, E, @"wrong scaleX value");
+ STAssertEqualsWithAccuracy(1.0f, quad.scaleY, E, @"wrong scaleY value");
+}
+
+- (void)testLocalToGlobal
+{
+ SPSprite *sprite = [[SPSprite alloc] init];
+ sprite.x = 10;
+ sprite.y = 20;
+ SPSprite *sprite2 = [[SPSprite alloc] init];
+ sprite2.x = 150;
+ sprite2.y = 200;
+ [sprite addChild:sprite2];
+
+ SPPoint *localPoint = [SPPoint pointWithX:0 y:0];
+ SPPoint *globalPoint = [sprite2 localToGlobal:localPoint];
+ SPPoint *expectedPoint = [SPPoint pointWithX:160 y:220];
+ STAssertEqualObjects(expectedPoint, globalPoint, @"wrong global point:");
+
+ [sprite release];
+ [sprite2 release];
+}
+
+- (void)testGlobalToLocal
+{
+ SPSprite *sprite = [[SPSprite alloc] init];
+ sprite.x = 10;
+ sprite.y = 20;
+ SPSprite *sprite2 = [[SPSprite alloc] init];
+ sprite2.x = 150;
+ sprite2.y = 200;
+ [sprite addChild:sprite2];
+
+ SPPoint *globalPoint = [SPPoint pointWithX:160 y:220];
+ SPPoint *localPoint = [sprite2 globalToLocal:globalPoint];
+ SPPoint *expectedPoint = [SPPoint pointWithX:0 y:0];
+ STAssertEqualObjects(expectedPoint, localPoint, @"wrong local point");
+
+ [sprite release];
+ [sprite2 release];
+}
+
+- (void)testHitTestPoint
+{
+ SPQuad *quad = [[SPQuad alloc] initWithWidth:25 height:10];
+
+ STAssertNotNil([quad hitTestPoint:[SPPoint pointWithX:15 y:5] forTouch:YES],
+ @"point should be inside");
+ STAssertNotNil([quad hitTestPoint:[SPPoint pointWithX:0 y:0] forTouch:YES],
+ @"point should be inside");
+ STAssertNotNil([quad hitTestPoint:[SPPoint pointWithX:25 y:0] forTouch:YES],
+ @"point should be inside");
+ STAssertNotNil([quad hitTestPoint:[SPPoint pointWithX:25 y:10] forTouch:YES],
+ @"point should be inside");
+ STAssertNotNil([quad hitTestPoint:[SPPoint pointWithX:0 y:10] forTouch:YES],
+ @"point should be inside");
+ STAssertNil([quad hitTestPoint:[SPPoint pointWithX:-1 y:-1] forTouch:YES],
+ @"point should be outside");
+ STAssertNil([quad hitTestPoint:[SPPoint pointWithX:26 y:11] forTouch:YES],
+ @"point should be outside");
+
+ quad.visible = NO;
+ STAssertNil([quad hitTestPoint:[SPPoint pointWithX:15 y:5] forTouch:YES],
+ @"hitTest should fail, object invisible");
+
+ quad.visible = YES;
+ quad.touchable = NO;
+ STAssertNil([quad hitTestPoint:[SPPoint pointWithX:15 y:5] forTouch:YES],
+ @"hitTest should fail, object untouchable");
+
+ quad.visible = NO;
+ quad.touchable = NO;
+ STAssertNotNil([quad hitTestPoint:[SPPoint pointWithX:15 y:5] forTouch:NO],
+ @"hitTest should succeed, this is no touch test");
+
+ [quad release];
+}
+
+- (void)testRotation
+{
+ SPQuad *quad = [SPQuad quadWithWidth:100 height:100];
+
+ quad.rotation = SP_D2R(400);
+ STAssertEqualsWithAccuracy(SP_D2R(40.0f), quad.rotation, E, @"wrong angle");
+ quad.rotation = SP_D2R(220);
+ STAssertEqualsWithAccuracy(SP_D2R(-140.0f), quad.rotation, E, @"wrong angle");
+ quad.rotation = SP_D2R(180);
+ STAssertEqualsWithAccuracy(SP_D2R(180.0f), quad.rotation, E, @"wrong angle");
+ quad.rotation = SP_D2R(-90);
+ STAssertEqualsWithAccuracy(SP_D2R(-90.0f), quad.rotation, E, @"wrong angle");
+ quad.rotation = SP_D2R(-179);
+ STAssertEqualsWithAccuracy(SP_D2R(-179.0f), quad.rotation, E, @"wrong angle");
+ quad.rotation = SP_D2R(-180);
+ STAssertEqualsWithAccuracy(SP_D2R(-180.0f), quad.rotation, E, @"wrong angle");
+ quad.rotation = SP_D2R(-181);
+ STAssertEqualsWithAccuracy(SP_D2R(179.0f), quad.rotation, E, @"wrong angle");
+ quad.rotation = SP_D2R(-300);
+ STAssertEqualsWithAccuracy(SP_D2R(60.0f), quad.rotation, E, @"wrong angle");
+ quad.rotation = SP_D2R(-370);
+ STAssertEqualsWithAccuracy(SP_D2R(-10.0f), quad.rotation, E, @"wrong angle");
+}
+
+- (void)testName
+{
+ SPSprite *sprite = [SPSprite sprite];
+ STAssertNil(sprite.name, @"name not nil after initialization");
+
+ sprite.name = @"hugo";
+ STAssertEqualObjects(@"hugo", sprite.name, @"wrong name");
+}
+
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+//
+// SPEventDispatcherTest.m
+// Sparrow
+//
+// Created by Daniel Sperl on 27.04.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 <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPEventDispatcher.h"
+#import "SPSprite.h"
+#import "SPEvent.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPEventDispatcherTest : SenTestCase
+{
+ int mTestCounter;
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPEventDispatcherTest
+
+#define EVENT_TYPE @"EVENT_TYPE"
+
+- (void)setUp
+{
+ mTestCounter = 0;
+}
+
+- (void)testAddAndRemoveEventListener
+{
+ SPEventDispatcher *dispatcher = [[SPEventDispatcher alloc] init];
+ [dispatcher addEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];
+ STAssertTrue([dispatcher hasEventListenerForType:EVENT_TYPE], @"missing event listener");
+
+ [dispatcher removeEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];
+ STAssertFalse([dispatcher hasEventListenerForType:EVENT_TYPE], @"remove failed");
+
+ [dispatcher release];
+}
+
+- (void)testRemoveAllEventListenersOfObject
+{
+ SPEventDispatcher *dispatcher = [[SPEventDispatcher alloc] init];
+ [dispatcher addEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];
+ STAssertTrue([dispatcher hasEventListenerForType:EVENT_TYPE], @"missing event listener");
+
+ [dispatcher removeEventListenersAtObject:self forType:EVENT_TYPE];
+ STAssertFalse([dispatcher hasEventListenerForType:EVENT_TYPE], @"remove failed");
+
+ [dispatcher release];
+}
+
+- (void)testSimpleEvent
+{
+ SPEventDispatcher *dispatcher = [[SPEventDispatcher alloc] init];
+ [dispatcher addEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];
+ [dispatcher dispatchEvent:[SPEvent eventWithType:EVENT_TYPE]];
+ STAssertEquals(1, mTestCounter, @"event listener not called");
+
+ [dispatcher removeEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];
+ [dispatcher release];
+}
+
+- (void)testBubblingEvent
+{
+ SPSprite *sprite1 = [[SPSprite alloc] init];
+ [sprite1 addEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];
+
+ SPSprite *sprite2 = [[SPSprite alloc] init];
+ [sprite2 addEventListener:@selector(stopEvent:) atObject:self forType:EVENT_TYPE];
+
+ SPSprite *sprite3 = [[SPSprite alloc] init];
+ [sprite3 addEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];
+
+ SPSprite *sprite4 = [[SPSprite alloc] init];
+ [sprite4 addEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];
+
+ [sprite1 addChild:sprite2];
+ [sprite2 addChild:sprite3];
+ [sprite3 addChild:sprite4];
+
+ [sprite4 dispatchEvent:[SPEvent eventWithType:EVENT_TYPE]];
+
+ STAssertEquals(1, mTestCounter, @"event bubbled, but should not have");
+ mTestCounter = 0;
+
+ [sprite4 dispatchEvent:[SPEvent eventWithType:EVENT_TYPE bubbles:YES]];
+ STAssertEquals(2, mTestCounter, @"event bubbling incorrect");
+
+ [sprite1 removeEventListenersAtObject:self forType:EVENT_TYPE];
+ [sprite2 removeEventListenersAtObject:self forType:EVENT_TYPE];
+ [sprite3 removeEventListenersAtObject:self forType:EVENT_TYPE];
+ [sprite4 removeEventListenersAtObject:self forType:EVENT_TYPE];
+ [sprite1 release];
+ [sprite2 release];
+ [sprite3 release];
+ [sprite4 release];
+}
+
+- (void)testStopImmediatePropagation
+{
+ SPSprite *sprite = [[SPSprite alloc] init];
+ [sprite addEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];
+ [sprite addEventListener:@selector(onEvent2:) atObject:self forType:EVENT_TYPE];
+ [sprite addEventListener:@selector(stopEventImmediately:) atObject:self forType:EVENT_TYPE];
+ [sprite addEventListener:@selector(onEvent3:) atObject:self forType:EVENT_TYPE];
+ [sprite dispatchEvent:[SPEvent eventWithType:EVENT_TYPE]];
+
+ STAssertEquals(2, mTestCounter, @"stopEventImmediately did not work correctly");
+
+ [sprite removeEventListenersAtObject:self forType:EVENT_TYPE];
+ [sprite release];
+}
+
+- (void)onEvent:(SPEvent*)event
+{
+ mTestCounter++;
+}
+
+- (void)onEvent2:(SPEvent*)event
+{
+ mTestCounter *= 2;
+}
+
+- (void)onEvent3:(SPEvent*)event
+{
+ mTestCounter += 3;
+}
+
+- (void)stopEvent:(SPEvent*)event
+{
+ [event stopPropagation];
+}
+
+- (void)stopEventImmediately:(SPEvent*)event
+{
+ [event stopImmediatePropagation];
+}
+
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+//
+// untitled.m
+// Sparrow
+//
+// Created by Daniel Sperl on 19.06.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 <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPImage.h"
+#import "SPPoint.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPImageTest : SenTestCase
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPImageTest
+
+- (void)testInit
+{
+ SPImage *image = [[SPImage alloc] init];
+ STAssertEqualObjects([SPPoint pointWithX:0 y:0], [image texCoordsOfVertex:0], @"wrong tex coords!");
+ STAssertEqualObjects([SPPoint pointWithX:1 y:0], [image texCoordsOfVertex:1], @"wrong tex coords!");
+ STAssertEqualObjects([SPPoint pointWithX:0 y:1], [image texCoordsOfVertex:2], @"wrong tex coords!");
+ STAssertEqualObjects([SPPoint pointWithX:1 y:1], [image texCoordsOfVertex:3], @"wrong tex coords!");
+ [image release];
+}
+
+- (void)testSetTexCoords
+{
+ SPImage *image = [[SPImage alloc] init];
+ [image setTexCoords:[SPPoint pointWithX:1 y:2] ofVertex:0];
+ [image setTexCoords:[SPPoint pointWithX:3 y:4] ofVertex:1];
+ [image setTexCoords:[SPPoint pointWithX:5 y:6] ofVertex:2];
+ [image setTexCoords:[SPPoint pointWithX:7 y:8] ofVertex:3];
+
+ STAssertEqualObjects([SPPoint pointWithX:1 y:2], [image texCoordsOfVertex:0], @"wrong tex coords!");
+ STAssertEqualObjects([SPPoint pointWithX:3 y:4], [image texCoordsOfVertex:1], @"wrong tex coords!");
+ STAssertEqualObjects([SPPoint pointWithX:5 y:6], [image texCoordsOfVertex:2], @"wrong tex coords!");
+ STAssertEqualObjects([SPPoint pointWithX:7 y:8], [image texCoordsOfVertex:3], @"wrong tex coords!");
+ [image release];
+}
+
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+//
+// SPJugglerTest.m
+// Sparrow
+//
+// Created by Daniel Sperl on 27.08.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 <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+
+#import "SPEventDispatcher.h"
+#import "SPEvent.h"
+#import "SPQuad.h"
+#import "SPTween.h"
+#import "SPJuggler.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPJugglerTest : SenTestCase
+{
+ SPJuggler *mJuggler;
+ SPQuad *mQuad;
+ BOOL mStartedReached;
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPJugglerTest
+
+- (void)setUp
+{
+ mStartedReached = NO;
+}
+
+- (void)testModificationWhileInEvent
+{
+ mJuggler = [[SPJuggler alloc] init];
+
+ SPQuad *quad = [[SPQuad alloc] initWithWidth:100 height:100];
+ SPTween *tween = [SPTween tweenWithTarget:quad time:1.0f];
+ [tween addEventListener:@selector(onTweenCompleted:) atObject:self
+ forType:SP_EVENT_TYPE_TWEEN_COMPLETED];
+ [mJuggler addObject:tween];
+
+ [mJuggler advanceTime:0.4]; // -> 0.4 (start)
+ [mJuggler advanceTime:0.4]; // -> 0.8 (update)
+
+ STAssertNoThrow([mJuggler advanceTime:0.4], // -> 1.2 (complete)
+ @"juggler could not cope with modification in tween callback");
+
+ [mJuggler advanceTime:0.4]; // 1.6 (start of new tween)
+ STAssertTrue(mStartedReached, @"juggler ignored modification made in callback");
+
+ [mJuggler release];
+ [mQuad release];
+
+ mJuggler = nil;
+ mQuad = nil;
+}
+
+- (void)testRemoveTweensWithTarget
+{
+ mJuggler = [SPJuggler juggler];
+
+ SPQuad *quad1 = [SPQuad quadWithWidth:100 height:100];
+ SPQuad *quad2 = [SPQuad quadWithWidth:200 height:200];
+
+ SPTween *tween1 = [SPTween tweenWithTarget:quad1 time:1.0];
+ SPTween *tween2 = [SPTween tweenWithTarget:quad2 time:1.0];
+
+ [tween1 animateProperty:@"rotation" targetValue:1.0f];
+ [tween2 animateProperty:@"rotation" targetValue:1.0f];
+
+ [mJuggler addObject:tween1];
+ [mJuggler addObject:tween2];
+
+ [mJuggler removeTweensWithTarget:quad1];
+ [mJuggler advanceTime:1.0];
+
+ STAssertEquals(0.0f, quad1.rotation, @"removed tween was advanced");
+ STAssertEquals(1.0f, quad2.rotation, @"wrong tween was removed");
+
+ mJuggler = nil;
+}
+
+- (void)onTweenCompleted:(SPEvent*)event
+{
+ SPTween *tween = [SPTween tweenWithTarget:mQuad time:1.0f];
+ [tween addEventListener:@selector(onTweenStarted:) atObject:self
+ forType:SP_EVENT_TYPE_TWEEN_STARTED];
+ [mJuggler addObject:tween];
+}
+
+- (void)onTweenStarted:(SPEvent*)event
+{
+ mStartedReached = YES;
+}
+
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+//
+// SPMatrixTest.m
+// Sparrow
+//
+// Created by Daniel Sperl on 26.03.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 <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPMatrix.h"
+#import "SPPoint.h"
+#import "SPMacros.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPMatrixTest : SenTestCase
+{
+ SPMatrix *countMatrix;
+ SPMatrix *identMatrix;
+}
+
+- (BOOL)checkMatrixValues:(SPMatrix*)matrix a:(float)a b:(float)b c:(float)c d:(float)d
+ tx:(float)tx ty:(float)ty;
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPMatrixTest
+
+- (void) setUp
+{
+ countMatrix = [[SPMatrix alloc] initWithA:1 b:2 c:3 d:4 tx:5 ty:6];
+ identMatrix = [[SPMatrix alloc] init];
+}
+
+- (void) tearDown
+{
+ [countMatrix release];
+ [identMatrix release];
+}
+
+- (void)testInit
+{
+ BOOL isEqual = [self checkMatrixValues:countMatrix a:1.0f b:2.0f c:3.0f d:4.0f tx:5.0f ty:6.0f];
+ STAssertTrue(isEqual, @"wrong matrix: %@", countMatrix);
+ isEqual = [self checkMatrixValues:identMatrix a:1.0f b:0.0f c:0.0f d:1.0f tx:0.0f ty:0.0f];
+ STAssertTrue(isEqual, @"wrong matrix: %@", identMatrix);
+}
+
+- (void)testCopy
+{
+ SPMatrix *copy = [countMatrix copy];
+ STAssertEqualObjects(countMatrix, copy, @"copy not equal: %@", copy);
+ STAssertFalse(countMatrix == copy, @"copy is identical");
+
+ [copy release];
+}
+
+- (void)testConcatMatrix
+{
+ SPMatrix *copy = [countMatrix copy];
+ [copy concatMatrix:identMatrix];
+ STAssertEqualObjects(countMatrix, copy, @"multiplication with identity modified matrix");
+ [copy release];
+ copy = [identMatrix copy];
+ [copy concatMatrix:countMatrix];
+ STAssertEqualObjects(countMatrix, copy, @"multiplication with identity modified matrix");
+
+ SPMatrix *countDownMatrix = [[SPMatrix alloc] initWithA:9 b:8 c:7 d:6 tx:5 ty:4];
+ [copy concatMatrix:countDownMatrix];
+ STAssertTrue([self checkMatrixValues:copy a:23 b:20 c:55 d:48 tx:92 ty:80],
+ @"wrong matrix: %@", copy);
+ [countDownMatrix concatMatrix:countMatrix];
+ STAssertTrue([self checkMatrixValues:countDownMatrix a:33 b:50 c:25 d:38 tx:22 ty:32],
+ @"wrong matrix: %@", copy);
+
+ [countDownMatrix release];
+ [copy release];
+}
+
+- (void)testInvert
+{
+ [countMatrix invert];
+ STAssertTrue([self checkMatrixValues:countMatrix a:-2 b:1 c:3.0f/2.0f d:-0.5f tx:1 ty:-2],
+ @"invert produced wrong result: %@", countMatrix);
+
+ SPMatrix *translateMatrix = [SPMatrix matrixWithIdentity];
+ [translateMatrix translateXBy:20 yBy:40];
+ [translateMatrix invert];
+
+ STAssertTrue([self checkMatrixValues:translateMatrix a:1 b:0 c:0 d:1 tx:-20 ty:-40],
+ @"invert produced wrong result: %@", translateMatrix);
+}
+
+- (void)testTranslate
+{
+ [identMatrix translateXBy:5 yBy:7];
+ SPPoint *point = [[SPPoint alloc] initWithX:10 y:20];
+ SPPoint *tPoint = [identMatrix transformPoint:point];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(15, tPoint.x), @"wrong x value: %f", tPoint.x);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(27, tPoint.y), @"wrong y value: %f", tPoint.y);
+ [point release];
+}
+
+- (void)testRotate
+{
+ [identMatrix rotateBy:PI/2.0f];
+ SPPoint *point = [[SPPoint alloc] initWithX:10 y:0];
+ SPPoint *rPoint = [identMatrix transformPoint:point];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(0, rPoint.x), @"wrong x value: %f", rPoint.x);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(10, rPoint.y), @"wrong y value: %f", rPoint.y);
+
+ [identMatrix identity];
+ [identMatrix rotateBy:PI];
+ point.y = 20;
+ rPoint = [identMatrix transformPoint:point];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(-10, rPoint.x), @"wrong x value: %f", rPoint.x);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(-20, rPoint.y), @"wrong y value: %f", rPoint.y);
+
+ [point release];
+}
+
+- (void)testScale
+{
+ [identMatrix scaleXBy:2.0 yBy:0.5];
+ SPPoint *point = [[SPPoint alloc] initWithX:10 y:20];
+ SPPoint *sPoint = [identMatrix transformPoint:point];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(20.0f, sPoint.x), @"wrong x value: %f", sPoint.x);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(10.0f, sPoint.y), @"wrong y value: %f", sPoint.y);
+ [point release];
+}
+
+- (void)testConcatenatedTransformations
+{
+ [identMatrix rotateBy:PI/2.0f];
+ [identMatrix scaleBy:0.5f];
+ [identMatrix translateXBy:0.0f yBy:5.0];
+ SPPoint *point = [[SPPoint alloc] initWithX:10 y:0];
+ SPPoint *ctPoint = [identMatrix transformPoint:point];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(0.0f, ctPoint.x), @"wrong x value: %f", ctPoint.x);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(10.0f, ctPoint.y), @"wrong y value: %f", ctPoint.y);
+ [point release];
+}
+
+- (BOOL)checkMatrixValues:(SPMatrix*)matrix a:(float)a b:(float)b c:(float)c d:(float)d
+ tx:(float)tx ty:(float)ty
+{
+ return SP_IS_FLOAT_EQUAL(a, matrix.a) && SP_IS_FLOAT_EQUAL(b, matrix.b) &&
+ SP_IS_FLOAT_EQUAL(b, matrix.b) && SP_IS_FLOAT_EQUAL(c, matrix.c) &&
+ SP_IS_FLOAT_EQUAL(tx, matrix.tx) && SP_IS_FLOAT_EQUAL(ty, matrix.ty);
+}
+
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+//
+// SPMovieClipTest.m
+// Sparrow
+//
+// Created by Daniel Sperl on 03.06.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 <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+
+#import "SPMovieClip.h"
+#import "SPTexture.h"
+
+#define E 0.0001f
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPMovieClipTest : SenTestCase
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPMovieClipTest
+
+- (void)testFrameManipulation
+{
+ float fps = 4.0;
+ double frameDuration = 1.0 / fps;
+
+ SPTexture *frame0 = [SPTexture emptyTexture];
+ SPTexture *frame1 = [SPTexture emptyTexture];
+ SPTexture *frame2 = [SPTexture emptyTexture];
+ SPTexture *frame3 = [SPTexture emptyTexture];
+
+ SPMovieClip *movie = [SPMovieClip movieWithFrame:frame0 fps:4.0f];
+
+ STAssertEqualsWithAccuracy(frame0.width, movie.width, E, @"wrong size");
+ STAssertEqualsWithAccuracy(frame0.height, movie.height, E, @"wrong size");
+
+ STAssertEquals(1, movie.numFrames, @"wrong number of frames");
+ STAssertEquals(0, movie.currentFrame, @"wrong start value");
+ STAssertEquals(frameDuration, movie.duration, @"wrong duration");
+ STAssertEquals(YES, movie.loop, @"wrong default value");
+ STAssertEquals(YES, movie.isPlaying, @"wrong default value");
+ STAssertEqualsWithAccuracy(frameDuration, movie.duration, E, @"wrong duration");
+
+ [movie pause];
+ STAssertFalse(movie.isPlaying, @"property returns wrong value");
+
+ [movie play];
+ STAssertTrue(movie.isPlaying, @"property returns wrong value");
+
+ movie.loop = NO;
+ STAssertFalse(movie.loop, @"property returns wrong value");
+
+ [movie addFrame:frame1];
+
+ STAssertEquals(2, movie.numFrames, @"wrong number of frames");
+ STAssertEqualsWithAccuracy(2 * frameDuration, movie.duration, E, @"wrong duration");
+
+ STAssertEqualObjects(frame0, [movie frameAtIndex:0], @"wrong frame");
+ STAssertEqualObjects(frame1, [movie frameAtIndex:1], @"wrong frame");
+
+ STAssertEqualsWithAccuracy(frameDuration, [movie durationAtIndex:0] , E, @"wrong frame duration");
+ STAssertEqualsWithAccuracy(frameDuration, [movie durationAtIndex:1] , E, @"wrong frame duration");
+
+ STAssertNil([movie soundAtIndex:0], @"sound not nil");
+ STAssertNil([movie soundAtIndex:1], @"sound not nil");
+
+ [movie addFrame:frame2 withDuration:0.5];
+ STAssertEqualsWithAccuracy(0.5, [movie durationAtIndex:2], E, @"wrong frame duration");
+ STAssertEqualsWithAccuracy(1.0, movie.duration, E, @"wrong duration");
+
+ [movie insertFrame:frame3 atIndex:2]; // -> 0, 1, 3, 2
+ STAssertEquals(4, movie.numFrames, @"wrong number of frames");
+ STAssertEqualsWithAccuracy(1.0 + frameDuration, movie.duration, E, @"wrong duration");
+ STAssertEqualObjects(frame1, [movie frameAtIndex:1], @"wrong frame");
+ STAssertEqualObjects(frame3, [movie frameAtIndex:2], @"wrong frame");
+ STAssertEqualObjects(frame2, [movie frameAtIndex:3], @"wrong frame");
+
+ [movie removeFrameAtIndex:0]; // -> 1, 3, 2
+ STAssertEquals(3, movie.numFrames, @"wrong number of frames");
+ STAssertEqualObjects(frame1, [movie frameAtIndex:0], @"wrong frame");
+ STAssertEqualsWithAccuracy(1.0, movie.duration, E, @"wrong duration");
+
+ [movie removeFrameAtIndex:1]; // -> 1, 2
+ STAssertEquals(2, movie.numFrames, @"wrong number of frames");
+ STAssertEqualObjects(frame1, [movie frameAtIndex:0], @"wrong frame");
+ STAssertEqualObjects(frame2, [movie frameAtIndex:1], @"wrong frame");
+ STAssertEqualsWithAccuracy(0.75, movie.duration, E, @"wrong duration");
+
+ [movie setFrame:frame3 atIndex:1];
+ STAssertEqualObjects(frame3, [movie frameAtIndex:1], @"wrong frame");
+
+ [movie setDuration:0.75 atIndex:1];
+ STAssertEqualsWithAccuracy(1.0, movie.duration, E, @"wrong duration");
+
+ [movie insertFrame:frame3 atIndex:2];
+ STAssertEquals(frame3, [movie frameAtIndex:2], @"wrong frame");
+}
+
+- (void)testAdvanceTime
+{
+ float fps = 4.0;
+ double frameDuration = 1.0 / fps;
+
+ SPTexture *frame0 = [SPTexture emptyTexture];
+ SPTexture *frame1 = [SPTexture emptyTexture];
+ SPTexture *frame2 = [SPTexture emptyTexture];
+ SPTexture *frame3 = [SPTexture emptyTexture];
+
+ SPMovieClip *movie = [SPMovieClip movieWithFrame:frame0 fps:fps];
+
+ [movie addFrame:frame1];
+ [movie addFrame:frame2 withDuration:0.5];
+ [movie addFrame:frame3];
+
+ STAssertEquals(0, movie.currentFrame, @"wrong current frame");
+ [movie advanceTime:frameDuration / 2.0];
+ STAssertEquals(0, movie.currentFrame, @"wrong current frame");
+ [movie advanceTime:frameDuration];
+ STAssertEquals(1, movie.currentFrame, @"wrong current frame");
+ [movie advanceTime:frameDuration];
+ STAssertEquals(2, movie.currentFrame, @"wrong current frame");
+ [movie advanceTime:frameDuration];
+ STAssertEquals(2, movie.currentFrame, @"wrong current frame");
+ [movie advanceTime:frameDuration];
+ STAssertEquals(3, movie.currentFrame, @"wrong current frame");
+ [movie advanceTime:frameDuration];
+ STAssertEquals(0, movie.currentFrame, @"movie did not loop");
+
+ movie.loop = NO;
+ [movie advanceTime:movie.duration + frameDuration];
+ STAssertEquals(3, movie.currentFrame, @"movie looped");
+
+ movie.currentFrame = 0;
+ STAssertEquals(0, movie.currentFrame, @"wrong current frame");
+ [movie advanceTime:frameDuration * 1.1];
+ STAssertEquals(1, movie.currentFrame, @"wrong current frame");
+}
+
+- (void)testChangeFps
+{
+ NSArray *frames = [NSArray arrayWithObjects:[SPTexture emptyTexture], [SPTexture emptyTexture],
+ [SPTexture emptyTexture], nil];
+
+ SPMovieClip *movie = [SPMovieClip movieWithFrames:frames fps:4.0f];
+ STAssertEquals(4.0f, movie.fps, @"wrong fps");
+
+ movie.fps = 3.0f;
+ STAssertEquals(3.0f, movie.fps, @"wrong fps");
+ STAssertEqualsWithAccuracy(1.0 / 3.0, [movie durationAtIndex:0], E, @"wrong frame duration");
+ STAssertEqualsWithAccuracy(1.0 / 3.0, [movie durationAtIndex:1], E, @"wrong frame duration");
+ STAssertEqualsWithAccuracy(1.0 / 3.0, [movie durationAtIndex:2], E, @"wrong frame duration");
+
+ [movie setDuration:1.0 atIndex:1];
+ STAssertEqualsWithAccuracy(1.0, [movie durationAtIndex:1], E, @"wrong frame duration");
+
+ movie.fps = 6.0f;
+ STAssertEqualsWithAccuracy(0.5, [movie durationAtIndex:1], E, @"wrong frame duration");
+ STAssertEqualsWithAccuracy(1.0 / 6.0, [movie durationAtIndex:0], E, @"wrong frame duration");
+
+ movie.fps = 0.0f;
+ STAssertEqualsWithAccuracy(0.0f, movie.fps, E, @"wrong fps");
+}
+
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+//
+// SPNSExtensionsTests.m
+// Sparrow
+//
+// Created by Daniel Sperl on 10.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 <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+
+#import "SPNSExtensions.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPNSExtensionsTest : SenTestCase
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPNSExtensionsTest
+
+- (void)testStringByAppendingSuffixToFilename
+{
+ NSString *filename = @"path/file.ext";
+ NSString *expandedFilename = [filename stringByAppendingSuffixToFilename:@"@2x"];
+ STAssertEqualObjects(@"path/file@2x.ext", expandedFilename, @"Appending suffix did not work!");
+}
+
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+//
+// SPPointTest.m
+// Sparrow
+//
+// Created by Daniel Sperl on 25.03.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 <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPPoint.h"
+#import "SPMacros.h"
+
+#define E 0.0001f
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPPointTest : SenTestCase
+{
+ SPPoint *mP1;
+ SPPoint *mP2;
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPPointTest
+
+- (void) setUp
+{
+ mP1 = [[SPPoint alloc] initWithX:2 y:3];
+ mP2 = [[SPPoint alloc] initWithX:4 y:1];
+}
+
+- (void) tearDown
+{
+ [mP1 release];
+ [mP2 release];
+}
+
+- (void)testInit
+{
+ SPPoint *point = [[SPPoint alloc] init];
+ STAssertEquals(0.0f, point.x, @"x is not zero");
+ STAssertEquals(0.0f, point.y, @"y is not zero");
+ [point release];
+}
+
+- (void)testInitWithXandY
+{
+ SPPoint *point = [[SPPoint alloc] initWithX:3 y:4];
+ STAssertEquals(3.0f, point.x, @"wrong x value");
+ STAssertEquals(4.0f, point.y, @"wrong y value");
+ [point release];
+}
+
+- (void)testLength
+{
+ SPPoint *point = [[SPPoint alloc] initWithX:-4 y:3];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(5.0f, point.length), @"wrong length");
+ point.x = 0;
+ point.y = 0;
+ STAssertEquals(0.0f, point.length, @"wrong length");
+ [point release];
+}
+
+- (void)testAngle
+{
+ SPPoint *point = [[SPPoint alloc] initWithX:10 y:0];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(0.0f, point.angle), @"wrong angle: %f", point.angle);
+ point.y = 10;
+ STAssertTrue(SP_IS_FLOAT_EQUAL(PI/4.0f, point.angle), @"wrong angle: %f", point.angle);
+ point.x = 0;
+ STAssertTrue(SP_IS_FLOAT_EQUAL(PI/2.0f, point.angle), @"wrong angle: %f", point.angle);
+ point.x = -10;
+ STAssertTrue(SP_IS_FLOAT_EQUAL(3*PI/4.0f, point.angle), @"wrong angle: %f", point.angle);
+ point.y = 0;
+ STAssertTrue(SP_IS_FLOAT_EQUAL(PI, point.angle), @"wrong angle: %f", point.angle);
+ point.y = -10;
+ STAssertTrue(SP_IS_FLOAT_EQUAL(-3*PI/4.0f, point.angle), @"wrong angle: %f", point.angle);
+ point.x = 0;
+ STAssertTrue(SP_IS_FLOAT_EQUAL(-PI/2.0f, point.angle), @"wrong angle: %f", point.angle);
+ point.x = 10;
+ STAssertTrue(SP_IS_FLOAT_EQUAL(-PI/4.0f, point.angle), @"wrong angle: %f", point.angle);
+ [point release];
+}
+
+- (void)testAddPoint
+{
+ SPPoint *result = [mP1 addPoint:mP2];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(6.0f, result.x), @"wrong x value");
+ STAssertTrue(SP_IS_FLOAT_EQUAL(4.0f, result.y), @"wrong y value");
+}
+
+- (void)testSubtractPoint
+{
+ SPPoint *result = [mP1 subtractPoint:mP2];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(-2.0f, result.x), @"wrong x value");
+ STAssertTrue(SP_IS_FLOAT_EQUAL(2.0f, result.y), @"wrong y value");
+}
+
+- (void)testScale
+{
+ SPPoint *point = [SPPoint pointWithX:0.0f y:0.0f];
+ point = [point scaleBy:100.0f];
+ STAssertEqualsWithAccuracy(point.x, 0.0f, E, @"wrong x value");
+ STAssertEqualsWithAccuracy(point.y, 0.0f, E, @"wrong y value");
+
+ point = [SPPoint pointWithX:1.0f y:2.0f];
+ float origLength = point.length;
+ point = [point scaleBy:2.0f];
+ float scaledLength = point.length;
+ STAssertEqualsWithAccuracy(point.x, 2.0f, E, @"wrong x value");
+ STAssertEqualsWithAccuracy(point.y, 4.0f, E, @"wrong y value");
+ STAssertEqualsWithAccuracy(origLength * 2.0f, scaledLength, E, @"wrong length");
+}
+
+- (void)testNormalize
+{
+ SPPoint *result = [mP1 normalize];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(1.0f, result.length), @"wrong length");
+ STAssertTrue(SP_IS_FLOAT_EQUAL(mP1.angle, result.angle), @"wrong angle");
+ SPPoint *origin = [[SPPoint alloc] init];
+ STAssertThrows([origin normalize], @"origin cannot be normalized!");
+ [origin release];
+}
+
+- (void)testClone
+{
+ SPPoint *result = [mP1 copy];
+ STAssertEquals(mP1.x, result.x, @"wrong x value");
+ STAssertEquals(mP1.y, result.y, @"wrong y value");
+ STAssertFalse(result == mP1, @"object should not be identical");
+ STAssertEqualObjects(mP1, result, @"objects should be equal");
+ [result release];
+}
+
+- (void)testIsEqual
+{
+ STAssertFalse([mP1 isEqual:mP2], @"should not be equal");
+ SPPoint *p3 = [[SPPoint alloc] initWithX:mP1.x y:mP1.y];
+ STAssertTrue([mP1 isEqual:p3], @"should be equal");
+ p3.x += 0.0000001;
+ p3.y -= 0.0000001;
+ STAssertTrue([mP1 isEqual:p3], @"should be equal, as difference is smaller than epsilon");
+ [p3 release];
+}
+
+- (void)testDistance
+{
+ SPPoint *p3 = [[SPPoint alloc] initWithX:5 y:0];
+ SPPoint *p4 = [[SPPoint alloc] initWithX:5 y:5];
+ float distance = [SPPoint distanceFromPoint:p3 toPoint:p4];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(5.0f, distance), @"wrong distance");
+ p3.y = -5;
+ distance = [SPPoint distanceFromPoint:p3 toPoint:p4];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(10.0f, distance), @"wrong distance");
+ [p3 release];
+ [p4 release];
+}
+
+- (void)testPolarPoint
+{
+ float angle = 5.0 * PI / 4.0;
+ float negAngle = -(2*PI - angle);
+ float length = 2.0f;
+ SPPoint *p3 = [SPPoint pointWithPolarLength:length angle:angle];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(length, p3.length), @"wrong length");
+ STAssertTrue(SP_IS_FLOAT_EQUAL(negAngle, p3.angle), @"wrong angle");
+ STAssertTrue(SP_IS_FLOAT_EQUAL(-cosf(angle-PI)*length, p3.x), @"wrong x");
+ STAssertTrue(SP_IS_FLOAT_EQUAL(-sinf(angle-PI)*length, p3.y), @"wrong y");
+}
+
+- (void)testInterpolate
+{
+ SPPoint *interpolation;
+
+ interpolation = [SPPoint interpolateFromPoint:mP1 toPoint:mP2 ratio:0.25f];
+ STAssertEqualsWithAccuracy(interpolation.x, 2.5f, E, @"wrong interpolated x");
+ STAssertEqualsWithAccuracy(interpolation.y, 2.5f, E, @"wrong interpolated y");
+
+ interpolation = [SPPoint interpolateFromPoint:mP1 toPoint:mP2 ratio:-0.25f];
+ STAssertEqualsWithAccuracy(interpolation.x, 1.5f, E, @"wrong interpolated x");
+ STAssertEqualsWithAccuracy(interpolation.y, 3.5f, E, @"wrong interpolated y");
+
+ interpolation = [SPPoint interpolateFromPoint:mP1 toPoint:mP2 ratio:1.25f];
+ STAssertEqualsWithAccuracy(interpolation.x, 4.5f, E, @"wrong interpolated x");
+ STAssertEqualsWithAccuracy(interpolation.y, 0.5f, E, @"wrong interpolated y");
+
+ SPPoint *p1 = [SPPoint pointWithX:2.0f y:1.0f];
+ SPPoint *p2 = [SPPoint pointWithX:-2.0f y:-1.0f];
+
+ interpolation = [SPPoint interpolateFromPoint:p1 toPoint:p2 ratio:0.5f];
+ STAssertEqualsWithAccuracy(interpolation.x, 0.0f, E, @"wrong interpolated x");
+ STAssertEqualsWithAccuracy(interpolation.y, 0.0f, E, @"wrong interpolated y");
+
+ interpolation = [SPPoint interpolateFromPoint:p1 toPoint:p2 ratio:0.0f];
+ STAssertEqualsWithAccuracy(interpolation.x, 2.0f, E, @"wrong interpolated x");
+ STAssertEqualsWithAccuracy(interpolation.y, 1.0f, E, @"wrong interpolated y");
+
+ interpolation = [SPPoint interpolateFromPoint:p1 toPoint:p2 ratio:1.0f];
+ STAssertEqualsWithAccuracy(interpolation.x, -2.0f, E, @"wrong interpolated x");
+ STAssertEqualsWithAccuracy(interpolation.y, -1.0f, E, @"wrong interpolated y");
+}
+
+// STAssertEquals(value, value, message, ...)
+// STAssertEqualObjects(object, object, message, ...)
+// STAssertNotNil(object, message, ...)
+// STAssertTrue(expression, message, ...)
+// STAssertFalse(expression, message, ...)
+// STAssertThrows(expression, message, ...)
+// STAssertThrowsSpecific(expression, exception, message, ...)
+// STAssertNoThrow(expression, message, ...)
+// STFail(message, ...)
+
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+//
+// SPQuadTest.m
+// Sparrow
+//
+// Created by Daniel Sperl on 23.04.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 <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+#import <math.h>
+
+#import "SPMatrix.h"
+#import "SPMacros.h"
+#import "SPPoint.h"
+#import "SPSprite.h"
+#import "SPQuad.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPQuadTest : SenTestCase
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPQuadTest
+
+- (void)testProperties
+{
+ float width = 30.0f;
+ float height = 20.0f;
+ float x = 3;
+ float y = 2;
+
+ SPQuad *quad = [[SPQuad alloc] initWithWidth:width height:height];
+ quad.x = x;
+ quad.y = y;
+
+ STAssertTrue(SP_IS_FLOAT_EQUAL(x, quad.x), @"wrong x");
+ STAssertTrue(SP_IS_FLOAT_EQUAL(y, quad.y), @"wrong y");
+ STAssertTrue(SP_IS_FLOAT_EQUAL(width, quad.width), @"wrong width");
+ STAssertTrue(SP_IS_FLOAT_EQUAL(height, quad.height), @"wrong height");
+
+ [quad release];
+}
+
+- (void)testWidth
+{
+ float width = 30;
+ float height = 40;
+ float angle = SP_D2R(45.0f);
+ SPQuad *quad = [[SPQuad alloc] initWithWidth:width height:height];
+ quad.rotation = angle;
+
+ float expectedWidth = cosf(angle) * (width + height);
+ STAssertTrue(SP_IS_FLOAT_EQUAL(expectedWidth, quad.width), @"wrong width: %f", quad.width);
+
+ [quad release];
+}
+
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+//
+// SPRectangleTest.m
+// Sparrow
+//
+// Created by Daniel Sperl on 25.04.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 <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPMacros.h"
+#import "SPPoint.h"
+#import "SPRectangle.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPRectangleTest : SenTestCase
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPRectangleTest
+
+- (void)testInit
+{
+ SPRectangle *rect = [[SPRectangle alloc] initWithX:10 y:20 width:30 height:40];
+ STAssertTrue(SP_IS_FLOAT_EQUAL(10, rect.x), @"wrong x");
+ STAssertTrue(SP_IS_FLOAT_EQUAL(20, rect.y), @"wrong y");
+ STAssertTrue(SP_IS_FLOAT_EQUAL(30, rect.width), @"wrong width");
+ STAssertTrue(SP_IS_FLOAT_EQUAL(40, rect.height), @"wrong height");
+ [rect release];
+}
+
+- (void)testContainsPoint
+{
+ SPRectangle *rect = [SPRectangle rectangleWithX:10 y:20 width:30 height:40];
+ STAssertFalse([rect containsPoint:[SPPoint pointWithX:0 y:0]], @"point inside");
+ STAssertTrue([rect containsPoint:[SPPoint pointWithX:15 y:25]], @"point not inside");
+ STAssertTrue([rect containsPoint:[SPPoint pointWithX:10 y:20]], @"point not inside");
+ STAssertTrue([rect containsPoint:[SPPoint pointWithX:40 y:60]], @"point not inside");
+}
+
+- (void)testContainsRect
+{
+ SPRectangle *rect = [SPRectangle rectangleWithX:-5 y:-10 width:10 height:20];
+
+ SPRectangle *overlapRect = [SPRectangle rectangleWithX:-10 y:-15 width:10 height:10];
+ SPRectangle *identRect = [SPRectangle rectangleWithX:-5 y:-10 width:10 height:20];
+ SPRectangle *outsideRect = [SPRectangle rectangleWithX:10 y:10 width:10 height:10];
+ SPRectangle *touchingRect = [SPRectangle rectangleWithX:5 y:0 width:10 height:10];
+ SPRectangle *insideRect = [SPRectangle rectangleWithX:0 y:0 width:1 height:2];
+
+ STAssertFalse([rect containsRectangle:overlapRect], @"overlapping, not inside");
+ STAssertTrue([rect containsRectangle:identRect], @"identical, should be inside");
+ STAssertFalse([rect containsRectangle:outsideRect], @"should be outside");
+ STAssertFalse([rect containsRectangle:touchingRect], @"touching, should be outside");
+ STAssertTrue([rect containsRectangle:insideRect], @"should be inside");
+}
+
+- (void)testIntersectionWithRectangle
+{
+ SPRectangle *rect = [SPRectangle rectangleWithX:-5 y:-10 width:10 height:20];
+
+ SPRectangle *overlapRect = [SPRectangle rectangleWithX:-10 y:-15 width:10 height:10];
+ SPRectangle *identRect = [SPRectangle rectangleWithX:-5 y:-10 width:10 height:20];
+ SPRectangle *outsideRect = [SPRectangle rectangleWithX:10 y:10 width:10 height:10];
+ SPRectangle *touchingRect = [SPRectangle rectangleWithX:5 y:0 width:10 height:10];
+ SPRectangle *insideRect = [SPRectangle rectangleWithX:0 y:0 width:1 height:2];
+
+ STAssertEqualObjects([SPRectangle rectangleWithX:-5 y:-10 width:5 height:5],
+ [rect intersectionWithRectangle:overlapRect], @"wrong intersection shape");
+ STAssertEqualObjects(rect, [rect intersectionWithRectangle:identRect], @"wrong intersection shape");
+ STAssertEqualObjects([SPRectangle rectangleWithX:0 y:0 width:0 height:0],
+ [rect intersectionWithRectangle:outsideRect], @"intersection should be empty");
+ STAssertEqualObjects([SPRectangle rectangleWithX:5 y:0 width:0 height:10],
+ [rect intersectionWithRectangle:touchingRect], @"wrong intersection shape");
+ STAssertEqualObjects(insideRect, [rect intersectionWithRectangle:insideRect],
+ @"wrong intersection shape");
+}
+
+- (void)testUniteWithRectangle
+{
+ SPRectangle *rect = [SPRectangle rectangleWithX:-5 y:-10 width:10 height:20];
+
+ SPRectangle *topLeftRect = [SPRectangle rectangleWithX:-15 y:-20 width:5 height:5];
+ SPRectangle *innerRect = [SPRectangle rectangleWithX:-5 y:-5 width:10 height:10];
+
+ STAssertEqualObjects([SPRectangle rectangleWithX:-15 y:-20 width:20 height:30],
+ [rect uniteWithRectangle:topLeftRect], @"wrong union");
+ STAssertEqualObjects(rect, [rect uniteWithRectangle:innerRect], @"wrong union");
+}
+
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+//
+// SPStageTest.m
+// Sparrow
+//
+// Created by Daniel Sperl on 25.04.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 <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPMatrix.h"
+#import "SPMacros.h"
+#import "SPPoint.h"
+#import "SPSprite.h"
+#import "SPStage.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPStageTest : SenTestCase
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPStageTest
+
+- (void)testForbiddenProperties
+{
+ SPStage *stage = [[SPStage alloc] init];
+ STAssertThrows([stage setX:10], @"allowed to set x coordinate of stage");
+ STAssertThrows([stage setY:10], @"allowed to set y coordinate of stage");
+ STAssertThrows([stage setScaleX:2.0], @"allowed to scale stage");
+ STAssertThrows([stage setScaleY:2.0], @"allowed to scale stage");
+ STAssertThrows([stage setRotation:PI], @"allowed to rotate stage");
+ [stage release];
+}
+
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+//
+// SPTweenerTest.m
+// Sparrow
+//
+// Created by Daniel Sperl on 15.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 <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPEventDispatcher.h"
+#import "SPEvent.h"
+#import "SPQuad.h"
+#import "SPTween.h"
+#import "SPMacros.h"
+
+#define E 0.0001f
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPTweenTest : SenTestCase
+{
+ @private
+ int mStartedCount;
+ int mUpdatedCount;
+ int mCompletedCount;
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPTweenTest
+
+- (void) setUp
+{
+ mStartedCount = mUpdatedCount = mCompletedCount = 0;
+}
+
+- (void)testBasicTween
+{
+ float startX = 10.0f;
+ float startY = 20.0f;
+ float endX = 100.0f;
+ float endY = 200.0f;
+ float startAlpha = 1.0f;
+ float endAlpha = 0.0f;
+ float totalTime = 2.0f;
+
+ SPQuad *quad = [SPQuad quadWithWidth:100 height:100];
+ quad.x = startX;
+ quad.y = startY;
+ quad.alpha = startAlpha;
+
+ SPTween *tween = [SPTween tweenWithTarget:quad time:totalTime transition:SP_TRANSITION_LINEAR];
+ [tween animateProperty:@"x" targetValue:endX];
+ [tween animateProperty:@"y" targetValue:endY];
+ [tween animateProperty:@"alpha" targetValue:endAlpha];
+ [tween addEventListener:@selector(onTweenStarted:) atObject:self forType:SP_EVENT_TYPE_TWEEN_STARTED];
+ [tween addEventListener:@selector(onTweenUpdated:) atObject:self forType:SP_EVENT_TYPE_TWEEN_UPDATED];
+ [tween addEventListener:@selector(onTweenCompleted:) atObject:self forType:SP_EVENT_TYPE_TWEEN_COMPLETED];
+
+ STAssertEqualsWithAccuracy(startX, quad.x, E, @"wrong x");
+ STAssertEqualsWithAccuracy(startY, quad.y, E, @"wrong y");
+ STAssertEqualsWithAccuracy(startAlpha, quad.alpha, E, @"wrong alpha");
+ STAssertEquals(0, mStartedCount, @"start event dispatched too soon");
+
+ [tween advanceTime: totalTime/3.0];
+ STAssertEqualsWithAccuracy(startX + (endX-startX)/3.0f, quad.x, E, @"wrong x: %f", quad.x);
+ STAssertEqualsWithAccuracy(startY + (endY-startY)/3.0f, quad.y, E, @"wrong y");
+ STAssertEqualsWithAccuracy(startAlpha + (endAlpha-startAlpha)/3.0f, quad.alpha, E, @"wrong alpha");
+ STAssertEquals(1, mStartedCount, @"missing start event");
+ STAssertEquals(1, mUpdatedCount, @"missing update event");
+ STAssertEquals(0, mCompletedCount, @"completed event dispatched too soon");
+
+ [tween advanceTime: totalTime/3.0];
+ STAssertEqualsWithAccuracy(startX + 2.0f*(endX-startX)/3.0f, quad.x, E, @"wrong x: %f", quad.x);
+ STAssertEqualsWithAccuracy(startY + 2.0f*(endY-startY)/3.0f, quad.y, E, @"wrong y");
+ STAssertEqualsWithAccuracy(startAlpha + 2.0f*(endAlpha-startAlpha)/3.0f, quad.alpha, E, @"wrong alpha");
+ STAssertEquals(1, mStartedCount, @"too many start events dipatched");
+ STAssertEquals(2, mUpdatedCount, @"missing update event");
+ STAssertEquals(0, mCompletedCount, @"completed event dispatched too soon");
+
+ [tween advanceTime: totalTime/3.0];
+ STAssertEqualsWithAccuracy(endX, quad.x, E, @"wrong x: %f", quad.x);
+ STAssertEqualsWithAccuracy(endY, quad.y, E, @"wrong y");
+ STAssertEqualsWithAccuracy(endAlpha, quad.alpha, E, @"wrong alpha");
+ STAssertEquals(1, mStartedCount, @"too many start events dispatched");
+ STAssertEquals(3, mUpdatedCount, @"missing update event");
+ STAssertEquals(1, mCompletedCount, @"missing completed event");
+
+ [tween removeEventListenersAtObject:self forType:SP_EVENT_TYPE_TWEEN_STARTED];
+ [tween removeEventListenersAtObject:self forType:SP_EVENT_TYPE_TWEEN_UPDATED];
+ [tween removeEventListenersAtObject:self forType:SP_EVENT_TYPE_TWEEN_COMPLETED];
+}
+
+- (void)onTweenStarted:(SPEvent*)event
+{
+ mStartedCount++;
+}
+
+- (void)onTweenUpdated:(SPEvent*)event
+{
+ mUpdatedCount++;
+}
+
+- (void)onTweenCompleted:(SPEvent*)event
+{
+ mCompletedCount++;
+}
+
+- (void)testSequentialTweens
+{
+ float startPos = 0.0f;
+ float targetPos = 50.0f;
+ SPQuad *quad = [SPQuad quadWithWidth:100 height:100];
+
+ // 2 tweens should move object up, then down
+ SPTween *tween1 = [SPTween tweenWithTarget:quad time:1];
+ [tween1 animateProperty:@"y" targetValue:targetPos];
+
+ SPTween *tween2 = [SPTween tweenWithTarget:quad time:1];
+ [tween2 animateProperty:@"y" targetValue:startPos];
+ tween2.delay = 1;
+
+ [tween1 advanceTime:1];
+ STAssertEquals(targetPos, quad.y, @"wrong y value");
+
+ [tween2 advanceTime:1];
+ STAssertEquals(targetPos, quad.y, @"second tween changed y value on start");
+
+ [tween2 advanceTime:0.5];
+ STAssertEqualsWithAccuracy((targetPos - startPos)/2.0f, quad.y, E,
+ @"second tween moves object the wrong way");
+
+ [tween2 advanceTime:0.5];
+ STAssertEquals(startPos, quad.y, @"second tween moved to wrong y position");
+}
+
+- (void)testTweenFromZero
+{
+ SPQuad *quad = [SPQuad quadWithWidth:100 height:100];
+ quad.scaleX = 0.0f;
+ SPTween *tween = [SPTween tweenWithTarget:quad time:1.0f];
+ [tween animateProperty:@"scaleX" targetValue:1.0f];
+
+ [tween advanceTime:0.0f];
+ STAssertEqualsWithAccuracy(0.0f, quad.width, E, @"wrong x value");
+
+ [tween advanceTime:0.5f];
+ STAssertEqualsWithAccuracy(50.0f, quad.width, E, @"wrong x value");
+
+ [tween advanceTime:0.5f];
+ STAssertEqualsWithAccuracy(100.0f, quad.width, E, @"wrong x value");
+}
+
+- (void)testTweenWithRepeatingLoop
+{
+ float startX = 100.0f;
+ float deltaX = 50.0f;
+ float totalTime = 2.0f;
+
+ SPQuad *quad = [SPQuad quadWithWidth:100 height:100];
+ quad.x = startX;
+
+ SPTween *tween = [SPTween tweenWithTarget:quad time:totalTime transition:SP_TRANSITION_LINEAR];
+ [tween animateProperty:@"x" targetValue:startX + deltaX];
+ tween.loop = SPLoopTypeRepeat;
+
+ [tween advanceTime:0.0];
+ STAssertEqualsWithAccuracy(startX, quad.x, E, @"wrong x value");
+
+ [tween advanceTime:totalTime / 2.0];
+ STAssertEqualsWithAccuracy(startX + 0.5f * deltaX, quad.x, E, @"wrong x value");
+
+ [tween advanceTime:totalTime / 2.0];
+ STAssertEqualsWithAccuracy(startX, quad.x, E, @"wrong x value");
+
+ [tween advanceTime:totalTime / 2.0];
+ STAssertEqualsWithAccuracy(startX + 0.5f * deltaX, quad.x, E, @"wrong x value");
+
+ [tween advanceTime:totalTime / 2.0];
+ STAssertEqualsWithAccuracy(startX, quad.x, E, @"wrong x value");
+}
+
+- (void)testTweenWithReversingLoop
+{
+ float startX = 100.0f;
+ float deltaX = 50.0f;
+ float totalTime = 2.0f;
+
+ SPQuad *quad = [SPQuad quadWithWidth:100 height:100];
+ quad.x = startX;
+
+ SPTween *tween = [SPTween tweenWithTarget:quad time:totalTime transition:SP_TRANSITION_LINEAR];
+ [tween animateProperty:@"x" targetValue:startX + deltaX];
+ tween.loop = SPLoopTypeReverse;
+
+ [tween advanceTime:0.0];
+ STAssertEqualsWithAccuracy(startX, quad.x, E, @"wrong x value");
+
+ [tween advanceTime:totalTime / 2.0];
+ STAssertEqualsWithAccuracy(startX + 0.5f * deltaX, quad.x, E, @"wrong x value");
+
+ [tween advanceTime:totalTime / 2.0];
+ STAssertEqualsWithAccuracy(startX + deltaX, quad.x, E, @"wrong x value");
+
+ [tween advanceTime:totalTime / 2.0];
+ STAssertEqualsWithAccuracy(startX + 0.5f * deltaX, quad.x, E, @"wrong x value");
+
+ [tween advanceTime:totalTime / 2.0];
+ STAssertEqualsWithAccuracy(startX, quad.x, E, @"wrong x value");
+}
+
+- (void)makeTweenWithTime:(double)time andAdvanceBy:(double)advanceTime
+{
+ SPQuad *quad = [SPQuad quadWithWidth:100 height:100];
+ SPTween *tween = [SPTween tweenWithTarget:quad time:time];
+ [tween animateProperty:@"x" targetValue:100.0f];
+ [tween addEventListener:@selector(onTweenStarted:) atObject:self forType:SP_EVENT_TYPE_TWEEN_STARTED];
+ [tween addEventListener:@selector(onTweenUpdated:) atObject:self forType:SP_EVENT_TYPE_TWEEN_UPDATED];
+ [tween addEventListener:@selector(onTweenCompleted:) atObject:self forType:SP_EVENT_TYPE_TWEEN_COMPLETED];
+
+ [tween advanceTime:advanceTime];
+
+ STAssertEquals(1, mUpdatedCount, @"short tween did not call onUpdate");
+ STAssertEquals(1, mStartedCount, @"short tween did not call onStarted");
+ STAssertEquals(1, mCompletedCount, @"short tween did not call onCompleted");
+
+ [tween removeEventListenersAtObject:self forType:SP_EVENT_TYPE_TWEEN_STARTED];
+ [tween removeEventListenersAtObject:self forType:SP_EVENT_TYPE_TWEEN_UPDATED];
+ [tween removeEventListenersAtObject:self forType:SP_EVENT_TYPE_TWEEN_COMPLETED];
+}
+
+- (void)testShortTween
+{
+ [self makeTweenWithTime:0.1f andAdvanceBy:0.1f];
+}
+
+- (void)testZeroTween
+{
+ [self makeTweenWithTime:0.0f andAdvanceBy:0.1f];
+}
+
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+//
+// SPUtilsTest.m
+// Sparrow
+//
+// Created by Daniel Sperl on 04.01.11.
+// Copyright 2011 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 <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+
+#import "SPUtils.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPUtilsTest : SenTestCase
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPUtilsTest
+
+- (void)testGetNextPowerOfTwo
+{
+ STAssertEquals(1, [SPUtils nextPowerOfTwo:0], @"wrong power of two");
+ STAssertEquals(1, [SPUtils nextPowerOfTwo:1], @"wrong power of two");
+ STAssertEquals(2, [SPUtils nextPowerOfTwo:2], @"wrong power of two");
+ STAssertEquals(4, [SPUtils nextPowerOfTwo:3], @"wrong power of two");
+ STAssertEquals(4, [SPUtils nextPowerOfTwo:4], @"wrong power of two");
+ STAssertEquals(8, [SPUtils nextPowerOfTwo:5], @"wrong power of two");
+ STAssertEquals(8, [SPUtils nextPowerOfTwo:6], @"wrong power of two");
+ STAssertEquals(256, [SPUtils nextPowerOfTwo:129], @"wrong power of two");
+ STAssertEquals(256, [SPUtils nextPowerOfTwo:255], @"wrong power of two");
+ STAssertEquals(256, [SPUtils nextPowerOfTwo:256], @"wrong power of two");
+}
+
+- (void)testGetRandomFloat
+{
+ for (int i=0; i<20; ++i)
+ {
+ float rnd = [SPUtils randomFloat];
+ STAssertTrue(rnd >= 0.0f, @"random number too small");
+ STAssertTrue(rnd < 1.0f, @"random number too big");
+ }
+}
+
+- (void)testGetRandomInt
+{
+ for (int i=0; i<20; ++i)
+ {
+ int rnd = [SPUtils randomIntBetween:5 and:10];
+ STAssertTrue(rnd >= 5, @"random number too small");
+ STAssertTrue(rnd < 10, @"random number too big");
+ }
+}
+
+@end
+
+#endif
\ No newline at end of file
--- /dev/null
+----------------------------------------------------------------------------------------------------
+The Sparrow Atlas Generator
+----------------------------------------------------------------------------------------------------
+
+The atlas generator is a Ruby script that arranges several images in one big atlas-texture.
+Using a textures atlas is important for the performance of your game, and it's very easy
+to use it in Sparrow.
+
+----------------------------------------------------------------------------------------------------
+Installation
+----------------------------------------------------------------------------------------------------
+
+1) Requirement: "ImageMagick"
+
+First, you need the command line tool "ImageMagick" (http://www.imagemagick.org).
+The recommended way to install it is via the packaging system "MacPorts" (http://www.macports.org/)
+
+After installing MacPorts, open a terminal and type:
+
+ sudo port install ImageMagick
+
+Now, go for a coffee, this will take a while ;-)
+
+2) Requirement: "QuickMagick"
+
+Now, we need a ruby gem that is used to work with ImageMagick. It is called "QuickMagick", and
+is installed the following way:
+
+ sudo gem install quick_magick
+
+3) Make the script executable:
+
+To allow the script to execute, we need to tell OS X that it can be executed:
+
+ chmod u+x generate_atlas.rb
+
+4) That's it!
+
+----------------------------------------------------------------------------------------------------
+Usage
+----------------------------------------------------------------------------------------------------
+
+To find out how the script works, have a look at the help screen.
+
+./generate_atlas.rb --help
+
+Here is simple usage example:
+
+./generate_atlas.rb input/*.png output/atlas.xml
+
+Some options:
+
+./generate_atlas.rb --scale 0.5 --padding 2 --sharpen --maxsize 2048x2048 *.png atlas.xml
+
--- /dev/null
+#!/usr/bin/env ruby
+
+#
+# generate_atlas.rb
+# Sparrow
+#
+# Created by Daniel Sperl on 14.06.2010
+# 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.
+#
+
+require 'rubygems'
+require 'ftools'
+require 'quick_magick'
+require 'optparse'
+require 'ostruct'
+require 'rexml/document'
+
+include REXML
+include QuickMagick
+
+# --------------------------------------------------------------------------------------------------
+# --- worker classes -------------------------------------------------------------------------------
+# --------------------------------------------------------------------------------------------------
+
+class TextureNode
+ attr_accessor :image
+ attr_reader :rect
+
+ def initialize(x, y, width, height)
+ @rect = Rectangle.new(x, y, width, height)
+ end
+
+ def insert_image(image, scale, padding)
+ if @image.nil?
+ img_width = (image.width * scale.to_f + padding).to_i
+ img_height = (image.height * scale.to_f + padding).to_i
+ if (img_width <= @rect.width and img_height <= @rect.height)
+ @image = image
+ @children = []
+ rest_width = @rect.width - img_width
+ rest_height = @rect.height - img_height
+ if (rest_width > rest_height)
+ @children << TextureNode.new(@rect.x, @rect.y + img_height,
+ img_width, @rect.height - img_height)
+ @children << TextureNode.new(@rect.x + img_width, @rect.y,
+ @rect.width - img_width, @rect.height)
+ else
+ @children << TextureNode.new(@rect.x + img_width, @rect.y,
+ @rect.width - img_width, img_height)
+ @children << TextureNode.new(@rect.x, @rect.y + img_height,
+ @rect.width, @rect.height - img_height)
+ end
+ return self
+ else
+ return nil
+ end
+ else
+ new_node = @children[0].insert_image(image, scale, padding)
+ unless (new_node.nil?)
+ return new_node
+ else
+ return @children[1].insert_image(image, scale, padding)
+ end
+ end
+ end
+
+ def image_name
+ image.nil? ? nil : File.basename(image.image_filename, File.extname(image.image_filename))
+ end
+
+end
+
+class Rectangle
+ attr_reader :x, :y, :width, :height
+ def initialize(x, y, width, height)
+ @x = x
+ @y = y
+ @width = width
+ @height = height
+ end
+end
+
+# --------------------------------------------------------------------------------------------------
+# --- option parsing -------------------------------------------------------------------------------
+# --------------------------------------------------------------------------------------------------
+
+options = OpenStruct.new
+options.scale = 1
+options.padding = 1
+options.sharpen = false
+options.maxsize = [1024, 1024]
+options.verbose = true
+
+option_parser = OptionParser.new do |opts|
+
+ opts.banner = "Usage: #{File.basename(__FILE__)} [options] source_images target_file"
+
+ opts.separator ""
+ opts.separator "Options:"
+
+ opts.on('-s', '--scale FACTOR', Float, 'Scale textures') do |factor|
+ options.scale = factor
+ end
+
+ opts.on('-p', '--padding DISTANCE', Integer, 'Padding of subtextures (default: 1)') do |distance|
+ options.padding = distance
+ end
+
+ opts.on('-r', '--sharpen', 'Sharpen images') do
+ options.sharpen = true
+ end
+
+ opts.on('-m', '--maxsize WIDTHxHEIGHT', 'Maximum atlas size (default: 1024x1024)') do |size|
+ options.maxsize = size.split('x').collect { |v| v.to_i }
+ end
+
+ opts.on("-v", "--[no-]verbose", "Run verbosely (default: verbose)") do |verbose|
+ options.verbose = verbose
+ end
+
+ opts.on_tail('-h', '--help', 'Show this message') do
+ puts opts
+ exit
+ end
+
+end
+
+if (ARGV.length < 2)
+ puts option_parser
+ exit
+else
+ option_parser.parse!
+end
+
+@verbose = options.verbose
+def vputs(text)
+ puts text if @verbose
+end
+
+# --------------------------------------------------------------------------------------------------
+# --- image processing -----------------------------------------------------------------------------
+# --------------------------------------------------------------------------------------------------
+
+# get commandline-arguments
+source_files = ARGV.clone
+target_file = source_files.pop
+
+if source_files.count == 0
+ puts "No images found!"
+ exit
+end
+
+images = source_files.collect { |file| Image.read(file).first }
+images.sort! { |i, j| (j.width * j.height) <=> (i.width * i.height) }
+
+vputs "Found #{images.count} images."
+
+# Start with a small atlas and make it bigger until all textures fit.
+
+image_nodes = []
+current_width, current_height = 32, 32
+loop_count = 0
+textures_fit = false
+
+until textures_fit do
+ textures_fit = true
+ root_node = TextureNode.new(0, 0, current_width + options.padding, current_height + options.padding)
+ image_nodes = []
+
+ images.each do |image|
+ new_node = root_node.insert_image(image, options.scale, options.padding)
+ if new_node.nil? then
+ textures_fit = false
+ break
+ else
+ image_nodes << new_node
+ end
+ end
+
+ unless textures_fit
+ loop_count += 1
+ if loop_count % 3 < 2
+ current_width *= 2
+ else
+ current_width /= 2
+ current_height *= 2
+ end
+ end
+end
+
+if current_width > options.maxsize[0] or current_height > options.maxsize[1]
+ puts "Error: Textures did not fit into a #{options.maxsize[0]}x#{options.maxsize[1]} image"
+ exit
+else
+ vputs "Arranged images in #{current_width}x#{current_height} atlas."
+end
+
+vputs "Drawing atlas ..."
+
+atlas_image = Image::solid(current_width, current_height, :transparent)
+
+# Use box filter for downscaling by whole number (creates sharper output)
+atlas_image.append_to_operators 'filter', 'Box' if (options.scale == 0.5 || options.scale == 0.25)
+
+# Draw all images into atlas
+image_nodes.each do |node|
+ atlas_image.draw_image :over, node.rect.x, node.rect.y,
+ (node.image.width * options.scale).to_i,
+ (node.image.height * options.scale).to_i,
+ "{" + node.image.image_filename + "}"
+end
+
+# Apply sharpening filter if requested
+atlas_image.append_to_operators 'sharpen', 1 if (options.sharpen)
+
+# Save output to correct path
+target_path = File.dirname(target_file)
+File.makedirs target_path
+atlas_name = File.basename(target_file, File.extname(target_file))
+atlas_image_path = File.join(target_path, atlas_name) + ".png"
+atlas_image.save(atlas_image_path)
+
+puts "Saved #{atlas_image_path}"
+
+# --------------------------------------------------------------------------------------------------
+# --- XML creation ---------------------------------------------------------------------------------
+# --------------------------------------------------------------------------------------------------
+
+atlas_xml_path = File.join(target_path, atlas_name) + ".xml"
+
+xml_doc = Document.new
+xml_doc << XMLDecl.default
+root = Element.new "TextureAtlas"
+root.attributes["imagePath"] = atlas_name + ".png"
+
+image_nodes.each do |node|
+ subnode = Element.new "SubTexture"
+ subnode.attributes["name"] = File.basename(node.image_name)
+ subnode.attributes["x"] = node.rect.x
+ subnode.attributes["y"] = node.rect.y
+ subnode.attributes["width"] = node.image.width * options.scale.to_f
+ subnode.attributes["height"] = node.image.height * options.scale.to_f
+ root << subnode
+end
+
+xml_doc << root
+
+File.open(atlas_xml_path, "w") do |file|
+ xml_doc.write file, 2
+end
+
+puts "Saved #{atlas_xml_path}"
\ No newline at end of file
--- /dev/null
+--- hiero2sparrow.rb ---
+
+This Ruby script converts Bitmap Font files created with the "Hiero"-Tool:
+-> http://slick.cokeandcode.com/
+to an XML format. That's the format Sparrow expects for its Bitmap Fonts.
+
+As an alternative, you can use the Bitmap Font Generator from AngelCode:
+-> http://www.angelcode.com/products/bmfont/
+It can export this xml format directly. However, this tool works only in MS Windows.
+
+Usage: hiero2sparrow.rb input.fnt [output.fnt]
+
+- The output parameter is optional. If omitted, the xml data overwrites the input file.
+
+A tip when you use Hiero:
+Add at least 1 pixel padding to the "up" and "left" directions!
\ No newline at end of file
--- /dev/null
+#!/usr/bin/env ruby
+
+#
+# hiero2sparrow.rb
+# Sparrow
+#
+# Created by Daniel Sperl on 11.02.2010
+# 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.
+#
+
+# This script converts Bitmap Font files created with the "Hiero"-Tool to the
+# format that is expected by Sparrow. See README for more information.
+
+require "rexml/document"
+
+SPACE_MARKER = '#SPACE#'
+
+if $*.count == 0
+ puts "Usage: hiero2sparrow.rb input.fnt [output.fnt]"
+ exit
+end
+
+# get commandline-arguments
+input_file_path = $*[0]
+output_file_path = $*.count >= 2 ? $*[1] : input_file_path
+
+if !File.exist?(input_file_path)
+ puts "File #{input_file_path} not found!"
+ exit
+end
+
+xml_doc = REXML::Document.new
+xml_doc << REXML::XMLDecl.new
+
+root_element = xml_doc.add_element "font"
+current_parent = root_element
+pages_parent = nil
+chars_parent = nil
+num_chars = 0
+
+puts "Parsing #{input_file_path} ..."
+
+IO.foreach(input_file_path) do |line|
+
+ # replace spaces within quotes
+ line.gsub!(/"(\S*)(\s+)(\S*")/) { |match| $1 + SPACE_MARKER + $3 }
+
+ line_parts = line.split /\s+/
+ element_name = line_parts.shift
+
+ next if element_name == "chars"
+
+ if element_name == "page"
+ pages_parent ||= root_element.add_element "pages"
+ current_parent = pages_parent
+ elsif element_name == "char"
+ chars_parent ||= root_element.add_element "chars"
+ current_parent = chars_parent
+ num_chars += 1
+ end
+
+ current_element = current_parent.add_element element_name
+
+ line_parts.each do |part|
+ name, value = part.split("=")
+ current_element.attributes[name] = value.gsub('"', "").gsub(SPACE_MARKER, " ")
+ end
+
+end
+
+chars_parent.attributes["count"] = num_chars
+
+puts "Saving output to #{output_file_path} ..."
+
+File.open output_file_path, 'w+' do |file|
+ xml_doc.write file, 2
+end
+
+puts "Finished successfully."
--- /dev/null
+--- packer2sparrow.rb ---
+
+This Ruby script converts texture atlas files created with the "Packer"-Tool:
+-> http://slick.cokeandcode.com/
+to the XML format that Sparrow expects.
+
+Usage: packer2sparrow input.xml image.png [output.xml]
+
+- The image parameter will be saved in the XML-file, and should be the path to the image on the
+ iPhone,relative to the atlas XML.
+- The output parameter is optional. If omitted, the xml data overwrites the input file.
--- /dev/null
+#!/usr/bin/env ruby
+
+#
+# packer2sparrow.rb
+# Sparrow
+#
+# Created by Daniel Sperl on 11.02.2010
+# 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.
+#
+
+# This script converts atlas XML files created with the "Packer"-Tool to the
+# format that is expected by Sparrow. See README for more information.
+
+if $*.count < 2
+ puts "Usage: packer2sparrow.rb input.xml image.png [output.xml]"
+ exit
+end
+
+# get commandline-arguments
+input_file_path = $*[0]
+image_file_path = $*[1]
+output_file_path = $*.count >= 3 ? $*[2] : input_file_path
+
+if !File.exist?(input_file_path)
+ puts "File #{input_file_path} not found!"
+ exit
+end
+
+puts "Parsing #{input_file_path} ..."
+
+contents = IO.read input_file_path
+
+contents.gsub! "<sheet>", "<TextureAtlas imagePath='#{image_file_path}'>"
+contents.gsub! "<sprite ", "<SubTexture "
+contents.gsub! /.png"/, '"'
+contents.gsub! "</sheet>", "</TextureAtlas>"
+
+puts "Saving output to #{output_file_path} ..."
+
+File.open output_file_path, 'w+' do |file|
+ file << contents
+end
+
+puts "Finished successfully."
--- /dev/null
+----------------------------------------------------------------------------------------------------
+The Sparrow Texture Scaler
+----------------------------------------------------------------------------------------------------
+
+Use this Ruby script to prepare your images for iPhone 4's Retina display.
+
+Create your graphics for the high resolution display. As recommended by Apple, append the suffix
+"@2x" to the images filenames, e.g.: texture@2x.png
+
+Now use this script to scale them down to the low resolution display, removing the suffix, and
+sharpening them (optionally).
+
+----------------------------------------------------------------------------------------------------
+Installation
+----------------------------------------------------------------------------------------------------
+
+This script has the same requirements as the atlas generator. Look in the atlas generator's
+README file for instructions.
+
+----------------------------------------------------------------------------------------------------
+Usage
+----------------------------------------------------------------------------------------------------
+
+To find out how the script works, have a look at the help screen.
+
+./scale_textures.rb --help
+
+Here is simple usage example:
+
+./scale_textures.rb input/*@2x.png output/
+
+Automatic sharpening:
+
+./scale_textures.rb --sharpen input/*@2x.png output/
+
--- /dev/null
+#!/usr/bin/env ruby
+
+#
+# scale_textures.rb
+# Sparrow
+#
+# Created by Daniel Sperl on 09.07.2010
+# 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.
+#
+
+require 'rubygems'
+require 'ftools'
+require 'quick_magick'
+require 'optparse'
+require 'ostruct'
+
+include QuickMagick
+
+# --------------------------------------------------------------------------------------------------
+# --- option parsing -------------------------------------------------------------------------------
+# --------------------------------------------------------------------------------------------------
+
+options = OpenStruct.new
+options.sharpen = false
+options.scale = 0.5
+options.purge_suffix = "@2x"
+options.append_suffix = ""
+
+option_parser = OptionParser.new do |opts|
+
+ opts.banner = "Usage: #{File.basename(__FILE__)} [options] source_images target_path"
+
+ opts.separator ""
+ opts.separator "Options:"
+
+ opts.on('-s', '--scale FACTOR', Float, 'Scale textures (default: 0.5)') do |factor|
+ options.scale = factor
+ end
+
+ opts.on('-r', '--sharpen', 'Sharpen images') do
+ options.sharpen = true
+ end
+
+ opts.on('-p', '--purge_suffix SUFFIX', 'Remove suffix on copies (default: @2x)') do |suffix|
+ options.purge_suffix = suffix
+ end
+
+ opts.on("-a", "--append_suffix SUFFIX", "Add suffix to copies") do |suffix|
+ options.append_suffix = suffix
+ end
+
+ opts.on_tail('-h', '--help', 'Show this message') do
+ puts opts
+ exit
+ end
+
+end
+
+if (ARGV.length < 2)
+ puts option_parser
+ exit
+else
+ option_parser.parse!
+end
+
+# --------------------------------------------------------------------------------------------------
+# --- image processing -----------------------------------------------------------------------------
+# --------------------------------------------------------------------------------------------------
+
+source_files = ARGV.clone
+target_path = source_files.pop
+
+images = source_files.collect { |file| Image.read(file).first }
+
+if images.count == 0
+ puts "No images found!"
+ exit
+else
+ File.makedirs target_path
+end
+
+images.each do |image|
+ extname = File.extname(image.image_filename)
+ basename = File.basename(image.image_filename, extname)
+ basename.gsub!(options.purge_suffix, "") if basename.end_with? options.purge_suffix
+ fullpath = File.join(target_path, basename + options.append_suffix + extname)
+
+ image.resize "#{options.scale * 100}%"
+ image.append_to_operators 'filter', 'Box' if (options.scale == 0.5 || options.scale == 0.25)
+ image.append_to_operators 'sharpen', 1 if (options.sharpen)
+ image.save fullpath
+ puts "Saved #{fullpath}"
+end
objects = {
/* Begin PBXBuildFile section */
+ 49132ED31372483800DFB46D /* libSparrow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 49132ED21372483700DFB46D /* libSparrow.a */; };
499668C713692E2D006E8125 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 499668C613692E2D006E8125 /* UIKit.framework */; };
499668C913692E2D006E8125 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 499668C813692E2D006E8125 /* Foundation.framework */; };
499668CB13692E2D006E8125 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 499668CA13692E2D006E8125 /* CoreGraphics.framework */; };
499668F613692E2D006E8125 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 499668F413692E2D006E8125 /* InfoPlist.strings */; };
499668F913692E2D006E8125 /* tanksTests.h in Resources */ = {isa = PBXBuildFile; fileRef = 499668F813692E2D006E8125 /* tanksTests.h */; };
499668FB13692E2D006E8125 /* tanksTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 499668FA13692E2D006E8125 /* tanksTests.m */; };
- 49966911136930E8006E8125 /* libSparrow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 49966910136930E8006E8125 /* libSparrow.a */; };
49966917136930E8006E8125 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49966912136930E8006E8125 /* AudioToolbox.framework */; };
49966918136930E8006E8125 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49966913136930E8006E8125 /* AVFoundation.framework */; };
49966919136930E8006E8125 /* OpenAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49966914136930E8006E8125 /* OpenAL.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
+ 49132ECC1372459400DFB46D /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 49132EC41372459300DFB46D /* Sparrow.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = DEFE4BC2101B317600E22471;
+ remoteInfo = Sparrow;
+ };
+ 49132ECE1372459400DFB46D /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 49132EC41372459300DFB46D /* Sparrow.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = DEEA9335101E32A30071DD21;
+ remoteInfo = UnitTests;
+ };
+ 49132ED01372482D00DFB46D /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 49132EC41372459300DFB46D /* Sparrow.xcodeproj */;
+ proxyType = 1;
+ remoteGlobalIDString = DEFE4BC1101B317600E22471;
+ remoteInfo = Sparrow;
+ };
499668EF13692E2D006E8125 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 499668B913692E2D006E8125 /* Project object */;
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
+ 49132EC41372459300DFB46D /* Sparrow.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Sparrow.xcodeproj; path = ../libs/sparrow/src/Sparrow.xcodeproj; sourceTree = "<group>"; };
+ 49132ED21372483700DFB46D /* libSparrow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libSparrow.a; sourceTree = SOURCE_ROOT; };
499668C213692E2D006E8125 /* tanks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tanks.app; sourceTree = BUILT_PRODUCTS_DIR; };
499668C613692E2D006E8125 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
499668C813692E2D006E8125 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
499668F713692E2D006E8125 /* tanksTests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "tanksTests-Prefix.pch"; sourceTree = "<group>"; };
499668F813692E2D006E8125 /* tanksTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tanksTests.h; sourceTree = "<group>"; };
499668FA13692E2D006E8125 /* tanksTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = tanksTests.m; sourceTree = "<group>"; };
- 4996690413692FB1006E8125 /* Sparrow.xcodeproj */ = {isa = PBXFileReference; name = Sparrow.xcodeproj; path = ../../../ios/sparrow/src/Sparrow.xcodeproj; sourceTree = SPARROW_SRC; };
- 49966910136930E8006E8125 /* libSparrow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libSparrow.a; sourceTree = SOURCE_ROOT; };
49966912136930E8006E8125 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
49966913136930E8006E8125 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
49966914136930E8006E8125 /* OpenAL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenAL.framework; path = System/Library/Frameworks/OpenAL.framework; sourceTree = SDKROOT; };
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 49132ED31372483800DFB46D /* libSparrow.a in Frameworks */,
49966917136930E8006E8125 /* AudioToolbox.framework in Frameworks */,
49966918136930E8006E8125 /* AVFoundation.framework in Frameworks */,
49966919136930E8006E8125 /* OpenAL.framework in Frameworks */,
4996691A136930E8006E8125 /* OpenGLES.framework in Frameworks */,
4996691B136930E8006E8125 /* QuartzCore.framework in Frameworks */,
- 49966911136930E8006E8125 /* libSparrow.a in Frameworks */,
499668C713692E2D006E8125 /* UIKit.framework in Frameworks */,
499668C913692E2D006E8125 /* Foundation.framework in Frameworks */,
499668CB13692E2D006E8125 /* CoreGraphics.framework in Frameworks */,
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 49132EC51372459300DFB46D /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 49132ECD1372459400DFB46D /* libSparrow.a */,
+ 49132ECF1372459400DFB46D /* UnitTest.octest */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
499668B713692E2D006E8125 = {
isa = PBXGroup;
children = (
- 4996690413692FB1006E8125 /* Sparrow.xcodeproj */,
+ 49132EC41372459300DFB46D /* Sparrow.xcodeproj */,
499668CC13692E2D006E8125 /* tanks */,
499668F113692E2D006E8125 /* tanksTests */,
499668C513692E2D006E8125 /* Frameworks */,
499668C513692E2D006E8125 /* Frameworks */ = {
isa = PBXGroup;
children = (
+ 49132ED21372483700DFB46D /* libSparrow.a */,
49966912136930E8006E8125 /* AudioToolbox.framework */,
49966913136930E8006E8125 /* AVFoundation.framework */,
49966914136930E8006E8125 /* OpenAL.framework */,
49966915136930E8006E8125 /* OpenGLES.framework */,
49966916136930E8006E8125 /* QuartzCore.framework */,
- 49966910136930E8006E8125 /* libSparrow.a */,
499668C613692E2D006E8125 /* UIKit.framework */,
499668C813692E2D006E8125 /* Foundation.framework */,
499668CA13692E2D006E8125 /* CoreGraphics.framework */,
buildRules = (
);
dependencies = (
+ 49132ED11372482D00DFB46D /* PBXTargetDependency */,
);
name = tanks;
productName = tanks;
mainGroup = 499668B713692E2D006E8125;
productRefGroup = 499668C313692E2D006E8125 /* Products */;
projectDirPath = "";
+ projectReferences = (
+ {
+ ProductGroup = 49132EC51372459300DFB46D /* Products */;
+ ProjectRef = 49132EC41372459300DFB46D /* Sparrow.xcodeproj */;
+ },
+ );
projectRoot = "";
targets = (
499668C113692E2D006E8125 /* tanks */,
};
/* End PBXProject section */
+/* Begin PBXReferenceProxy section */
+ 49132ECD1372459400DFB46D /* libSparrow.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = libSparrow.a;
+ remoteRef = 49132ECC1372459400DFB46D /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 49132ECF1372459400DFB46D /* UnitTest.octest */ = {
+ isa = PBXReferenceProxy;
+ fileType = wrapper.cfbundle;
+ path = UnitTest.octest;
+ remoteRef = 49132ECE1372459400DFB46D /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+/* End PBXReferenceProxy section */
+
/* Begin PBXResourcesBuildPhase section */
499668C013692E2D006E8125 /* Resources */ = {
isa = PBXResourcesBuildPhase;
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
+ 49132ED11372482D00DFB46D /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ name = Sparrow;
+ targetProxy = 49132ED01372482D00DFB46D /* PBXContainerItemProxy */;
+ };
499668F013692E2D006E8125 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 499668C113692E2D006E8125 /* tanks */;
499668FF13692E2D006E8125 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
+ ALWAYS_SEARCH_USER_PATHS = YES;
COPY_PHASE_STRIP = NO;
GCC_DYNAMIC_NO_PIC = NO;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "tanks/tanks-Prefix.pch";
INFOPLIST_FILE = "tanks/tanks-Info.plist";
LIBRARY_SEARCH_PATHS = (
- "$(inherited)",
+ "../libs/**",
"\"$(SRCROOT)\"",
);
OTHER_LDFLAGS = (
"-all_load",
);
PRODUCT_NAME = "$(TARGET_NAME)";
- USER_HEADER_SEARCH_PATHS = "${SPARROW_SRC}/**";
+ USER_HEADER_SEARCH_PATHS = "../libs/**";
WRAPPER_EXTENSION = app;
};
name = Debug;
4996690013692E2D006E8125 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
+ ALWAYS_SEARCH_USER_PATHS = YES;
COPY_PHASE_STRIP = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "tanks/tanks-Prefix.pch";
INFOPLIST_FILE = "tanks/tanks-Info.plist";
LIBRARY_SEARCH_PATHS = (
- "$(inherited)",
+ "../libs/**",
"\"$(SRCROOT)\"",
);
OTHER_LDFLAGS = (
"-all_load",
);
PRODUCT_NAME = "$(TARGET_NAME)";
- USER_HEADER_SEARCH_PATHS = "${SPARROW_SRC}/**";
+ USER_HEADER_SEARCH_PATHS = "../libs/**";
VALIDATE_PRODUCT = YES;
WRAPPER_EXTENSION = app;
};
4996690013692E2D006E8125 /* Release */,
);
defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
};
4996690113692E2D006E8125 /* Build configuration list for PBXNativeTarget "tanksTests" */ = {
isa = XCConfigurationList;
4996690313692E2D006E8125 /* Release */,
);
defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};