# 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