Fixes the mess with sparrow.
authordsc <david.schoonover@gmail.com>
Thu, 5 May 2011 02:58:51 +0000 (19:58 -0700)
committerdsc <david.schoonover@gmail.com>
Thu, 5 May 2011 02:58:51 +0000 (19:58 -0700)
126 files changed:
.gitignore
libs/sparrow/BUILDING [new file with mode: 0644]
libs/sparrow/CHANGELOG [new file with mode: 0644]
libs/sparrow/LICENSE [new file with mode: 0644]
libs/sparrow/README [new file with mode: 0644]
libs/sparrow/doc/generate.sh [new file with mode: 0755]
libs/sparrow/src/Classes/SPALSound.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPALSound.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPALSoundChannel.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPALSoundChannel.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPAVSound.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPAVSound.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPAVSoundChannel.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPAVSoundChannel.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPAnimatable.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPAudioEngine.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPAudioEngine.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPBitmapChar.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPBitmapChar.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPBitmapFont.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPBitmapFont.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPButton.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPButton.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPCompiledSprite.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPCompiledSprite.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPDelayedInvocation.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPDelayedInvocation.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPDisplayObject.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPDisplayObject.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPDisplayObjectContainer.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPDisplayObjectContainer.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPDisplayObject_Internal.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPEnterFrameEvent.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPEnterFrameEvent.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPEvent.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPEvent.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPEventDispatcher.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPEventDispatcher.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPEvent_Internal.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPGLTexture.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPGLTexture.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPImage.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPImage.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPJuggler.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPJuggler.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPMacros.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPMatrix.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPMatrix.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPMovieClip.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPMovieClip.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPNSExtensions.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPNSExtensions.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPPoint.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPPoint.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPPoolObject.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPPoolObject.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPQuad.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPQuad.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPRectangle.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPRectangle.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPRenderSupport.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPRenderSupport.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPRenderTexture.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPRenderTexture.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPRendering.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPSound.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPSound.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPSoundChannel.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPSoundChannel.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPSprite.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPSprite.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPStage.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPStage.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPStage_Internal.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPSubTexture.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPSubTexture.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPTextField.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPTextField.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPTexture.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPTexture.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPTextureAtlas.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPTextureAtlas.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPTouch.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPTouch.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPTouchEvent.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPTouchEvent.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPTouchProcessor.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPTouchProcessor.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPTouch_Internal.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPTransitions.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPTransitions.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPTween.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPTween.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPTweenedProperty.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPTweenedProperty.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPUtils.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPUtils.m [new file with mode: 0644]
libs/sparrow/src/Classes/SPView.h [new file with mode: 0644]
libs/sparrow/src/Classes/SPView.m [new file with mode: 0644]
libs/sparrow/src/Classes/Sparrow.h [new file with mode: 0644]
libs/sparrow/src/Sparrow.xcodeproj/project.pbxproj [new file with mode: 0755]
libs/sparrow/src/UnitTests-Info.plist [new file with mode: 0644]
libs/sparrow/src/UnitTests/SPDelayedInvocationTest.m [new file with mode: 0644]
libs/sparrow/src/UnitTests/SPDisplayObjectContainerTest.m [new file with mode: 0644]
libs/sparrow/src/UnitTests/SPDisplayObjectTest.m [new file with mode: 0644]
libs/sparrow/src/UnitTests/SPEventDispatcherTest.m [new file with mode: 0644]
libs/sparrow/src/UnitTests/SPImageTest.m [new file with mode: 0644]
libs/sparrow/src/UnitTests/SPJugglerTest.m [new file with mode: 0644]
libs/sparrow/src/UnitTests/SPMatrixTest.m [new file with mode: 0644]
libs/sparrow/src/UnitTests/SPMovieClipTest.m [new file with mode: 0644]
libs/sparrow/src/UnitTests/SPNSExtensionsTest.m [new file with mode: 0644]
libs/sparrow/src/UnitTests/SPPointTest.m [new file with mode: 0644]
libs/sparrow/src/UnitTests/SPQuadTest.m [new file with mode: 0644]
libs/sparrow/src/UnitTests/SPRectangleTest.m [new file with mode: 0644]
libs/sparrow/src/UnitTests/SPStageTest.m [new file with mode: 0644]
libs/sparrow/src/UnitTests/SPTweenTest.m [new file with mode: 0644]
libs/sparrow/src/UnitTests/SPUtilsTest.m [new file with mode: 0644]
libs/sparrow/util/atlas_generator/README [new file with mode: 0644]
libs/sparrow/util/atlas_generator/generate_atlas.rb [new file with mode: 0755]
libs/sparrow/util/hiero2sparrow/README [new file with mode: 0644]
libs/sparrow/util/hiero2sparrow/hiero2sparrow.rb [new file with mode: 0755]
libs/sparrow/util/packer2sparrow/README [new file with mode: 0644]
libs/sparrow/util/packer2sparrow/packer2sparrow.rb [new file with mode: 0644]
libs/sparrow/util/texture_scaler/README [new file with mode: 0644]
libs/sparrow/util/texture_scaler/scale_textures.rb [new file with mode: 0755]
tanks/tanks.xcodeproj/project.pbxproj

index edc082c..9fac711 100644 (file)
@@ -1,5 +1,5 @@
 # Xcode
-build/*
+*/build/*
 *.pbxuser
 !default.pbxuser
 *.mode1v3
diff --git a/libs/sparrow/BUILDING b/libs/sparrow/BUILDING
new file mode 100644 (file)
index 0000000..2f3e629
--- /dev/null
@@ -0,0 +1,46 @@
+----------------------------------------------------------------------------------------------------
+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
diff --git a/libs/sparrow/CHANGELOG b/libs/sparrow/CHANGELOG
new file mode 100644 (file)
index 0000000..8aeb392
--- /dev/null
@@ -0,0 +1,110 @@
+----------------------------------------------------------------------------------------------------
+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 
diff --git a/libs/sparrow/LICENSE b/libs/sparrow/LICENSE
new file mode 100644 (file)
index 0000000..7164bd5
--- /dev/null
@@ -0,0 +1,33 @@
+----------------------------------------------------------------------------------------------
+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
diff --git a/libs/sparrow/README b/libs/sparrow/README
new file mode 100644 (file)
index 0000000..9d9715c
--- /dev/null
@@ -0,0 +1,26 @@
+----------------------------------------------------------------------------------------------------
+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
diff --git a/libs/sparrow/doc/generate.sh b/libs/sparrow/doc/generate.sh
new file mode 100755 (executable)
index 0000000..c6bb5e6
--- /dev/null
@@ -0,0 +1,33 @@
+#!/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."
diff --git a/libs/sparrow/src/Classes/SPALSound.h b/libs/sparrow/src/Classes/SPALSound.h
new file mode 100644 (file)
index 0000000..20e250c
--- /dev/null
@@ -0,0 +1,45 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPALSound.m b/libs/sparrow/src/Classes/SPALSound.m
new file mode 100644 (file)
index 0000000..801960a
--- /dev/null
@@ -0,0 +1,94 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPALSoundChannel.h b/libs/sparrow/src/Classes/SPALSoundChannel.h
new file mode 100644 (file)
index 0000000..957277c
--- /dev/null
@@ -0,0 +1,46 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPALSoundChannel.m b/libs/sparrow/src/Classes/SPALSoundChannel.m
new file mode 100644 (file)
index 0000000..2ce4410
--- /dev/null
@@ -0,0 +1,217 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPAVSound.h b/libs/sparrow/src/Classes/SPAVSound.h
new file mode 100644 (file)
index 0000000..ce66c0b
--- /dev/null
@@ -0,0 +1,46 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPAVSound.m b/libs/sparrow/src/Classes/SPAVSound.m
new file mode 100644 (file)
index 0000000..5b917e7
--- /dev/null
@@ -0,0 +1,56 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPAVSoundChannel.h b/libs/sparrow/src/Classes/SPAVSoundChannel.h
new file mode 100644 (file)
index 0000000..8d722ae
--- /dev/null
@@ -0,0 +1,43 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPAVSoundChannel.m b/libs/sparrow/src/Classes/SPAVSoundChannel.m
new file mode 100644 (file)
index 0000000..81c411c
--- /dev/null
@@ -0,0 +1,136 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPAnimatable.h b/libs/sparrow/src/Classes/SPAnimatable.h
new file mode 100644 (file)
index 0000000..4668d71
--- /dev/null
@@ -0,0 +1,29 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPAudioEngine.h b/libs/sparrow/src/Classes/SPAudioEngine.h
new file mode 100644 (file)
index 0000000..d59fe5d
--- /dev/null
@@ -0,0 +1,64 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPAudioEngine.m b/libs/sparrow/src/Classes/SPAudioEngine.m
new file mode 100644 (file)
index 0000000..3f8bd06
--- /dev/null
@@ -0,0 +1,174 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPBitmapChar.h b/libs/sparrow/src/Classes/SPBitmapChar.h
new file mode 100644 (file)
index 0000000..07804fa
--- /dev/null
@@ -0,0 +1,57 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPBitmapChar.m b/libs/sparrow/src/Classes/SPBitmapChar.m
new file mode 100644 (file)
index 0000000..e3124c2
--- /dev/null
@@ -0,0 +1,55 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPBitmapFont.h b/libs/sparrow/src/Classes/SPBitmapFont.h
new file mode 100644 (file)
index 0000000..402ef57
--- /dev/null
@@ -0,0 +1,94 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPBitmapFont.m b/libs/sparrow/src/Classes/SPBitmapFont.m
new file mode 100644 (file)
index 0000000..ea81f05
--- /dev/null
@@ -0,0 +1,289 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPButton.h b/libs/sparrow/src/Classes/SPButton.h
new file mode 100644 (file)
index 0000000..f8c0214
--- /dev/null
@@ -0,0 +1,112 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPButton.m b/libs/sparrow/src/Classes/SPButton.m
new file mode 100644 (file)
index 0000000..c2cb93a
--- /dev/null
@@ -0,0 +1,261 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPCompiledSprite.h b/libs/sparrow/src/Classes/SPCompiledSprite.h
new file mode 100644 (file)
index 0000000..81bcce0
--- /dev/null
@@ -0,0 +1,61 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPCompiledSprite.m b/libs/sparrow/src/Classes/SPCompiledSprite.m
new file mode 100644 (file)
index 0000000..d400f74
--- /dev/null
@@ -0,0 +1,436 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPDelayedInvocation.h b/libs/sparrow/src/Classes/SPDelayedInvocation.h
new file mode 100644 (file)
index 0000000..9dbea9f
--- /dev/null
@@ -0,0 +1,58 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPDelayedInvocation.m b/libs/sparrow/src/Classes/SPDelayedInvocation.m
new file mode 100644 (file)
index 0000000..ba8e762
--- /dev/null
@@ -0,0 +1,93 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPDisplayObject.h b/libs/sparrow/src/Classes/SPDisplayObject.h
new file mode 100644 (file)
index 0000000..2e85055
--- /dev/null
@@ -0,0 +1,172 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPDisplayObject.m b/libs/sparrow/src/Classes/SPDisplayObject.m
new file mode 100644 (file)
index 0000000..6eab8f6
--- /dev/null
@@ -0,0 +1,317 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPDisplayObjectContainer.h b/libs/sparrow/src/Classes/SPDisplayObjectContainer.h
new file mode 100644 (file)
index 0000000..4fc405f
--- /dev/null
@@ -0,0 +1,100 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPDisplayObjectContainer.m b/libs/sparrow/src/Classes/SPDisplayObjectContainer.m
new file mode 100644 (file)
index 0000000..f9420c1
--- /dev/null
@@ -0,0 +1,250 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPDisplayObject_Internal.h b/libs/sparrow/src/Classes/SPDisplayObject_Internal.h
new file mode 100644 (file)
index 0000000..6ee335b
--- /dev/null
@@ -0,0 +1,20 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPEnterFrameEvent.h b/libs/sparrow/src/Classes/SPEnterFrameEvent.h
new file mode 100644 (file)
index 0000000..535bc86
--- /dev/null
@@ -0,0 +1,54 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPEnterFrameEvent.m b/libs/sparrow/src/Classes/SPEnterFrameEvent.m
new file mode 100644 (file)
index 0000000..50aa213
--- /dev/null
@@ -0,0 +1,48 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPEvent.h b/libs/sparrow/src/Classes/SPEvent.h
new file mode 100644 (file)
index 0000000..a4c2a90
--- /dev/null
@@ -0,0 +1,93 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPEvent.m b/libs/sparrow/src/Classes/SPEvent.m
new file mode 100644 (file)
index 0000000..66b0b81
--- /dev/null
@@ -0,0 +1,104 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPEventDispatcher.h b/libs/sparrow/src/Classes/SPEventDispatcher.h
new file mode 100644 (file)
index 0000000..264e9da
--- /dev/null
@@ -0,0 +1,82 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPEventDispatcher.m b/libs/sparrow/src/Classes/SPEventDispatcher.m
new file mode 100644 (file)
index 0000000..9721b9d
--- /dev/null
@@ -0,0 +1,140 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPEvent_Internal.h b/libs/sparrow/src/Classes/SPEvent_Internal.h
new file mode 100644 (file)
index 0000000..acace14
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  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
+
diff --git a/libs/sparrow/src/Classes/SPGLTexture.h b/libs/sparrow/src/Classes/SPGLTexture.h
new file mode 100644 (file)
index 0000000..71b2e44
--- /dev/null
@@ -0,0 +1,82 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPGLTexture.m b/libs/sparrow/src/Classes/SPGLTexture.m
new file mode 100644 (file)
index 0000000..5ff43a4
--- /dev/null
@@ -0,0 +1,183 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPImage.h b/libs/sparrow/src/Classes/SPImage.h
new file mode 100644 (file)
index 0000000..06e4d3b
--- /dev/null
@@ -0,0 +1,72 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPImage.m b/libs/sparrow/src/Classes/SPImage.m
new file mode 100644 (file)
index 0000000..732a432
--- /dev/null
@@ -0,0 +1,80 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPJuggler.h b/libs/sparrow/src/Classes/SPJuggler.h
new file mode 100644 (file)
index 0000000..c7b48da
--- /dev/null
@@ -0,0 +1,88 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPJuggler.m b/libs/sparrow/src/Classes/SPJuggler.m
new file mode 100644 (file)
index 0000000..831919a
--- /dev/null
@@ -0,0 +1,96 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPMacros.h b/libs/sparrow/src/Classes/SPMacros.h
new file mode 100644 (file)
index 0000000..aea1b3e
--- /dev/null
@@ -0,0 +1,56 @@
+//
+//  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)
+
+
diff --git a/libs/sparrow/src/Classes/SPMatrix.h b/libs/sparrow/src/Classes/SPMatrix.h
new file mode 100644 (file)
index 0000000..7ca72df
--- /dev/null
@@ -0,0 +1,99 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPMatrix.m b/libs/sparrow/src/Classes/SPMatrix.m
new file mode 100644 (file)
index 0000000..2e83df0
--- /dev/null
@@ -0,0 +1,165 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPMovieClip.h b/libs/sparrow/src/Classes/SPMovieClip.h
new file mode 100644 (file)
index 0000000..c3e85c8
--- /dev/null
@@ -0,0 +1,136 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPMovieClip.m b/libs/sparrow/src/Classes/SPMovieClip.m
new file mode 100644 (file)
index 0000000..bbabe34
--- /dev/null
@@ -0,0 +1,274 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPNSExtensions.h b/libs/sparrow/src/Classes/SPNSExtensions.h
new file mode 100644 (file)
index 0000000..8b981d0
--- /dev/null
@@ -0,0 +1,39 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPNSExtensions.m b/libs/sparrow/src/Classes/SPNSExtensions.m
new file mode 100644 (file)
index 0000000..9453d16
--- /dev/null
@@ -0,0 +1,50 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPPoint.h b/libs/sparrow/src/Classes/SPPoint.h
new file mode 100644 (file)
index 0000000..2e635d7
--- /dev/null
@@ -0,0 +1,82 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPPoint.m b/libs/sparrow/src/Classes/SPPoint.m
new file mode 100644 (file)
index 0000000..a6ffe87
--- /dev/null
@@ -0,0 +1,142 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPPoolObject.h b/libs/sparrow/src/Classes/SPPoolObject.h
new file mode 100644 (file)
index 0000000..0656acb
--- /dev/null
@@ -0,0 +1,78 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPPoolObject.m b/libs/sparrow/src/Classes/SPPoolObject.m
new file mode 100644 (file)
index 0000000..289af57
--- /dev/null
@@ -0,0 +1,113 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPQuad.h b/libs/sparrow/src/Classes/SPQuad.h
new file mode 100644 (file)
index 0000000..3120155
--- /dev/null
@@ -0,0 +1,97 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPQuad.m b/libs/sparrow/src/Classes/SPQuad.m
new file mode 100644 (file)
index 0000000..134bf01
--- /dev/null
@@ -0,0 +1,104 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPRectangle.h b/libs/sparrow/src/Classes/SPRectangle.h
new file mode 100644 (file)
index 0000000..a5d1b2f
--- /dev/null
@@ -0,0 +1,77 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPRectangle.m b/libs/sparrow/src/Classes/SPRectangle.m
new file mode 100644 (file)
index 0000000..0a00da7
--- /dev/null
@@ -0,0 +1,143 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPRenderSupport.h b/libs/sparrow/src/Classes/SPRenderSupport.h
new file mode 100644 (file)
index 0000000..e79951f
--- /dev/null
@@ -0,0 +1,69 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPRenderSupport.m b/libs/sparrow/src/Classes/SPRenderSupport.m
new file mode 100644 (file)
index 0000000..13103ec
--- /dev/null
@@ -0,0 +1,125 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPRenderTexture.h b/libs/sparrow/src/Classes/SPRenderTexture.h
new file mode 100644 (file)
index 0000000..6655c54
--- /dev/null
@@ -0,0 +1,91 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPRenderTexture.m b/libs/sparrow/src/Classes/SPRenderTexture.m
new file mode 100644 (file)
index 0000000..75c6eb2
--- /dev/null
@@ -0,0 +1,210 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPRendering.m b/libs/sparrow/src/Classes/SPRendering.m
new file mode 100644 (file)
index 0000000..be27e5b
--- /dev/null
@@ -0,0 +1,125 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPSound.h b/libs/sparrow/src/Classes/SPSound.h
new file mode 100644 (file)
index 0000000..0e90616
--- /dev/null
@@ -0,0 +1,72 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPSound.m b/libs/sparrow/src/Classes/SPSound.m
new file mode 100644 (file)
index 0000000..d137e68
--- /dev/null
@@ -0,0 +1,204 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPSoundChannel.h b/libs/sparrow/src/Classes/SPSoundChannel.h
new file mode 100644 (file)
index 0000000..d1b1d83
--- /dev/null
@@ -0,0 +1,67 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPSoundChannel.m b/libs/sparrow/src/Classes/SPSoundChannel.m
new file mode 100644 (file)
index 0000000..8965284
--- /dev/null
@@ -0,0 +1,91 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPSprite.h b/libs/sparrow/src/Classes/SPSprite.h
new file mode 100644 (file)
index 0000000..643748c
--- /dev/null
@@ -0,0 +1,48 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPSprite.m b/libs/sparrow/src/Classes/SPSprite.m
new file mode 100644 (file)
index 0000000..0e1c317
--- /dev/null
@@ -0,0 +1,22 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPStage.h b/libs/sparrow/src/Classes/SPStage.h
new file mode 100644 (file)
index 0000000..efade55
--- /dev/null
@@ -0,0 +1,97 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPStage.m b/libs/sparrow/src/Classes/SPStage.m
new file mode 100644 (file)
index 0000000..f767c3e
--- /dev/null
@@ -0,0 +1,234 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPStage_Internal.h b/libs/sparrow/src/Classes/SPStage_Internal.h
new file mode 100644 (file)
index 0000000..20c89cb
--- /dev/null
@@ -0,0 +1,19 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPSubTexture.h b/libs/sparrow/src/Classes/SPSubTexture.h
new file mode 100644 (file)
index 0000000..36106c9
--- /dev/null
@@ -0,0 +1,52 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPSubTexture.m b/libs/sparrow/src/Classes/SPSubTexture.m
new file mode 100644 (file)
index 0000000..b5478f1
--- /dev/null
@@ -0,0 +1,118 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPTextField.h b/libs/sparrow/src/Classes/SPTextField.h
new file mode 100644 (file)
index 0000000..ce28744
--- /dev/null
@@ -0,0 +1,169 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPTextField.m b/libs/sparrow/src/Classes/SPTextField.m
new file mode 100644 (file)
index 0000000..482f399
--- /dev/null
@@ -0,0 +1,329 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPTexture.h b/libs/sparrow/src/Classes/SPTexture.h
new file mode 100644 (file)
index 0000000..d570053
--- /dev/null
@@ -0,0 +1,133 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPTexture.m b/libs/sparrow/src/Classes/SPTexture.m
new file mode 100644 (file)
index 0000000..3a8aeaf
--- /dev/null
@@ -0,0 +1,306 @@
+//
+//  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
diff --git a/libs/sparrow/src/Classes/SPTextureAtlas.h b/libs/sparrow/src/Classes/SPTextureAtlas.h
new file mode 100644 (file)
index 0000000..3981fc8
--- /dev/null
@@ -0,0 +1,103 @@
+//
+//  SPTextureAtlas.h
+//  Sparrow
+//
+//  Created by Daniel Sperl on 27.06.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+
+@class SPTexture;
+@class SPRectangle;
+
+/** ------------------------------------------------------------------------------------------------
+
+ A texture atlas is a collection of many smaller textures in one big image. The class
+ `SPTextureAtlas` is used to access textures from such an atlas.
+ Using a texture atlas for your textures has two main advantages:
+ * In OpenGL, there’s always one texture active at a given moment. Whenever you change the active 
+   texture, a "texture-switch" has to be executed, and that switch takes time.
+ * To use a texture in OpenGL, its height and width must each be a power of 2. Sparrow hides this 
+   limitation from you, but you will nevertheless use more memory if you do not follow that rule.
+ By using a texture atlas, you avoid both texture switches and the power-of-two limitation. All 
+ textures are within one big "super-texture", and Sparrow takes care that the correct part of this 
+ texture is displayed.
+ There are several ways to create a texture atlas. One is to use the atlas generator script that
+ is provided with Sparrow. Here is a sample on how to use it:
+       # creates "atlas.xml" and "atlas.png" from the  provided images 
+       ./generate_atlas.rb input/*.png output/atlas.xml
+ The atlas generator can be found in the 'utils' directory in the Sparrow package. A README file
+ shows you how to install and use it. If you want to have more control over your atlas, you will
+ find great alternative tools on the Internet (e.g. "Texture Packer").
+ Whatever tool you use, Sparrow expects the following file format:
+
+       <TextureAtlas imagePath='atlas.png'>
+         <SubTexture name='texture_1' x='0'  y='0' height='50' width='50'/>
+         <SubTexture name='texture_2' x='50' y='0' height='30' width='20'/> 
+       </TextureAtlas>
+------------------------------------------------------------------------------------------------- */
+
+#ifdef __IPHONE_4_0
+@interface SPTextureAtlas : NSObject <NSXMLParserDelegate>
+#else
+@interface SPTextureAtlas : NSObject
+#endif
+{
+  @private
+    SPTexture *mAtlasTexture;
+    NSMutableDictionary *mTextureRegions;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a texture atlas from an XML file and a custom texture. _Designated Initializer_.
+- (id)initWithContentsOfFile:(NSString *)path texture:(SPTexture *)texture;
+
+/// Initializes a texture atlas from an XML file, loading the texture that is specified in the XML.
+- (id)initWithContentsOfFile:(NSString *)path;
+
+/// Initializes a teture atlas from a texture. Add the regions manually with `addName:forRegion:`.
+- (id)initWithTexture:(SPTexture *)texture;
+
+/// Factory Method.
++ (SPTextureAtlas *)atlasWithContentsOfFile:(NSString *)path;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Retrieve a subtexture by name. Returns `nil` if it is not found.
+- (SPTexture *)textureByName:(NSString *)name;
+
+/// Returns all textures that start with a certain string, sorted alphabetically
+/// (especially useful for `SPMovieClip`).
+- (NSArray *)texturesStartingWith:(NSString *)name;
+
+/// Creates a region for a subtexture and gives it a name.
+- (void)addRegion:(SPRectangle *)region withName:(NSString *)name;
+
+/// Removes a region with a certain name.
+- (void)removeRegion:(NSString *)name;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The number of available subtextures.
+@property (nonatomic, readonly) int count;
+
+@end
diff --git a/libs/sparrow/src/Classes/SPTextureAtlas.m b/libs/sparrow/src/Classes/SPTextureAtlas.m
new file mode 100644 (file)
index 0000000..74e0f20
--- /dev/null
@@ -0,0 +1,160 @@
+//
+//  SPTextureAtlas.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 27.06.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import "SPTextureAtlas.h"
+#import "SPMacros.h"
+#import "SPTexture.h"
+#import "SPSubTexture.h"
+#import "SPGLTexture.h"
+#import "SPRectangle.h"
+#import "SPNSExtensions.h"
+#import "SPStage.h"
+
+// --- private interface ---------------------------------------------------------------------------
+
+@interface SPTextureAtlas()
+
+- (void)parseAtlasXml:(NSString*)path;
+
+@end
+
+// --- class implementation ------------------------------------------------------------------------
+
+@implementation SPTextureAtlas
+
+- (id)initWithContentsOfFile:(NSString *)path texture:(SPTexture *)texture
+{
+    if (self = [super init])
+    {
+        mTextureRegions = [[NSMutableDictionary alloc] init];
+        mAtlasTexture = [texture retain];
+        [self parseAtlasXml:path];
+    }
+    return self;    
+}
+
+- (id)initWithContentsOfFile:(NSString *)path
+{
+    return [self initWithContentsOfFile:path texture:nil];
+}
+
+- (id)initWithTexture:(SPTexture *)texture
+{
+    return [self initWithContentsOfFile:nil texture:(SPTexture *)texture];
+}
+
+- (id)init
+{
+    return [self initWithContentsOfFile:nil texture:nil];
+}
+
+- (void)parseAtlasXml:(NSString *)path
+{
+    SP_CREATE_POOL(pool);
+    
+    if (!path) return;
+    
+    float scale = [SPStage contentScaleFactor];
+    NSString *fullPath = [[NSBundle mainBundle] pathForResource:path withScaleFactor:scale];
+    NSURL *xmlUrl = [NSURL fileURLWithPath:fullPath];
+    NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlUrl];
+    xmlParser.delegate = self;    
+    BOOL success = [xmlParser parse];
+    
+    SP_RELEASE_POOL(pool);
+    
+    if (!success)    
+        [NSException raise:SP_EXC_FILE_INVALID 
+                    format:@"could not parse texture atlas %@. Error code: %d, domain: %@", 
+                           path, xmlParser.parserError.code, xmlParser.parserError.domain];
+
+    [xmlParser release];    
+}
+
+- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName 
+                                        namespaceURI:(NSString *)namespaceURI 
+                                       qualifiedName:(NSString *)qName 
+                                          attributes:(NSDictionary *)attributeDict 
+{
+    if ([elementName isEqualToString:@"SubTexture"])
+    {
+        float scale = mAtlasTexture.scale;
+        
+        NSString *name = [attributeDict valueForKey:@"name"];
+        float x = [[attributeDict valueForKey:@"x"] floatValue] / scale;
+        float y = [[attributeDict valueForKey:@"y"] floatValue] / scale;
+        float width = [[attributeDict valueForKey:@"width"] floatValue] / scale;
+        float height = [[attributeDict valueForKey:@"height"] floatValue] / scale;
+        
+        [self addRegion:[SPRectangle rectangleWithX:x y:y width:width height:height] withName:name];
+    }
+    else if ([elementName isEqualToString:@"TextureAtlas"] && !mAtlasTexture)
+    {
+        // load atlas texture
+        NSString *imagePath = [attributeDict valueForKey:@"imagePath"];        
+        mAtlasTexture = [[SPTexture alloc] initWithContentsOfFile:imagePath];
+    }
+}
+
+- (int)count
+{
+    return [mTextureRegions count];
+}
+
+- (SPTexture *)textureByName:(NSString *)name
+{
+    SPRectangle *region = [mTextureRegions objectForKey:name];
+    if (!region) return nil;    
+    return [SPSubTexture textureWithRegion:region ofTexture:mAtlasTexture];    
+}
+
+- (NSArray *)texturesStartingWith:(NSString *)name
+{
+    NSMutableArray *textureNames = [[NSMutableArray alloc] init];
+    
+    for (NSString *textureName in mTextureRegions)
+        if ([textureName rangeOfString:name].location == 0)
+            [textureNames addObject:textureName];
+    
+    // note: when switching to iOS 4, 'localizedStandardCompare:' would be preferable    
+    [textureNames sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
+    
+    NSMutableArray *textures = [NSMutableArray arrayWithCapacity:textureNames.count];
+    for (NSString *textureName in textureNames)
+        [textures addObject:[self textureByName:textureName]];
+    
+    [textureNames release];
+    return textures;
+}
+
+- (void)addRegion:(SPRectangle *)region withName:(NSString *)name
+{
+    [mTextureRegions setObject:region forKey:name];
+}
+
+- (void)removeRegion:(NSString *)name
+{
+    [mTextureRegions removeObjectForKey:name];
+}
+
++ (SPTextureAtlas *)atlasWithContentsOfFile:(NSString *)path
+{
+    return [[[SPTextureAtlas alloc] initWithContentsOfFile:path] autorelease];
+}
+
+- (void)dealloc
+{
+    [mAtlasTexture release];
+    [mTextureRegions release];
+    [super dealloc];
+}
+
+@end
diff --git a/libs/sparrow/src/Classes/SPTouch.h b/libs/sparrow/src/Classes/SPTouch.h
new file mode 100644 (file)
index 0000000..573ae73
--- /dev/null
@@ -0,0 +1,105 @@
+//
+//  SPTouch.h
+//  Sparrow
+//
+//  Created by Daniel Sperl on 01.05.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+
+@class SPDisplayObject;
+@class SPPoint;
+
+/// SPTouchPhase describes the phases in the life-cycle of a touch.
+typedef enum 
+{    
+    SPTouchPhaseBegan,      /// The finger just touched the screen.    
+    SPTouchPhaseMoved,      /// The finger moves around.    
+    SPTouchPhaseStationary, /// The finger has not moved since the last frame.    
+    SPTouchPhaseEnded,      /// The finger was lifted from the screen.    
+    SPTouchPhaseCancelled   /// The touch was aborted by the system (e.g. because of an AlertBox popping up)
+} SPTouchPhase;
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPTouch contains information about the presence or movement of a finger on the screen.
+ You receive objects of this type via an SPTouchEvent. When such an event is triggered, you can 
+ query it for all touches that are currently present on the screen. One SPTouch object contains
+ information about a single touch.
+ *The phase of a touch*
+ Each touch normally moves through the following phases in its life:
+ `Began -> Moved -> Ended`
+ Furthermore, a touch can enter a `Stationary` phase. That phase will not trigger an event itself, 
+ but you might receive it when another touch does so. Finally, there's the `Cancelled` phase,
+ which happens when the system aborts a touch (e.g. because of an AlertBox that pops up).
+ *The position of a touch*
+ You can get the current and last position on the screen with corresponding properties. However, 
+ you'll want to have the position in a different coordinate system most of the time. 
+ For this reason, there are methods that convert the current and previous touches into the local
+ coordinate system of any object.
+------------------------------------------------------------------------------------------------- */
+
+@interface SPTouch : NSObject 
+{
+  @private
+    double mTimestamp;
+    float mGlobalX;
+    float mGlobalY;
+    float mPreviousGlobalX;
+    float mPreviousGlobalY;    
+    int mTapCount;
+    SPTouchPhase mPhase;
+    SPDisplayObject *mTarget;
+}
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Converts the current location of a touch to the local coordinate system of a display object.
+- (SPPoint*)locationInSpace:(SPDisplayObject*)space;
+
+/// Converts the previous location of a touch to the local coordinate system of a display object.
+- (SPPoint*)previousLocationInSpace:(SPDisplayObject*)space;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The moment the event occurred (in seconds since application start).
+@property (nonatomic, readonly) double timestamp;
+
+/// The x-position of the touch in screen coordinates
+@property (nonatomic, readonly) float globalX;
+
+/// The y-position of the touch in screen coordinates
+@property (nonatomic, readonly) float globalY;
+
+/// The previous x-position of the touch in screen coordinates
+@property (nonatomic, readonly) float previousGlobalX;
+
+/// The previous y-position of the touch in screen coordinates
+@property (nonatomic, readonly) float previousGlobalY;
+
+/// The number of taps the finger made in a short amount of time. Use this to detect double-taps, etc. 
+@property (nonatomic, readonly) int tapCount;
+
+/// The current phase the touch is in.
+@property (nonatomic, readonly) SPTouchPhase phase;
+
+/// The display object at which the touch occurred.
+@property (nonatomic, readonly) SPDisplayObject *target;
+
+@end
diff --git a/libs/sparrow/src/Classes/SPTouch.m b/libs/sparrow/src/Classes/SPTouch.m
new file mode 100644 (file)
index 0000000..7e04083
--- /dev/null
@@ -0,0 +1,109 @@
+//
+//  SPTouch.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 01.05.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import "SPTouch.h"
+#import "SPTouch_Internal.h"
+#import "SPDisplayObject.h"
+#import "SPPoint.h"
+
+@implementation SPTouch
+
+@synthesize timestamp = mTimestamp;
+@synthesize globalX = mGlobalX;
+@synthesize globalY = mGlobalY;
+@synthesize previousGlobalX = mPreviousGlobalX;
+@synthesize previousGlobalY = mPreviousGlobalY;
+@synthesize tapCount = mTapCount;
+@synthesize phase = mPhase;
+@synthesize target = mTarget;
+
+- (id)init
+{
+    return [super init];
+}
+
+- (SPPoint*)locationInSpace:(SPDisplayObject*)space
+{
+    SPPoint *point = [SPPoint pointWithX:mGlobalX y:mGlobalY];
+    SPMatrix *transformationMatrix = [mTarget.root transformationMatrixToSpace:space];
+    return [transformationMatrix transformPoint:point];
+}
+
+- (SPPoint*)previousLocationInSpace:(SPDisplayObject*)space
+{
+    SPPoint *point = [SPPoint pointWithX:mPreviousGlobalX y:mPreviousGlobalY];
+    SPMatrix *transformationMatrix = [mTarget.root transformationMatrixToSpace:space];
+    return [transformationMatrix transformPoint:point];
+}
+
+- (void)dealloc
+{
+    [mTarget release];
+    [super dealloc];
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPTouch (Internal)
+
+- (void)setTimestamp:(double)timestamp
+{
+    mTimestamp = timestamp;
+}
+
+- (void)setGlobalX:(float)x
+{
+    mGlobalX = x;
+}
+
+- (void)setGlobalY:(float)y
+{
+    mGlobalY = y;
+}
+
+- (void)setPreviousGlobalX:(float)x
+{
+    mPreviousGlobalX = x;
+}
+
+- (void)setPreviousGlobalY:(float)y
+{
+    mPreviousGlobalY = y;
+}
+
+- (void)setTapCount:(int)tapCount
+{
+    mTapCount = tapCount;
+}
+
+- (void)setPhase:(SPTouchPhase)phase
+{
+    mPhase = phase;
+}
+
+- (void)setTarget:(SPDisplayObject*)target
+{
+    if (target != mTarget)
+    {    
+        [mTarget release];
+        mTarget = [target retain];
+    }
+}
+
++ (SPTouch*)touch
+{
+    return [[[SPTouch alloc] init] autorelease];
+}
+
+@end
+
diff --git a/libs/sparrow/src/Classes/SPTouchEvent.h b/libs/sparrow/src/Classes/SPTouchEvent.h
new file mode 100644 (file)
index 0000000..5ca2225
--- /dev/null
@@ -0,0 +1,98 @@
+//
+//  SPTouchEvent.h
+//  Sparrow
+//
+//  Created by Daniel Sperl on 02.05.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+#import "SPEvent.h"
+#import "SPTouch.h"
+
+@class SPDisplayObject;
+
+#define SP_EVENT_TYPE_TOUCH @"touch"
+
+/** ------------------------------------------------------------------------------------------------
+
+ When one or more fingers touch the screen, move around or are raised, an SPTouchEvent is triggered.
+ The event contains a list of all touches that are currently present. Since you are normally only
+ interested in those touches that occurred on top of certain objects, you can query the event for
+ touches with certain targets.
+ In this context, the target of a touch is not only the object that was touched (e.g. an SPImage), 
+ but also each of its parents - e.g. the container that holds that image.
+ Here is an example of how to react on touch events at 'self', which could be a subclass of SPSprite:
+
+       // e.g. in 'init'
+       [self addEventListener:@selector(onTouch:) atObject:self forType:SP_EVENT_TYPE_TOUCH];
+       
+       // the corresponding listener:
+       - (void)onTouch:(SPTouchEvent*)event
+       {
+           // query all touches that are currently moving on top of 'self'
+           NSArray *touches = [[event touchesWithTarget:self andPhase:SPTouchPhaseMoved] allObjects];
+       
+           if (touches.count == 1)
+           {
+               // one finger touching
+               SPTouch *touch = [touches objectAtIndex:0];
+               SPPoint *currentPos = [touch locationInSpace:self.parent];
+               SPPoint *previousPos = [touch previousLocationInSpace:self.parent];
+               // ...
+           }
+           else if (touches.count >= 2)
+           {
+               // two fingers touching
+               // ...
+           }
+       }
+
+------------------------------------------------------------------------------------------------- */ 
+@interface SPTouchEvent : SPEvent
+{
+  @private
+    NSSet *mTouches;    
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Creates a touch with a set of touches. _Designated Initializer_.
+- (id)initWithType:(NSString*)type bubbles:(BOOL)bubbles touches:(NSSet*)touches;
+
+/// Creates a touch with a set of touches.
+- (id)initWithType:(NSString*)type touches:(NSSet*)touches;
+
+/// Factory method.
++ (SPTouchEvent*)eventWithType:(NSString*)type touches:(NSSet*)touches;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Gets a set of SPTouch objects that originated over a certain target.
+- (NSSet*)touchesWithTarget:(SPDisplayObject*)target;
+
+/// Gets a set of SPTouch objects that originated over a certain target and are in a certain phase.
+- (NSSet*)touchesWithTarget:(SPDisplayObject*)target andPhase:(SPTouchPhase)phase;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// All touches that are currently happening.
+@property (nonatomic, readonly) NSSet *touches;
+
+/// The time the event occurred (in seconds since application launch).
+@property (nonatomic, readonly) double timestamp;
+
+@end
diff --git a/libs/sparrow/src/Classes/SPTouchEvent.m b/libs/sparrow/src/Classes/SPTouchEvent.m
new file mode 100644 (file)
index 0000000..5d895a5
--- /dev/null
@@ -0,0 +1,92 @@
+//
+//  SPTouchEvent.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 02.05.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import "SPTouchEvent.h"
+#import "SPDisplayObject.h"
+#import "SPDisplayObjectContainer.h"
+#import "SPEvent_Internal.h"
+
+@implementation SPTouchEvent
+
+@synthesize touches = mTouches;
+
+- (id)initWithType:(NSString*)type bubbles:(BOOL)bubbles touches:(NSSet*)touches
+{   
+    if (self = [super initWithType:type bubbles:bubbles])
+    {        
+        mTouches = [touches retain];
+    }
+    return self;
+}
+
+- (id)initWithType:(NSString*)type touches:(NSSet*)touches
+{   
+    return [self initWithType:type bubbles:YES touches:touches];
+}
+
+- (id)initWithType:(NSString*)type bubbles:(BOOL)bubbles
+{
+    return [self initWithType:type bubbles:bubbles touches:[NSSet set]];
+}
+
+- (SPEvent*)clone
+{
+    return [SPTouchEvent eventWithType:self.type touches:self.touches];
+}
+
+- (double)timestamp
+{
+    return [[mTouches anyObject] timestamp];    
+}
+
+- (NSSet*)touchesWithTarget:(SPDisplayObject*)target
+{
+    NSMutableSet *touchesFound = [NSMutableSet set];
+    for (SPTouch *touch in mTouches)
+    {
+        if ([target isEqual:touch.target] ||
+            ([target isKindOfClass:[SPDisplayObjectContainer class]] &&
+             [(SPDisplayObjectContainer*)target containsChild:touch.target]))
+        {
+            [touchesFound addObject: touch];
+        }
+    }    
+    return touchesFound;    
+}
+
+- (NSSet*)touchesWithTarget:(SPDisplayObject*)target andPhase:(SPTouchPhase)phase
+{
+    NSMutableSet *touchesFound = [NSMutableSet set];
+    for (SPTouch *touch in mTouches)
+    {
+        if (touch.phase == phase &&
+            ([target isEqual:touch.target] || 
+             ([target isKindOfClass:[SPDisplayObjectContainer class]] &&
+              [(SPDisplayObjectContainer*)target containsChild:touch.target])))
+        {
+            [touchesFound addObject: touch];
+        }
+    }    
+    return touchesFound;    
+}
+
+- (void)dealloc
+{
+    [mTouches release];
+    [super dealloc];
+}
+
++ (SPTouchEvent*)eventWithType:(NSString*)type touches:(NSSet*)touches
+{
+    return [[[SPTouchEvent alloc] initWithType:type touches:touches] autorelease];
+}
+
+@end
diff --git a/libs/sparrow/src/Classes/SPTouchProcessor.h b/libs/sparrow/src/Classes/SPTouchProcessor.h
new file mode 100644 (file)
index 0000000..ed3ac24
--- /dev/null
@@ -0,0 +1,52 @@
+//
+//  SPTouchProcessor.h
+//  Sparrow
+//
+//  Created by Daniel Sperl on 03.05.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+
+@class SPDisplayObjectContainer;
+
+/** ------------------------------------------------------------------------------------------------
+
+ The SPTouchProcesser processes raw touch information and dispatches it on display objects.
+ _This is an internal class. You do not have to use it manually._
+
+------------------------------------------------------------------------------------------------- */
+
+@interface SPTouchProcessor : NSObject 
+{
+  @private
+    SPDisplayObjectContainer *mRoot;
+    NSMutableSet *mCurrentTouches;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a touch processor with a certain root object.
+- (id)initWithRoot:(SPDisplayObjectContainer*)root;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// @name Processes raw touches and dispatches events on the touched display objects.
+- (void)processTouches:(NSSet*)touches;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The root display container to check for touched targets.
+@property (nonatomic, assign) SPDisplayObjectContainer *root;
+
+@end
diff --git a/libs/sparrow/src/Classes/SPTouchProcessor.m b/libs/sparrow/src/Classes/SPTouchProcessor.m
new file mode 100644 (file)
index 0000000..b5e0907
--- /dev/null
@@ -0,0 +1,121 @@
+//
+//  SPTouchProcessor.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 03.05.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import "SPTouchProcessor.h"
+#import "SPMacros.h"
+#import "SPTouchEvent.h"
+#import "SPTouch.h"
+#import "SPTouch_Internal.h"
+#import "SPPoint.h"
+#import "SPMatrix.h"
+#import "SPDisplayObjectContainer.h"
+
+#define MULTITAP_TIME 0.25f
+#define MULTITAP_DIST 25
+
+@implementation SPTouchProcessor
+
+@synthesize root = mRoot;
+
+- (id)initWithRoot:(SPDisplayObjectContainer*)root
+{
+    if (self = [super init])
+    {
+        mRoot = root;
+        mCurrentTouches = [[NSMutableSet alloc] initWithCapacity:2];
+    }
+    return self;
+}
+
+- (id)init
+{    
+    return [self initWithRoot:nil];
+}
+
+- (void)processTouches:(NSSet*)touches
+{
+    SP_CREATE_POOL(pool);    
+    
+    NSMutableSet *processedTouches = [[NSMutableSet alloc] init];
+    
+    // process new touches
+    for (SPTouch *touch in touches)
+    {
+        SPTouch *currentTouch = nil;
+        
+        for (SPTouch *existingTouch in mCurrentTouches)
+        {
+            if ((existingTouch.globalX == touch.previousGlobalX &&
+                 existingTouch.globalY == touch.previousGlobalY) ||
+                (existingTouch.globalX == touch.globalX &&
+                 existingTouch.globalY == touch.globalY))
+            {
+                // existing touch; update values
+                existingTouch.timestamp = touch.timestamp;
+                existingTouch.previousGlobalX = touch.previousGlobalX;
+                existingTouch.previousGlobalY = touch.previousGlobalY;
+                existingTouch.globalX = touch.globalX;
+                existingTouch.globalY = touch.globalY;
+                existingTouch.phase = touch.phase;
+                existingTouch.tapCount = touch.tapCount;
+                
+                if (!existingTouch.target.stage)
+                {
+                    // target could have been removed from stage -> find new target in that case
+                    SPPoint *touchPosition = [SPPoint pointWithX:touch.globalX y:touch.globalY];
+                    existingTouch.target = [mRoot hitTestPoint:touchPosition forTouch:YES];       
+                }
+                
+                currentTouch = existingTouch;
+                break;
+            }
+        }
+        
+        if (!currentTouch)
+        {
+            // new touch!
+            currentTouch = [SPTouch touch];
+            currentTouch.timestamp = touch.timestamp;
+            currentTouch.globalX = touch.globalX;
+            currentTouch.globalY = touch.globalY;
+            currentTouch.previousGlobalX = touch.previousGlobalX;
+            currentTouch.previousGlobalY = touch.previousGlobalY;
+            currentTouch.phase = touch.phase;
+            currentTouch.tapCount = touch.tapCount;
+            SPPoint *touchPosition = [SPPoint pointWithX:touch.globalX y:touch.globalY];
+            currentTouch.target = [mRoot hitTestPoint:touchPosition forTouch:YES];
+        }
+        
+        [processedTouches addObject:currentTouch];
+    }
+    
+    // dispatch events         
+    for (SPTouch *touch in processedTouches)
+    {       
+        SPTouchEvent *touchEvent = [[SPTouchEvent alloc] initWithType:SP_EVENT_TYPE_TOUCH 
+                                                              touches:processedTouches];
+        [touch.target dispatchEvent:touchEvent];
+        [touchEvent release];
+    }
+    
+    [mCurrentTouches release];    
+    mCurrentTouches = processedTouches;    
+    
+    SP_RELEASE_POOL(pool);
+}
+
+- (void) dealloc
+{
+    [mCurrentTouches release];
+    [super dealloc];
+}
+
+@end
diff --git a/libs/sparrow/src/Classes/SPTouch_Internal.h b/libs/sparrow/src/Classes/SPTouch_Internal.h
new file mode 100644 (file)
index 0000000..b36fe30
--- /dev/null
@@ -0,0 +1,28 @@
+//
+//  SPTouch_Internal.h
+//  Sparrow
+//
+//  Created by Daniel Sperl on 03.05.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+#import "SPTouch.h"
+
+@interface SPTouch (Internal)
+
+- (void)setTimestamp:(double)timestamp;
+- (void)setGlobalX:(float)x;
+- (void)setGlobalY:(float)y;
+- (void)setPreviousGlobalX:(float)x;
+- (void)setPreviousGlobalY:(float)y;
+- (void)setTapCount:(int)tapCount;
+- (void)setPhase:(SPTouchPhase)phase;
+- (void)setTarget:(SPDisplayObject*)target;
+
++ (SPTouch*)touch;
+
+@end
diff --git a/libs/sparrow/src/Classes/SPTransitions.h b/libs/sparrow/src/Classes/SPTransitions.h
new file mode 100644 (file)
index 0000000..b720b7f
--- /dev/null
@@ -0,0 +1,75 @@
+//
+//  SPTransitions.h
+//  Sparrow
+//
+//  Created by Daniel Sperl on 11.05.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+
+#define SP_TRANSITION_LINEAR                @"linear"
+#define SP_TRANSITION_RANDOMIZE             @"randomize"
+
+#define SP_TRANSITION_EASE_IN               @"easeIn"
+#define SP_TRANSITION_EASE_OUT              @"easeOut"
+#define SP_TRANSITION_EASE_IN_OUT           @"easeInOut"
+#define SP_TRANSITION_EASE_OUT_IN           @"easeOutIn"
+
+#define SP_TRANSITION_EASE_IN_BACK          @"easeInBack"
+#define SP_TRANSITION_EASE_OUT_BACK         @"easeOutBack"
+#define SP_TRANSITION_EASE_IN_OUT_BACK      @"easeInOutBack"
+#define SP_TRANSITION_EASE_OUT_IN_BACK      @"easeOutInBack"
+
+#define SP_TRANSITION_EASE_IN_ELASTIC       @"easeInElastic"
+#define SP_TRANSITION_EASE_OUT_ELASTIC      @"easeOutElastic"
+#define SP_TRANSITION_EASE_IN_OUT_ELASTIC   @"easeInOutElastic"
+#define SP_TRANSITION_EASE_OUT_IN_ELASTIC   @"easeOutInElastic"  
+
+#define SP_TRANSITION_EASE_IN_BOUNCE        @"easeInBounce"
+#define SP_TRANSITION_EASE_OUT_BOUNCE       @"easeOutBounce"
+#define SP_TRANSITION_EASE_IN_OUT_BOUNCE    @"easeInOutBounce"
+#define SP_TRANSITION_EASE_OUT_IN_BOUNCE    @"easeOutInBounce" 
+
+/** ------------------------------------------------------------------------------------------------
+ The SPTransitions class contains static methods that define easing functions. Those functions
+ will be used by SPTween to execute animations.
+ Find a visual representation of the transitions at this URL: <br/>  
+ http://www.sparrow-framework.org/wp-content/uploads/2010/06/transitions.png
+  
+ You can define your own transitions by extending this class. The name of the method you declare 
+ acts as the key that is used to identify the transition when you create the tween.
+------------------------------------------------------------------------------------------------- */
+@interface SPTransitions : NSObject 
+
++ (float)linear:(float)ratio;
++ (float)randomize:(float)ratio;
+
++ (float)easeIn:(float)ratio;
++ (float)easeOut:(float)ratio;
++ (float)easeInOut:(float)ratio;
++ (float)easeOutIn:(float)ratio;
+
++ (float)easeInBack:(float)ratio;
++ (float)easeOutBack:(float)ratio;
++ (float)easeInOutBack:(float)ratio;
++ (float)easeOutInBack:(float)ratio;
+
++ (float)easeInElastic:(float)ratio;
++ (float)easeOutElastic:(float)ratio;
++ (float)easeInOutElastic:(float)ratio;
++ (float)easeOutInElastic:(float)ratio;
+
++ (float)easeInBounce:(float)ratio;
++ (float)easeOutBounce:(float)ratio;
++ (float)easeInOutBounce:(float)ratio;
++ (float)easeOutInBounce:(float)ratio;
+
+@end
diff --git a/libs/sparrow/src/Classes/SPTransitions.m b/libs/sparrow/src/Classes/SPTransitions.m
new file mode 100644 (file)
index 0000000..f1e6fcc
--- /dev/null
@@ -0,0 +1,171 @@
+//
+//  SPTransitions.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 11.05.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+//  The easing functions were thankfully taken from http://dojotoolkit.org
+//                                              and http://www.robertpenner.com/easing
+//
+
+#import "SPTransitions.h"
+#import "SPMacros.h"
+#import "SPUtils.h"
+
+@implementation SPTransitions
+
+- (id)init
+{
+    [self release];
+    [NSException raise:NSGenericException format:@"Static class - do not initialize!"];        
+    return nil;
+}
+
++ (float)linear:(float)ratio
+{
+    return ratio;
+}
+
++ (float)randomize:(float)ratio
+{
+    return [SPUtils randomFloat];
+}
+
++ (float)easeIn:(float)ratio
+{
+    return ratio * ratio * ratio;
+}
+
++ (float)easeOut:(float)ratio
+{
+    float invRatio = ratio - 1.0f;
+    return invRatio * invRatio * invRatio + 1.0f;
+}
+
++ (float)easeInOut:(float)ratio
+{
+    if (ratio < 0.5f) return 0.5f * [SPTransitions easeIn:ratio*2.0f];
+    else              return 0.5f * [SPTransitions easeOut:(ratio-0.5f)*2.0f] + 0.5f;
+}
+
++ (float)easeOutIn:(float)ratio
+{
+    if (ratio < 0.5f) return 0.5f * [SPTransitions easeOut:ratio*2.0f];
+    else              return 0.5f * [SPTransitions easeIn:(ratio-0.5f)*2.0f] + 0.5f;
+}
+
++ (float)easeInBack:(float)ratio
+{
+    float s = 1.70158f;
+    return powf(ratio, 2.0f) * ((s + 1.0f)*ratio - s);    
+}
+
++ (float)easeOutBack:(float)ratio
+{
+    float invRatio = ratio - 1.0f;
+    float s = 1.70158f;
+    return powf(invRatio, 2.0f) * ((s + 1.0f)*invRatio + s) + 1.0f;    
+}
+
++ (float)easeInOutBack:(float)ratio
+{
+    if (ratio < 0.5f) return 0.5f * [SPTransitions easeInBack:ratio*2.0f];
+    else              return 0.5f * [SPTransitions easeOutBack:(ratio-0.5f)*2.0f] + 0.5f;
+}
+
++ (float)easeOutInBack:(float)ratio
+{
+    if (ratio < 0.5f) return 0.5f * [SPTransitions easeOutBack:ratio*2.0f];
+    else              return 0.5f * [SPTransitions easeInBack:(ratio-0.5f)*2.0f] + 0.5f;
+}
+
++ (float)easeInElastic:(float)ratio
+{
+    if (ratio == 0.0f || ratio == 1.0f) return ratio;
+    else
+    {
+        float p = 0.3f;
+        float s = p / 4.0f;
+        float invRatio = ratio - 1.0f;
+        return -1.0f * powf(2.0f, 10.0f*invRatio) * sinf((invRatio-s)*TWO_PI/p);        
+    }
+}
+
++ (float)easeOutElastic:(float)ratio
+{
+    if (ratio == 0.0f || ratio == 1.0f) return ratio;
+    else 
+    {
+        float p = 0.3f;
+        float s = p / 4.0f;
+        return powf(2.0f, -10.0f*ratio) * sinf((ratio-s)*TWO_PI/p) + 1.0f;
+    }
+}
+
++ (float)easeInOutElastic:(float)ratio
+{
+    if (ratio < 0.5f) return 0.5f * [SPTransitions easeInElastic:ratio*2.0f];
+    else              return 0.5f * [SPTransitions easeOutElastic:(ratio-0.5f)*2.0f] + 0.5f;    
+}
+
++ (float)easeOutInElastic:(float)ratio
+{
+    if (ratio < 0.5f) return 0.5f * [SPTransitions easeOutElastic:ratio*2.0f];
+    else              return 0.5f * [SPTransitions easeInElastic:(ratio-0.5f)*2.0f] + 0.5f;    
+}
+
++ (float)easeInBounce:(float)ratio
+{
+    return 1.0f - [SPTransitions easeOutBounce:1.0f - ratio];
+}
+
++ (float)easeOutBounce:(float)ratio
+{
+    float s = 7.5625f;
+    float p = 2.75f;
+    float l;
+    if (ratio < (1.0f/p))
+    {
+        l = s * powf(ratio, 2.0f);
+    }
+    else
+    {    
+        if (ratio < (2.0f/p))
+        {
+            ratio -= 1.5f/p;
+            l = s * powf(ratio, 2.0f) + 0.75f;
+        }
+        else
+        {
+            if (ratio < 2.5f/p)
+            {
+                ratio -= 2.25f/p;
+                l = s * powf(ratio, 2.0f) + 0.9375f;
+            }
+            else
+            {
+                ratio -= 2.625f/p;
+                l = s * powf(ratio, 2.0f) + 0.984375f;
+            }
+        }
+    }
+    return l;
+}
+
++ (float)easeInOutBounce:(float)ratio
+{
+    if (ratio < 0.5f) return 0.5f * [SPTransitions easeInBounce:ratio*2.0f];
+    else              return 0.5f * [SPTransitions easeOutBounce:(ratio-0.5f)*2.0f] + 0.5f;  
+}
+
++ (float)easeOutInBounce:(float)ratio
+{
+    if (ratio < 0.5f) return 0.5f * [SPTransitions easeOutBounce:ratio*2.0f];
+    else              return 0.5f * [SPTransitions easeInBounce:(ratio-0.5f)*2.0f] + 0.5f;  
+}
+
+@end
diff --git a/libs/sparrow/src/Classes/SPTween.h b/libs/sparrow/src/Classes/SPTween.h
new file mode 100644 (file)
index 0000000..54f6063
--- /dev/null
@@ -0,0 +1,124 @@
+//
+//  SPTween.h
+//  Sparrow
+//
+//  Created by Daniel Sperl on 09.05.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+#import "SPEventDispatcher.h"
+#import "SPAnimatable.h"
+#import "SPTransitions.h"
+
+typedef enum 
+{
+    SPLoopTypeNone,
+    SPLoopTypeRepeat,
+    SPLoopTypeReverse
+} SPLoopType;
+
+#define SP_EVENT_TYPE_TWEEN_STARTED   @"tweenStarted"
+#define SP_EVENT_TYPE_TWEEN_UPDATED   @"tweenUpdated"
+#define SP_EVENT_TYPE_TWEEN_COMPLETED @"tweenCompleted"
+
+/** ------------------------------------------------------------------------------------------------
+ An SPTween animates numeric properties of objects. It uses different transition functions to give
+ the animations various styles.
+ The primary use of this class is to do standard animations like movement, fading, rotation, etc.
+ But there are no limits on what to animate; as long as the property you want to animate is numeric
+ (`int`, `float`, `double`), the tween can handle it. For a list of available Transition types,
+ see `SPTransitions`. 
+ Here is an example of a tween that moves an object to the right, rotates it, and fades it out:
+       SPTween *tween = [SPTween tweenWithTarget:object time:2.0 transition:SP_TRANSITION_EASE_IN_OUT];
+       [tween animateProperty:@"x" targetValue:object.x + 50];
+       [tween animateProperty:@"rotation" targetValue:object.rotation + SP_D2R(45)];
+       [tween animateProperty:@"alpha" targetValue:0.0f];
+       [self.stage.juggler addObject:tween];
+ Note that the object is added to a juggler at the end. A tween will only be executed if its
+ `advanceTime:` method is executed regularly - the juggler will do that for us, and will release
+ the tween when it is finished.
+ Tweens dispatch events in certain phases of their life time:
+ * `SP_EVENT_TYPE_TWEEN_STARTED`: Dispatched once when the tween starts
+ * `SP_EVENT_TYPE_TWEEN_UPDATED`: Dispatched every time it is advanced
+ * `SP_EVENT_TYPE_TWEEN_COMPLETED`: Dispatched when it reaches its target value (except when it loops).
+ Tweens can loop in two ways:
+ * `SPLoopTypeRepeat`: Starts the animation from the beginning when it's finished.
+ * `SPLoopTypeReverse`: Reverses the animation when it's finished, tweening back to the start value.
+------------------------------------------------------------------------------------------------- */
+
+@interface SPTween : SPEventDispatcher <SPAnimatable>
+{
+  @private
+    id mTarget;    
+    SEL mTransition;
+    IMP mTransitionFunc;    
+    NSMutableArray *mProperties;
+    
+    double mTotalTime;
+    double mCurrentTime;
+    double mDelay;
+    
+    SPLoopType mLoop;
+    BOOL mInvertTransition;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a tween with a target, duration (in seconds) and a transition function. 
+/// _Designated Initializer_.
+- (id)initWithTarget:(id)target time:(double)time transition:(NSString*)transition;
+
+/// Initializes a tween with a target, a time (in seconds) and a linear transition 
+/// (`SP_TRANSITION_LINEAR`).
+- (id)initWithTarget:(id)target time:(double)time;
+
+/// Factory method.
++ (SPTween *)tweenWithTarget:(id)target time:(double)time transition:(NSString *)transition;
+
+/// Factory method.
++ (SPTween *)tweenWithTarget:(id)target time:(double)time;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Animates the property of an object to a target value. You can call this method multiple times
+/// on one tween.
+- (void)animateProperty:(NSString*)property targetValue:(float)value;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The target object that is animated.
+@property (nonatomic, readonly) id target;
+
+/// The transition method used for the animation.
+@property (nonatomic, readonly) NSString *transition;
+
+/// The total time the tween will take (in seconds).
+@property (nonatomic, readonly) double time;
+
+/// The delay before the tween is started.
+@property (nonatomic, assign)   double delay;
+
+/// The type of loop. (Default: SPLoopTypeNone)
+@property (nonatomic, assign)   SPLoopType loop;
+
+@end
diff --git a/libs/sparrow/src/Classes/SPTween.m b/libs/sparrow/src/Classes/SPTween.m
new file mode 100644 (file)
index 0000000..c345c82
--- /dev/null
@@ -0,0 +1,169 @@
+//
+//  SPTween.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 09.05.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import "SPTween.h"
+#import "SPTransitions.h"
+#import "SPTweenedProperty.h"
+#import "SPMacros.h"
+
+#define TRANS_SUFFIX  @":"
+
+typedef float (*FnPtrTransition) (id, SEL, float);
+
+@implementation SPTween
+
+@synthesize time = mTotalTime;
+@synthesize delay = mDelay;
+@synthesize target = mTarget;
+@synthesize loop = mLoop;
+
+- (id)initWithTarget:(id)target time:(double)time transition:(NSString*)transition
+{
+    if (self = [super init])
+    {
+        mTarget = [target retain];
+        mTotalTime = MAX(0.0001, time); // zero is not allowed
+        mCurrentTime = 0;
+        mDelay = 0;
+        mProperties = [[NSMutableArray alloc] init];        
+        mLoop = SPLoopTypeNone;
+        mInvertTransition = NO;
+        
+        // create function pointer for transition
+        NSString *transMethod = [transition stringByAppendingString:TRANS_SUFFIX];
+        mTransition = NSSelectorFromString(transMethod);    
+        if (![SPTransitions respondsToSelector:mTransition])
+            [NSException raise:SP_EXC_INVALID_OPERATION 
+                        format:@"transition not found: '%@'", transition];
+        mTransitionFunc = [SPTransitions methodForSelector:mTransition];
+    }
+    return self;
+}
+
+- (id)initWithTarget:(id)target time:(double)time
+{
+    return [self initWithTarget:target time:time transition:SP_TRANSITION_LINEAR];
+}
+
+- (void)animateProperty:(NSString*)property targetValue:(float)value
+{    
+    if (!mTarget) return; // tweening nil just does nothing.
+    
+    SPTweenedProperty *tweenedProp = [[SPTweenedProperty alloc] 
+        initWithTarget:mTarget name:property endValue:value];
+    [mProperties addObject:tweenedProp];
+    [tweenedProp release];
+}
+
+- (void)advanceTime:(double)seconds
+{
+    if (seconds == 0.0) return; // nothing to do
+    
+    double previousTime = mCurrentTime;    
+    mCurrentTime = MIN(mTotalTime, mCurrentTime + seconds);
+
+    if (mCurrentTime < 0 || previousTime >= mTotalTime) return;
+
+    if (previousTime <= 0 && mCurrentTime >= 0 &&
+        [self hasEventListenerForType:SP_EVENT_TYPE_TWEEN_STARTED])
+    {
+        SPEvent *event = [[SPEvent alloc] initWithType:SP_EVENT_TYPE_TWEEN_STARTED];        
+        [self dispatchEvent:event];
+        [event release];        
+    }   
+    
+    float ratio = mCurrentTime / mTotalTime;
+    FnPtrTransition transFunc = (FnPtrTransition) mTransitionFunc;
+    Class transClass = [SPTransitions class];
+    
+    for (SPTweenedProperty *prop in mProperties)
+    {        
+        if (previousTime <= 0 && mCurrentTime >= 0) 
+            prop.startValue = prop.currentValue;
+
+        float transitionValue = mInvertTransition ? 
+            1.0f - transFunc(transClass, mTransition, 1.0f - ratio) :
+            transFunc(transClass, mTransition, ratio);        
+        
+        prop.currentValue = prop.startValue + prop.delta * transitionValue;
+    }
+   
+    if ([self hasEventListenerForType:SP_EVENT_TYPE_TWEEN_UPDATED])
+    {
+        SPEvent *event = [[SPEvent alloc] initWithType:SP_EVENT_TYPE_TWEEN_UPDATED];
+        [self dispatchEvent:event];    
+        [event release];
+    }
+    
+    if (previousTime < mTotalTime && mCurrentTime >= mTotalTime)
+    {
+               if (mLoop == SPLoopTypeRepeat)
+               {
+                       for (SPTweenedProperty *prop in mProperties)
+                               prop.currentValue = prop.startValue;
+
+                       mCurrentTime = 0;
+               }
+               else if (mLoop == SPLoopTypeReverse)
+               {
+                       for (SPTweenedProperty *prop in mProperties)
+            {
+                prop.currentValue = prop.endValue; // since tweens not necessarily end with endValue
+                prop.endValue = prop.startValue;
+                mInvertTransition = !mInvertTransition;
+            }
+
+                       mCurrentTime = 0;
+               }        
+        else if ([self hasEventListenerForType:SP_EVENT_TYPE_TWEEN_COMPLETED])
+        {
+            SPEvent *event = [[SPEvent alloc] initWithType:SP_EVENT_TYPE_TWEEN_COMPLETED];
+            [self dispatchEvent:event];
+            [event release];
+        }
+    }
+}
+
+- (NSString*)transition
+{
+    NSString *selectorName = NSStringFromSelector(mTransition);
+    return [selectorName substringToIndex:selectorName.length - [TRANS_SUFFIX length]];
+}
+
+- (BOOL)isComplete
+{
+    return mCurrentTime >= mTotalTime && mLoop == SPLoopTypeNone;
+}
+
+- (void)setDelay:(double)delay
+{
+    mCurrentTime = mCurrentTime + mDelay - delay;
+    mDelay = delay;
+}
+
++ (SPTween*)tweenWithTarget:(id)target time:(double)time transition:(NSString*)transition
+{
+    return [[[SPTween alloc] initWithTarget:target time:time transition:transition] autorelease];
+}
+
++ (SPTween*)tweenWithTarget:(id)target time:(double)time
+{
+    return [[[SPTween alloc] initWithTarget:target time:time] autorelease];
+}
+
+- (void)dealloc
+{
+    [mTarget release];
+    [mProperties release];
+    [super dealloc];
+}
+
+@end
diff --git a/libs/sparrow/src/Classes/SPTweenedProperty.h b/libs/sparrow/src/Classes/SPTweenedProperty.h
new file mode 100644 (file)
index 0000000..f67d469
--- /dev/null
@@ -0,0 +1,62 @@
+//
+//  SPTweenedProperty.h
+//  Sparrow
+//
+//  Created by Daniel Sperl on 17.10.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+
+/** ------------------------------------------------------------------------------------------------
+ An SPTweenedProperty stores the information about the tweening of a single property of an object.
+ Its `currentValue` property updates the specified property of the target object.
+ _This is an internal class. You do not have to use it manually._
+------------------------------------------------------------------------------------------------- */
+
+@interface SPTweenedProperty : NSObject
+{
+  @private
+    id  mTarget;
+    
+    SEL mGetter;
+    IMP mGetterFunc;    
+    SEL mSetter;    
+    IMP mSetterFunc;
+
+    float mStartValue;
+    float mEndValue;
+    char  mNumericType;
+}
+
+/// ------------------
+/// @name Initializers
+/// ------------------
+
+/// Initializes a tween property on a certain target. The start value will be zero.
+- (id)initWithTarget:(id)target name:(NSString *)name endValue:(float)endValue;
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// The start value of the tween.
+@property (nonatomic, assign) float startValue;
+
+/// The current value of the tween. Setting this property updates the target property.
+@property (nonatomic, assign) float currentValue;
+
+/// The end value of the tween.
+@property (nonatomic, assign) float endValue;
+
+/// The animation delta (endValue - startValue)
+@property (nonatomic, readonly) float delta;
+
+
+@end
\ No newline at end of file
diff --git a/libs/sparrow/src/Classes/SPTweenedProperty.m b/libs/sparrow/src/Classes/SPTweenedProperty.m
new file mode 100644 (file)
index 0000000..ebe32f1
--- /dev/null
@@ -0,0 +1,110 @@
+//
+//  SPTweenedProperty.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 17.10.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import "SPTweenedProperty.h"
+#import "SPMacros.h"
+
+typedef float  (*FnPtrGetterF) (id, SEL);
+typedef double (*FnPtrGetterD) (id, SEL);
+typedef int    (*FnPtrGetterI) (id, SEL);
+
+typedef void (*FnPtrSetterF) (id, SEL, float);
+typedef void (*FnPtrSetterD) (id, SEL, double);
+typedef void (*FnPtrSetterI) (id, SEL, int);
+@implementation SPTweenedProperty
+
+@synthesize startValue = mStartValue;
+@synthesize endValue = mEndValue;
+
+- (id)initWithTarget:(id)target name:(NSString *)name endValue:(float)endValue;
+{
+    if (self = [super init])
+    {
+        mTarget = [target retain];        
+        mEndValue = endValue;
+        
+        mGetter = NSSelectorFromString(name);
+        mSetter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", 
+                                        [[name substringToIndex:1] uppercaseString], 
+                                        [name substringFromIndex:1]]);
+        
+        if (![mTarget respondsToSelector:mGetter] || ![mTarget respondsToSelector:mSetter])
+            [NSException raise:SP_EXC_INVALID_OPERATION format:@"property not found or readonly: '%@'", 
+             name];    
+        
+        // query argument type
+        NSMethodSignature *sig = [mTarget methodSignatureForSelector:mGetter];
+        mNumericType = *[sig methodReturnType];    
+        if (mNumericType != 'f' && mNumericType != 'i' && mNumericType != 'd')
+            [NSException raise:SP_EXC_INVALID_OPERATION format:@"property not numeric: '%@'", name];
+        
+        mGetterFunc = [mTarget methodForSelector:mGetter];
+        mSetterFunc = [mTarget methodForSelector:mSetter];       
+    }
+    return self;
+}
+
+- (id)init
+{
+    return [self initWithTarget:nil name:nil endValue:0.0f];
+}
+
+- (void)setCurrentValue:(float)value
+{
+    if (mNumericType == 'f')
+    {
+        FnPtrSetterF func = (FnPtrSetterF)mSetterFunc;
+        func(mTarget, mSetter, value);
+    }        
+    else if (mNumericType == 'd')
+    {
+        FnPtrSetterD func = (FnPtrSetterD)mSetterFunc;
+        func(mTarget, mSetter, (double)value);
+    }        
+    else
+    {
+        FnPtrSetterI func = (FnPtrSetterI)mSetterFunc;
+        func(mTarget, mSetter, (int)(value+0.5f));
+    }        
+}
+
+- (float)currentValue
+{
+    if (mNumericType == 'f')
+    {
+        FnPtrGetterF func = (FnPtrGetterF)mGetterFunc;
+        return func(mTarget, mGetter);
+    }
+    else if (mNumericType == 'd')
+    {
+        FnPtrGetterD func = (FnPtrGetterD)mGetterFunc;
+        return func(mTarget, mGetter);
+    }
+    else 
+    {
+        FnPtrGetterI func = (FnPtrGetterI)mGetterFunc;
+        return func(mTarget, mGetter);
+    }
+}
+
+- (float)delta
+{
+    return mEndValue - mStartValue;
+}
+
+- (void)dealloc
+{
+    [mTarget release];
+    [super dealloc];
+}
+
+@end
diff --git a/libs/sparrow/src/Classes/SPUtils.h b/libs/sparrow/src/Classes/SPUtils.h
new file mode 100644 (file)
index 0000000..caf8fcc
--- /dev/null
@@ -0,0 +1,27 @@
+//
+//  SPUtils.h
+//  Sparrow
+//
+//  Created by Daniel Sperl on 04.01.11.
+//  Copyright 2011 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Foundation/Foundation.h>
+
+/// The SPUtils class contains utility methods for different purposes.
+
+@interface SPUtils : NSObject 
+
+/// Finds the next power of two equal to or above the specified number.
++ (int)nextPowerOfTwo:(int)number;
+
+/// Returns a random integer number between `minValue` (inclusive) and `maxValue` (exclusive).
++ (int)randomIntBetween:(int)minValue and:(int)maxValue;
+
+/// Returns a random float number between 0.0 and 1.0
++ (float)randomFloat;
+
+@end
diff --git a/libs/sparrow/src/Classes/SPUtils.m b/libs/sparrow/src/Classes/SPUtils.m
new file mode 100644 (file)
index 0000000..2def1bc
--- /dev/null
@@ -0,0 +1,34 @@
+//
+//  SPUtils.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 04.01.11.
+//  Copyright 2011 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import "SPUtils.h"
+
+
+@implementation SPUtils
+
++ (int)nextPowerOfTwo:(int)number
+{    
+    int result = 1; 
+    while (result < number) result *= 2;
+    return result;    
+}
+
++ (int)randomIntBetween:(int)minValue and:(int)maxValue
+{
+    return (int)(minValue + [self randomFloat] * (maxValue - minValue));
+}
+
++ (float)randomFloat
+{
+    return (float) arc4random() / UINT_MAX;
+}
+
+@end
diff --git a/libs/sparrow/src/Classes/SPView.h b/libs/sparrow/src/Classes/SPView.h
new file mode 100644 (file)
index 0000000..537f238
--- /dev/null
@@ -0,0 +1,78 @@
+//
+//  EAGLView.h
+//  Sparrow
+//
+//  Created by Daniel Sperl on 13.03.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+
+#import <UIKit/UIKit.h>
+#import <OpenGLES/EAGL.h>
+#import <OpenGLES/ES1/gl.h>
+#import <OpenGLES/ES1/glext.h>
+
+@class SPStage;
+@class SPRenderSupport;
+
+/** ------------------------------------------------------------------------------------------------
+
+ An SPView is the UIView object that Sparrow renders its content into. 
+ Add it to the UIKit display list like any other view. Beware that Sparrow will only receive
+ multitouch events if the multitouchEnabled property of the view is enabled.
+ To start Sparrow, connect this class to your stage subclass and call the start method. When
+ the application ends or moves into the background, you should call the stop method.
+------------------------------------------------------------------------------------------------- */
+
+@interface SPView : UIView
+{ 
+  @private  
+    int mWidth;
+    int mHeight;
+    
+    SPStage *mStage;
+    SPRenderSupport *mRenderSupport;
+    
+    EAGLContext *mContext;    
+    GLuint mRenderbuffer;
+    GLuint mFramebuffer;    
+    
+    float mFrameRate;
+    NSTimer *mTimer;
+    id mDisplayLink;
+    BOOL mDisplayLinkSupported;        
+    
+    double mLastFrameTimestamp;
+    double mLastTouchTimestamp;
+}
+
+/// ----------------
+/// @name Properties
+/// ----------------
+
+/// Indicates if start was called.
+@property (nonatomic, readonly) BOOL isStarted;
+
+/// Assigns the desired framerate. Only dividers of 60 are allowed (60, 30, 20, 15, 12, 10, etc.)
+@property (nonatomic, assign) float frameRate;
+
+/// The stage object that will be processed.
+@property (nonatomic, retain) SPStage *stage;
+
+/// -------------
+/// @name Methods
+/// -------------
+
+/// Starts rendering and event handling.
+- (void)start;
+
+/// Stops rendering and event handling. Call this when the application moves into the background.
+- (void)stop;
+
+@end
diff --git a/libs/sparrow/src/Classes/SPView.m b/libs/sparrow/src/Classes/SPView.m
new file mode 100644 (file)
index 0000000..811cd64
--- /dev/null
@@ -0,0 +1,314 @@
+//
+//  EAGLView.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 13.03.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <QuartzCore/QuartzCore.h>
+#import <OpenGLES/EAGLDrawable.h>
+
+#import "SPStage.h"
+#import "SPStage_Internal.h"
+#import "SPView.h"
+#import "SPMacros.h"
+#import "SPTouch.h"
+#import "SPTouch_Internal.h"
+#import "SPRenderSupport.h"
+
+// --- private interface ---------------------------------------------------------------------------
+
+@interface SPView ()
+
+@property (nonatomic, retain) NSTimer *timer;
+@property (nonatomic, retain) id displayLink;
+
+- (void)setup;
+- (void)createFramebuffer;
+- (void)destroyFramebuffer;
+
+- (void)renderStage;
+- (void)processTouchEvent:(UIEvent*)event;
+
+@end
+
+// --- class implementation ------------------------------------------------------------------------
+
+@implementation SPView
+
+#define REFRESH_RATE 60
+
+@synthesize stage = mStage;
+@synthesize timer = mTimer;
+@synthesize displayLink = mDisplayLink;
+@synthesize frameRate = mFrameRate;
+
+- (id)initWithFrame:(CGRect)frame 
+{    
+    if ([super initWithFrame:frame]) 
+    {
+        [self setup];
+    }
+    return self;
+}
+
+- (id)initWithCoder:(NSCoder *)decoder 
+{
+    if ([super initWithCoder:decoder]) 
+    {
+        [self setup];
+    }
+    return self;
+}
+
+- (void)setup
+{
+    if (mContext) return; // already initialized!
+    
+    // A system version of 3.1 or greater is required to use CADisplayLink.
+    NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
+    if ([currSysVer compare:@"3.1" options:NSNumericSearch] != NSOrderedAscending)
+        mDisplayLinkSupported = YES;
+    
+    self.frameRate = 30.0f;
+    
+    // get the layer
+    CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
+    
+    eaglLayer.opaque = YES;
+    eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
+        [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, 
+        kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];    
+
+    mContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];    
+    
+    if (!mContext || ![EAGLContext setCurrentContext:mContext])        
+        NSLog(@"Could not create render context");    
+    
+    mRenderSupport = [[SPRenderSupport alloc] init];
+}
+
+- (void)layoutSubviews 
+{
+    [self destroyFramebuffer]; // reset framebuffer (scale factor could have changed)
+    [self createFramebuffer];
+    [self renderStage];        // fill buffer immediately to avoid flickering
+}
+
+- (void)createFramebuffer 
+{    
+    glGenFramebuffersOES(1, &mFramebuffer);
+    glGenRenderbuffersOES(1, &mRenderbuffer);
+    
+    glBindFramebufferOES(GL_FRAMEBUFFER_OES, mFramebuffer);
+    glBindRenderbufferOES(GL_RENDERBUFFER_OES, mRenderbuffer);
+    [mContext renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];
+    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, mRenderbuffer);
+    
+    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &mWidth);
+    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &mHeight);
+    
+    if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) 
+        NSLog(@"failed to create framebuffer: %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
+}
+
+- (void)destroyFramebuffer 
+{
+    glDeleteFramebuffersOES(1, &mFramebuffer);
+    mFramebuffer = 0;
+    glDeleteRenderbuffersOES(1, &mRenderbuffer);
+    mRenderbuffer = 0;    
+}
+
+- (void)renderStage
+{
+    if (mFramebuffer == 0 || mRenderbuffer == 0) 
+        return; // buffers not yet initialized
+    
+    SP_CREATE_POOL(pool);
+    
+    double now = CACurrentMediaTime();
+    double timePassed = now - mLastFrameTimestamp;
+    [mStage advanceTime:timePassed];
+    mLastFrameTimestamp = now;
+    
+    [EAGLContext setCurrentContext:mContext];
+    
+    glBindFramebufferOES(GL_FRAMEBUFFER_OES, mFramebuffer);
+    glViewport(0, 0, mWidth, mHeight);
+    
+    [mRenderSupport bindTexture:nil]; // old textures could have become invalid
+    [mStage render:mRenderSupport];
+    
+    glBindRenderbufferOES(GL_RENDERBUFFER_OES, mRenderbuffer);
+    [mContext presentRenderbuffer:GL_RENDERBUFFER_OES];
+    
+    SP_RELEASE_POOL(pool);
+}
+
+- (void)setTimer:(NSTimer *)newTimer 
+{    
+    if (mTimer != newTimer)
+    {
+        [mTimer invalidate];        
+        mTimer = newTimer;
+    }
+}
+
+- (void)setDisplayLink:(id)newDisplayLink
+{
+    if (mDisplayLink != newDisplayLink)
+    {
+        [mDisplayLink invalidate];
+        mDisplayLink = newDisplayLink;
+    }
+}
+
+- (void)setFrameRate:(float)value
+{    
+    if (mDisplayLinkSupported)
+    {
+        int frameInterval = 1;            
+        while (REFRESH_RATE / frameInterval > value)
+            ++frameInterval;
+        mFrameRate = REFRESH_RATE / frameInterval;
+    }
+    else 
+        mFrameRate = value;
+    
+    if (self.isStarted)
+    {
+        [self stop];
+        [self start];
+    }
+}
+
+- (BOOL)isStarted
+{
+    return mTimer || mDisplayLink;
+}
+
+- (void)start
+{
+    if (self.isStarted) return;
+    if (mFrameRate > 0.0f)
+    {
+        mLastFrameTimestamp = CACurrentMediaTime();
+        
+        if (mDisplayLinkSupported)
+        {
+            mDisplayLink = [NSClassFromString(@"CADisplayLink") 
+                            displayLinkWithTarget:self selector:@selector(renderStage)];
+            
+                       [mDisplayLink setFrameInterval: (int)(REFRESH_RATE / mFrameRate)];
+                       [mDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+        }
+        else 
+        {
+            // timer used as a fallback
+            self.timer = [NSTimer scheduledTimerWithTimeInterval:(1.0f / mFrameRate) 
+                target:self selector:@selector(renderStage) userInfo:nil repeats:YES];            
+        }
+    }    
+}
+
+- (void)stop
+{
+    [self renderStage]; // draw last-moment changes
+    
+    self.timer = nil;
+    self.displayLink = nil;
+}
+
+- (void)setStage:(SPStage*)stage
+{
+    if (mStage != stage)
+    {
+        mStage.nativeView = nil;
+        [mStage release];
+        mStage = [stage retain];
+        mStage.nativeView = self;        
+    }
+}
+
++ (Class)layerClass 
+{
+    return [CAEAGLLayer class];
+}
+
+- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event 
+{   
+    [self processTouchEvent:event];
+} 
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 
+{      
+    [self processTouchEvent:event];
+}
+
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 
+{
+    [self processTouchEvent:event];
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
+{    
+    mLastTouchTimestamp -= 0.0001f; // cancelled touch events have an old timestamp -> workaround
+    [self processTouchEvent:event];
+}
+
+- (void)processTouchEvent:(UIEvent*)event
+{
+    if (self.isStarted && mLastTouchTimestamp != event.timestamp)
+    {
+        SP_CREATE_POOL(pool);
+        
+        CGSize viewSize = self.bounds.size;
+        float xConversion = mStage.width / viewSize.width;
+        float yConversion = mStage.height / viewSize.height;
+        
+        // convert to SPTouches and forward to stage
+        NSMutableSet *touches = [NSMutableSet set];        
+        double now = CACurrentMediaTime();
+        for (UITouch *uiTouch in [event touchesForView:self])
+        {
+            CGPoint location = [uiTouch locationInView:self];            
+            CGPoint previousLocation = [uiTouch previousLocationInView:self];
+            SPTouch *touch = [SPTouch touch];
+            touch.timestamp = now; // timestamp of uiTouch not compatible to Sparrow timestamp
+            touch.globalX = location.x * xConversion;
+            touch.globalY = location.y * yConversion;
+            touch.previousGlobalX = previousLocation.x * xConversion;
+            touch.previousGlobalY = previousLocation.y * yConversion;
+            touch.tapCount = uiTouch.tapCount;
+            touch.phase = (SPTouchPhase) uiTouch.phase;            
+            [touches addObject:touch];
+        }
+        [mStage processTouches:touches];        
+        mLastTouchTimestamp = event.timestamp;
+        
+        SP_RELEASE_POOL(pool);
+    }    
+}
+
+- (void)dealloc 
+{    
+    if ([EAGLContext currentContext] == mContext) 
+        [EAGLContext setCurrentContext:nil];
+    
+    [mContext release];
+    [mStage release];   
+    [mRenderSupport release];
+    [self destroyFramebuffer];
+    
+    self.timer = nil;       // invalidates timer    
+    self.displayLink = nil; // invalidates displayLink        
+    
+    [super dealloc];
+}
+
+@end
diff --git a/libs/sparrow/src/Classes/Sparrow.h b/libs/sparrow/src/Classes/Sparrow.h
new file mode 100644 (file)
index 0000000..549ae5f
--- /dev/null
@@ -0,0 +1,45 @@
+//
+//  Sparrow.h
+//  Sparrow
+//
+//  Created by Daniel Sperl on 21.03.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#define SPARROW_VERSION @"1.1.0"
+
+#import "SPNSExtensions.h"
+#import "SPEventDispatcher.h"
+#import "SPDisplayObject.h"
+#import "SPDisplayObjectContainer.h"
+#import "SPQuad.h"
+#import "SPImage.h"
+#import "SPTextField.h"
+#import "SPBitmapFont.h"
+#import "SPButton.h"
+#import "SPStage.h"
+#import "SPSprite.h"
+#import "SPMovieClip.h"
+#import "SPTexture.h"
+#import "SPSubTexture.h"
+#import "SPRenderTexture.h"
+#import "SPGLTexture.h"
+#import "SPTextureAtlas.h"
+#import "SPEvent.h"
+#import "SPTouchEvent.h"
+#import "SPEnterFrameEvent.h"
+#import "SPJuggler.h"
+#import "SPTransitions.h"
+#import "SPTween.h"
+#import "SPDelayedInvocation.h"
+#import "SPRectangle.h"
+#import "SPMacros.h"
+#import "SPUtils.h"
+#import "SPView.h"
+#import "SPRenderSupport.h"
+#import "SPAudioEngine.h"
+#import "SPSound.h"
+#import "SPSoundChannel.h"
diff --git a/libs/sparrow/src/Sparrow.xcodeproj/project.pbxproj b/libs/sparrow/src/Sparrow.xcodeproj/project.pbxproj
new file mode 100755 (executable)
index 0000000..b08d747
--- /dev/null
@@ -0,0 +1,868 @@
+// !$*UTF8*$!
+{
+       archiveVersion = 1;
+       classes = {
+       };
+       objectVersion = 45;
+       objects = {
+
+/* Begin PBXBuildFile section */
+               DE019C391026360B00ECB0AC /* SPTween.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7044760FB62080007F5ECC /* SPTween.m */; };
+               DE019C3A1026361200ECB0AC /* SPDelayedInvocation.m in Sources */ = {isa = PBXBuildFile; fileRef = DEFB1B94100926260022C117 /* SPDelayedInvocation.m */; };
+               DE019C3B1026363D00ECB0AC /* SPNSExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = DE68EA160FBB5660004DBC95 /* SPNSExtensions.m */; };
+               DE019C3C1026364800ECB0AC /* SPJuggler.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7044270FB61506007F5ECC /* SPJuggler.m */; };
+               DE13D18B12AADBF6000C77E6 /* SPRenderTexture.h in Headers */ = {isa = PBXBuildFile; fileRef = DE13D18912AADBF6000C77E6 /* SPRenderTexture.h */; };
+               DE13D18C12AADBF6000C77E6 /* SPRenderTexture.m in Sources */ = {isa = PBXBuildFile; fileRef = DE13D18A12AADBF6000C77E6 /* SPRenderTexture.m */; };
+               DE14412E12421EA600FFA95A /* SPNSExtensionsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE05748611E915A900F3A8A4 /* SPNSExtensionsTest.m */; };
+               DE14413612421F4700FFA95A /* SPImageTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE0853A40FEC286900DAF53C /* SPImageTest.m */; };
+               DE14413712421F4800FFA95A /* SPDisplayObjectTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE469D6E0F938FAB00F56E91 /* SPDisplayObjectTest.m */; };
+               DE14413812421F4800FFA95A /* SPMovieClipTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC54F6211B7765500E439B0 /* SPMovieClipTest.m */; };
+               DE14413912421F4900FFA95A /* SPRectangleTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DED67F7C0FA359F00050E779 /* SPRectangleTest.m */; };
+               DE14413A12421F4900FFA95A /* SPDisplayObjectContainerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEB21CF80F93C9780080D5C2 /* SPDisplayObjectContainerTest.m */; };
+               DE14413B12421F4A00FFA95A /* SPEventDispatcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE594490FA63BA800E3AEFC /* SPEventDispatcherTest.m */; };
+               DE14413C12421F4B00FFA95A /* SPDelayedInvocationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5286BA11F77C6200F916E8 /* SPDelayedInvocationTest.m */; };
+               DE14413D12421F4B00FFA95A /* SPMatrixTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8F1E2D0F7C1F3A0085E9E4 /* SPMatrixTest.m */; };
+               DE14413E12421F4B00FFA95A /* SPPointTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEABCF5B0F7AE187003B6C9D /* SPPointTest.m */; };
+               DE14413F12421F4C00FFA95A /* SPQuadTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DED2B6F90FA0CF5900083578 /* SPQuadTest.m */; };
+               DE14414012421F4C00FFA95A /* SPJugglerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE1F9446104704440084D470 /* SPJugglerTest.m */; };
+               DE14414112421F4D00FFA95A /* SPTweenTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE75E8660FBDC57E00C64495 /* SPTweenTest.m */; };
+               DE14414212421F4E00FFA95A /* SPStageTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DED67F330FA3514C0050E779 /* SPStageTest.m */; };
+               DE18FE4511B083B200D01F04 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE18FE4411B083B200D01F04 /* AVFoundation.framework */; };
+               DE20D9CC10713B0C006658C9 /* SPRenderSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = DE20D9CA10713B0C006658C9 /* SPRenderSupport.m */; };
+               DE33072512D2EBCD009CC5E7 /* SPUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = DE33072312D2EBCD009CC5E7 /* SPUtils.h */; };
+               DE33072612D2EBCD009CC5E7 /* SPUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = DE33072412D2EBCD009CC5E7 /* SPUtils.m */; };
+               DE33072B12D2ED1A009CC5E7 /* SPUtilsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE33072812D2ECB1009CC5E7 /* SPUtilsTest.m */; };
+               DED8B48711B0827E00A1766C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; };
+               DED8B48811B0827E00A1766C /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE9CF1EB0FED3128008A32FF /* CoreGraphics.framework */; };
+               DED8B48911B0827E00A1766C /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28FD15070DC6FC5B0079059D /* QuartzCore.framework */; };
+               DED8B48A11B0827E00A1766C /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28FD14FF0DC6FC520079059D /* OpenGLES.framework */; };
+               DED8B48B11B0827E00A1766C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; };
+               DED8B48C11B0827E00A1766C /* OpenAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEE63A7111AED4FA00D60321 /* OpenAL.framework */; };
+               DED8B48D11B0827E00A1766C /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEE63A8511AED51400D60321 /* AudioToolbox.framework */; };
+               DED9B51E10629D9F00989853 /* SPPoolObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DED9B51C10629D9F00989853 /* SPPoolObject.m */; };
+               DEE09D7B108364A900ECC896 /* SPBitmapFont.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE09D79108364A900ECC896 /* SPBitmapFont.m */; };
+               DEE09D7F108369AE00ECC896 /* SPBitmapChar.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE09D7D108369AE00ECC896 /* SPBitmapChar.m */; };
+               DEE63A5211AED38100D60321 /* SPAudioEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE63A4C11AED38100D60321 /* SPAudioEngine.m */; };
+               DEE63A5411AED38100D60321 /* SPSound.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE63A4E11AED38100D60321 /* SPSound.m */; };
+               DEE63A5611AED38100D60321 /* SPSoundChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE63A5011AED38100D60321 /* SPSoundChannel.m */; };
+               DEE6516B11F9DF200065207E /* SPCompiledSprite.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2CC36011EF75B600439B43 /* SPCompiledSprite.m */; };
+               DEE94E8311B43DE60000FE20 /* SPMovieClip.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE94E8111B43DE60000FE20 /* SPMovieClip.m */; };
+               DEEA933A101E32E10071DD21 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE9CF1EB0FED3128008A32FF /* CoreGraphics.framework */; };
+               DEEA933B101E32E10071DD21 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; };
+               DEEA933C101E32E10071DD21 /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28FD14FF0DC6FC520079059D /* OpenGLES.framework */; };
+               DEEA933D101E32E10071DD21 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28FD15070DC6FC5B0079059D /* QuartzCore.framework */; };
+               DEEA933E101E32E10071DD21 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; };
+               DEEA934E101E33F00071DD21 /* libSparrow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DEFE4BC2101B317600E22471 /* libSparrow.a */; };
+               DEED173A108A50000071438F /* SPTweenedProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = DEED1738108A50000071438F /* SPTweenedProperty.m */; };
+               DEEDE3A111B29908009FD4C8 /* OpenAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEE63A7111AED4FA00D60321 /* OpenAL.framework */; };
+               DEEDE3A211B29908009FD4C8 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE18FE4411B083B200D01F04 /* AVFoundation.framework */; };
+               DEEDE3A311B29909009FD4C8 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEE63A8511AED51400D60321 /* AudioToolbox.framework */; };
+               DEF1730F11B0645A00A11DD7 /* SPALSound.m in Sources */ = {isa = PBXBuildFile; fileRef = DEF1730D11B0645A00A11DD7 /* SPALSound.m */; };
+               DEF1731311B0648B00A11DD7 /* SPALSoundChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = DEF1731111B0648B00A11DD7 /* SPALSoundChannel.m */; };
+               DEF1735D11B075B000A11DD7 /* SPAVSound.m in Sources */ = {isa = PBXBuildFile; fileRef = DEF1735B11B075B000A11DD7 /* SPAVSound.m */; };
+               DEF1736111B075BC00A11DD7 /* SPAVSoundChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = DEF1735F11B075BC00A11DD7 /* SPAVSoundChannel.m */; };
+               DEFE4BCE101B31DF00E22471 /* SPTransitions.m in Sources */ = {isa = PBXBuildFile; fileRef = DED859440FB883EE00D3D7D2 /* SPTransitions.m */; };
+               DEFE4BD1101B31DF00E22471 /* SPView.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC2B67A0F6D4CE90063AB1D /* SPView.m */; };
+               DEFE4BD2101B31DF00E22471 /* SPEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE593D20FA6116B00E3AEFC /* SPEvent.m */; };
+               DEFE4BD3101B31DF00E22471 /* SPEventDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC2B6810F6D51430063AB1D /* SPEventDispatcher.m */; };
+               DEFE4BD4101B31DF00E22471 /* SPTouch.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE4449D0FABA7860085D36D /* SPTouch.m */; };
+               DEFE4BD5101B31DF00E22471 /* SPTouchEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE444A90FABABBB0085D36D /* SPTouchEvent.m */; };
+               DEFE4BD6101B31DF00E22471 /* SPEnterFrameEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = DEEEB8C90FAA2C300059D72B /* SPEnterFrameEvent.m */; };
+               DEFE4BD7101B31DF00E22471 /* SPDisplayObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2ED8050F6D52080012B6BA /* SPDisplayObject.m */; };
+               DEFE4BD8101B31DF00E22471 /* SPDisplayObjectContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2ED8090F6D53020012B6BA /* SPDisplayObjectContainer.m */; };
+               DEFE4BD9101B31DF00E22471 /* SPSprite.m in Sources */ = {isa = PBXBuildFile; fileRef = DE4D6AEB0F75913D0045CBF7 /* SPSprite.m */; };
+               DEFE4BDA101B31DF00E22471 /* SPStage.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2ED8590F6D54AC0012B6BA /* SPStage.m */; };
+               DEFE4BDB101B31DF00E22471 /* SPQuad.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2ED8560F6D54900012B6BA /* SPQuad.m */; };
+               DEFE4BDC101B31DF00E22471 /* SPImage.m in Sources */ = {isa = PBXBuildFile; fileRef = DE08535D0FEC21F500DAF53C /* SPImage.m */; };
+               DEFE4BDD101B31DF00E22471 /* SPTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = DED4EFCA0FF9439D0093AD29 /* SPTextField.m */; };
+               DEFE4BDE101B31DF00E22471 /* SPTexture.m in Sources */ = {isa = PBXBuildFile; fileRef = DE0853F90FEC2CFF00DAF53C /* SPTexture.m */; };
+               DEFE4BDF101B31DF00E22471 /* SPGLTexture.m in Sources */ = {isa = PBXBuildFile; fileRef = DECF84320FF649D50026A4ED /* SPGLTexture.m */; };
+               DEFE4BE0101B31DF00E22471 /* SPSubTexture.m in Sources */ = {isa = PBXBuildFile; fileRef = DECF84BA0FF681BA0026A4ED /* SPSubTexture.m */; };
+               DEFE4BE1101B31DF00E22471 /* SPButton.m in Sources */ = {isa = PBXBuildFile; fileRef = DED85312100BC9DA0014F729 /* SPButton.m */; };
+               DEFE4BE2101B31DF00E22471 /* SPMatrix.m in Sources */ = {isa = PBXBuildFile; fileRef = DE469D260F9386FD00F56E91 /* SPMatrix.m */; };
+               DEFE4BE3101B31DF00E22471 /* SPPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = DE469D280F9386FD00F56E91 /* SPPoint.m */; };
+               DEFE4BE4101B31DF00E22471 /* SPRectangle.m in Sources */ = {isa = PBXBuildFile; fileRef = DE469D2A0F9386FD00F56E91 /* SPRectangle.m */; };
+               DEFE4BE6101B31DF00E22471 /* SPTextureAtlas.m in Sources */ = {isa = PBXBuildFile; fileRef = DECF84270FF619150026A4ED /* SPTextureAtlas.m */; };
+               DEFE4C38101B5FB100E22471 /* SPRendering.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9CF1BC0FED2678008A32FF /* SPRendering.m */; };
+               DEFE4C3A101B5FB100E22471 /* SPTouchProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = DEDCD3AD0FADEE280022011C /* SPTouchProcessor.m */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+               DEEA934C101E33ED0071DD21 /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = DEFE4BC1101B317600E22471;
+                       remoteInfo = SparrowLib;
+               };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+               1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+               1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
+               28FD14FF0DC6FC520079059D /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; };
+               28FD15070DC6FC5B0079059D /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
+               DE05748611E915A900F3A8A4 /* SPNSExtensionsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPNSExtensionsTest.m; sourceTree = "<group>"; };
+               DE08535C0FEC21F500DAF53C /* SPImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPImage.h; sourceTree = "<group>"; };
+               DE08535D0FEC21F500DAF53C /* SPImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPImage.m; sourceTree = "<group>"; };
+               DE0853A40FEC286900DAF53C /* SPImageTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPImageTest.m; sourceTree = "<group>"; };
+               DE0853F80FEC2CFF00DAF53C /* SPTexture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTexture.h; sourceTree = "<group>"; };
+               DE0853F90FEC2CFF00DAF53C /* SPTexture.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTexture.m; sourceTree = "<group>"; };
+               DE13D18912AADBF6000C77E6 /* SPRenderTexture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPRenderTexture.h; sourceTree = "<group>"; };
+               DE13D18A12AADBF6000C77E6 /* SPRenderTexture.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRenderTexture.m; sourceTree = "<group>"; };
+               DE18FE4411B083B200D01F04 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
+               DE1F9446104704440084D470 /* SPJugglerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPJugglerTest.m; sourceTree = "<group>"; };
+               DE20D9C910713B0C006658C9 /* SPRenderSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPRenderSupport.h; sourceTree = "<group>"; };
+               DE20D9CA10713B0C006658C9 /* SPRenderSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRenderSupport.m; sourceTree = "<group>"; };
+               DE2AD849104A84F1001AF0A0 /* SPStage_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPStage_Internal.h; sourceTree = "<group>"; };
+               DE2CC35F11EF75B600439B43 /* SPCompiledSprite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPCompiledSprite.h; sourceTree = "<group>"; };
+               DE2CC36011EF75B600439B43 /* SPCompiledSprite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPCompiledSprite.m; sourceTree = "<group>"; };
+               DE2ED8040F6D52080012B6BA /* SPDisplayObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDisplayObject.h; sourceTree = "<group>"; };
+               DE2ED8050F6D52080012B6BA /* SPDisplayObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDisplayObject.m; sourceTree = "<group>"; };
+               DE2ED8080F6D53020012B6BA /* SPDisplayObjectContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDisplayObjectContainer.h; sourceTree = "<group>"; };
+               DE2ED8090F6D53020012B6BA /* SPDisplayObjectContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDisplayObjectContainer.m; sourceTree = "<group>"; };
+               DE2ED8550F6D54900012B6BA /* SPQuad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPQuad.h; sourceTree = "<group>"; };
+               DE2ED8560F6D54900012B6BA /* SPQuad.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPQuad.m; sourceTree = "<group>"; };
+               DE2ED8580F6D54AC0012B6BA /* SPStage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPStage.h; sourceTree = "<group>"; };
+               DE2ED8590F6D54AC0012B6BA /* SPStage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPStage.m; sourceTree = "<group>"; };
+               DE33072312D2EBCD009CC5E7 /* SPUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPUtils.h; sourceTree = "<group>"; };
+               DE33072412D2EBCD009CC5E7 /* SPUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPUtils.m; sourceTree = "<group>"; };
+               DE33072812D2ECB1009CC5E7 /* SPUtilsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPUtilsTest.m; sourceTree = "<group>"; };
+               DE469D240F9386FD00F56E91 /* SPMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMacros.h; sourceTree = "<group>"; };
+               DE469D250F9386FD00F56E91 /* SPMatrix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMatrix.h; sourceTree = "<group>"; };
+               DE469D260F9386FD00F56E91 /* SPMatrix.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMatrix.m; sourceTree = "<group>"; };
+               DE469D270F9386FD00F56E91 /* SPPoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPoint.h; sourceTree = "<group>"; };
+               DE469D280F9386FD00F56E91 /* SPPoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPoint.m; sourceTree = "<group>"; };
+               DE469D290F9386FD00F56E91 /* SPRectangle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPRectangle.h; sourceTree = "<group>"; };
+               DE469D2A0F9386FD00F56E91 /* SPRectangle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRectangle.m; sourceTree = "<group>"; };
+               DE469D6E0F938FAB00F56E91 /* SPDisplayObjectTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDisplayObjectTest.m; sourceTree = "<group>"; };
+               DE4D6AEA0F75913D0045CBF7 /* SPSprite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSprite.h; sourceTree = "<group>"; };
+               DE4D6AEB0F75913D0045CBF7 /* SPSprite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSprite.m; sourceTree = "<group>"; };
+               DE5286BA11F77C6200F916E8 /* SPDelayedInvocationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDelayedInvocationTest.m; sourceTree = "<group>"; };
+               DE68EA150FBB5660004DBC95 /* SPNSExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPNSExtensions.h; sourceTree = "<group>"; };
+               DE68EA160FBB5660004DBC95 /* SPNSExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPNSExtensions.m; sourceTree = "<group>"; };
+               DE7044260FB61506007F5ECC /* SPJuggler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPJuggler.h; sourceTree = "<group>"; };
+               DE7044270FB61506007F5ECC /* SPJuggler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPJuggler.m; sourceTree = "<group>"; };
+               DE70442A0FB618AE007F5ECC /* SPAnimatable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAnimatable.h; sourceTree = "<group>"; };
+               DE7044750FB62080007F5ECC /* SPTween.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTween.h; sourceTree = "<group>"; };
+               DE7044760FB62080007F5ECC /* SPTween.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTween.m; sourceTree = "<group>"; };
+               DE75E8660FBDC57E00C64495 /* SPTweenTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTweenTest.m; sourceTree = "<group>"; };
+               DE8F1E2D0F7C1F3A0085E9E4 /* SPMatrixTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMatrixTest.m; sourceTree = "<group>"; };
+               DE9CF1BC0FED2678008A32FF /* SPRendering.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRendering.m; sourceTree = "<group>"; };
+               DE9CF1EB0FED3128008A32FF /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
+               DEABCF5B0F7AE187003B6C9D /* SPPointTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPointTest.m; sourceTree = "<group>"; };
+               DEB21CF80F93C9780080D5C2 /* SPDisplayObjectContainerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDisplayObjectContainerTest.m; sourceTree = "<group>"; };
+               DEC2B6790F6D4CE90063AB1D /* SPView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPView.h; sourceTree = "<group>"; };
+               DEC2B67A0F6D4CE90063AB1D /* SPView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPView.m; sourceTree = "<group>"; };
+               DEC2B6800F6D51430063AB1D /* SPEventDispatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPEventDispatcher.h; sourceTree = "<group>"; };
+               DEC2B6810F6D51430063AB1D /* SPEventDispatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPEventDispatcher.m; sourceTree = "<group>"; };
+               DEC54F6211B7765500E439B0 /* SPMovieClipTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMovieClipTest.m; sourceTree = "<group>"; };
+               DECF84260FF619150026A4ED /* SPTextureAtlas.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTextureAtlas.h; sourceTree = "<group>"; };
+               DECF84270FF619150026A4ED /* SPTextureAtlas.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTextureAtlas.m; sourceTree = "<group>"; };
+               DECF84310FF649D50026A4ED /* SPGLTexture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPGLTexture.h; sourceTree = "<group>"; };
+               DECF84320FF649D50026A4ED /* SPGLTexture.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPGLTexture.m; sourceTree = "<group>"; };
+               DECF84B90FF681BA0026A4ED /* SPSubTexture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSubTexture.h; sourceTree = "<group>"; };
+               DECF84BA0FF681BA0026A4ED /* SPSubTexture.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSubTexture.m; sourceTree = "<group>"; };
+               DED2B6F90FA0CF5900083578 /* SPQuadTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPQuadTest.m; sourceTree = "<group>"; };
+               DED4EFC90FF9439D0093AD29 /* SPTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTextField.h; sourceTree = "<group>"; };
+               DED4EFCA0FF9439D0093AD29 /* SPTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTextField.m; sourceTree = "<group>"; };
+               DED67F330FA3514C0050E779 /* SPStageTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPStageTest.m; sourceTree = "<group>"; };
+               DED67F7C0FA359F00050E779 /* SPRectangleTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRectangleTest.m; sourceTree = "<group>"; };
+               DED85311100BC9DA0014F729 /* SPButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPButton.h; sourceTree = "<group>"; };
+               DED85312100BC9DA0014F729 /* SPButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPButton.m; sourceTree = "<group>"; };
+               DED859430FB883EE00D3D7D2 /* SPTransitions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTransitions.h; sourceTree = "<group>"; };
+               DED859440FB883EE00D3D7D2 /* SPTransitions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTransitions.m; sourceTree = "<group>"; };
+               DED9B51B10629D9F00989853 /* SPPoolObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPoolObject.h; sourceTree = "<group>"; };
+               DED9B51C10629D9F00989853 /* SPPoolObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPoolObject.m; sourceTree = "<group>"; };
+               DEDCD3AC0FADEE280022011C /* SPTouchProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTouchProcessor.h; sourceTree = "<group>"; };
+               DEDCD3AD0FADEE280022011C /* SPTouchProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTouchProcessor.m; sourceTree = "<group>"; };
+               DEDCD3CF0FADF52B0022011C /* SPTouch_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTouch_Internal.h; sourceTree = "<group>"; };
+               DEDCD44A0FADFF250022011C /* SPEvent_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPEvent_Internal.h; sourceTree = "<group>"; };
+               DEDCD44E0FADFFA40022011C /* SPDisplayObject_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDisplayObject_Internal.h; sourceTree = "<group>"; };
+               DEE09D78108364A900ECC896 /* SPBitmapFont.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPBitmapFont.h; sourceTree = "<group>"; };
+               DEE09D79108364A900ECC896 /* SPBitmapFont.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPBitmapFont.m; sourceTree = "<group>"; };
+               DEE09D7C108369AE00ECC896 /* SPBitmapChar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPBitmapChar.h; sourceTree = "<group>"; };
+               DEE09D7D108369AE00ECC896 /* SPBitmapChar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPBitmapChar.m; sourceTree = "<group>"; };
+               DEE4449C0FABA7860085D36D /* SPTouch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTouch.h; sourceTree = "<group>"; };
+               DEE4449D0FABA7860085D36D /* SPTouch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTouch.m; sourceTree = "<group>"; };
+               DEE444A80FABABBB0085D36D /* SPTouchEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTouchEvent.h; sourceTree = "<group>"; };
+               DEE444A90FABABBB0085D36D /* SPTouchEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTouchEvent.m; sourceTree = "<group>"; };
+               DEE593D10FA6116B00E3AEFC /* SPEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPEvent.h; sourceTree = "<group>"; };
+               DEE593D20FA6116B00E3AEFC /* SPEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPEvent.m; sourceTree = "<group>"; };
+               DEE594490FA63BA800E3AEFC /* SPEventDispatcherTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPEventDispatcherTest.m; sourceTree = "<group>"; };
+               DEE63A4B11AED38100D60321 /* SPAudioEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAudioEngine.h; sourceTree = "<group>"; };
+               DEE63A4C11AED38100D60321 /* SPAudioEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAudioEngine.m; sourceTree = "<group>"; };
+               DEE63A4D11AED38100D60321 /* SPSound.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSound.h; sourceTree = "<group>"; };
+               DEE63A4E11AED38100D60321 /* SPSound.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSound.m; sourceTree = "<group>"; };
+               DEE63A4F11AED38100D60321 /* SPSoundChannel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSoundChannel.h; sourceTree = "<group>"; };
+               DEE63A5011AED38100D60321 /* SPSoundChannel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSoundChannel.m; sourceTree = "<group>"; };
+               DEE63A7111AED4FA00D60321 /* OpenAL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenAL.framework; path = System/Library/Frameworks/OpenAL.framework; sourceTree = SDKROOT; };
+               DEE63A8511AED51400D60321 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
+               DEE94E8011B43DE60000FE20 /* SPMovieClip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMovieClip.h; sourceTree = "<group>"; };
+               DEE94E8111B43DE60000FE20 /* SPMovieClip.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMovieClip.m; sourceTree = "<group>"; };
+               DEEA9335101E32A30071DD21 /* UnitTest.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTest.octest; sourceTree = BUILT_PRODUCTS_DIR; };
+               DEEA937F101E3BA20071DD21 /* UnitTests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "UnitTests-Info.plist"; sourceTree = "<group>"; };
+               DEEA9402101E44F10071DD21 /* Sparrow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Sparrow.h; sourceTree = "<group>"; };
+               DEED1737108A50000071438F /* SPTweenedProperty.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTweenedProperty.h; sourceTree = "<group>"; };
+               DEED1738108A50000071438F /* SPTweenedProperty.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTweenedProperty.m; sourceTree = "<group>"; };
+               DEEEB8C80FAA2C300059D72B /* SPEnterFrameEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPEnterFrameEvent.h; sourceTree = "<group>"; };
+               DEEEB8C90FAA2C300059D72B /* SPEnterFrameEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPEnterFrameEvent.m; sourceTree = "<group>"; };
+               DEF1730C11B0645A00A11DD7 /* SPALSound.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPALSound.h; sourceTree = "<group>"; };
+               DEF1730D11B0645A00A11DD7 /* SPALSound.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPALSound.m; sourceTree = "<group>"; };
+               DEF1731011B0648B00A11DD7 /* SPALSoundChannel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPALSoundChannel.h; sourceTree = "<group>"; };
+               DEF1731111B0648B00A11DD7 /* SPALSoundChannel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPALSoundChannel.m; sourceTree = "<group>"; };
+               DEF1735A11B075B000A11DD7 /* SPAVSound.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAVSound.h; sourceTree = "<group>"; };
+               DEF1735B11B075B000A11DD7 /* SPAVSound.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAVSound.m; sourceTree = "<group>"; };
+               DEF1735E11B075BC00A11DD7 /* SPAVSoundChannel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAVSoundChannel.h; sourceTree = "<group>"; };
+               DEF1735F11B075BC00A11DD7 /* SPAVSoundChannel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAVSoundChannel.m; sourceTree = "<group>"; };
+               DEFB1B93100926260022C117 /* SPDelayedInvocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDelayedInvocation.h; sourceTree = "<group>"; };
+               DEFB1B94100926260022C117 /* SPDelayedInvocation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDelayedInvocation.m; sourceTree = "<group>"; };
+               DEFE4BC2101B317600E22471 /* libSparrow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSparrow.a; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+               DEEA9332101E32A30071DD21 /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               DEEA934E101E33F00071DD21 /* libSparrow.a in Frameworks */,
+                               DEEA933A101E32E10071DD21 /* CoreGraphics.framework in Frameworks */,
+                               DEEA933B101E32E10071DD21 /* Foundation.framework in Frameworks */,
+                               DEEA933C101E32E10071DD21 /* OpenGLES.framework in Frameworks */,
+                               DEEA933D101E32E10071DD21 /* QuartzCore.framework in Frameworks */,
+                               DEEA933E101E32E10071DD21 /* UIKit.framework in Frameworks */,
+                               DEEDE3A111B29908009FD4C8 /* OpenAL.framework in Frameworks */,
+                               DEEDE3A211B29908009FD4C8 /* AVFoundation.framework in Frameworks */,
+                               DEEDE3A311B29909009FD4C8 /* AudioToolbox.framework in Frameworks */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               DEFE4BC0101B317600E22471 /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               DED8B48711B0827E00A1766C /* Foundation.framework in Frameworks */,
+                               DED8B48811B0827E00A1766C /* CoreGraphics.framework in Frameworks */,
+                               DED8B48911B0827E00A1766C /* QuartzCore.framework in Frameworks */,
+                               DED8B48A11B0827E00A1766C /* OpenGLES.framework in Frameworks */,
+                               DED8B48B11B0827E00A1766C /* UIKit.framework in Frameworks */,
+                               DED8B48C11B0827E00A1766C /* OpenAL.framework in Frameworks */,
+                               DED8B48D11B0827E00A1766C /* AudioToolbox.framework in Frameworks */,
+                               DE18FE4511B083B200D01F04 /* AVFoundation.framework in Frameworks */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+               080E96DDFE201D6D7F000001 /* Classes */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DEE63A4A11AED36700D60321 /* Audio */,
+                               DEDCD3CE0FADF2100022011C /* System */,
+                               DEDCD3C60FADF1870022011C /* Events */,
+                               DEDCD3B00FADF1720022011C /* Display */,
+                               DEE968F51086553D001D57E9 /* Textures */,
+                               DEE202BD0FBDAD2900B8A467 /* Animation */,
+                               DE469D310F93870A00F56E91 /* Utils */,
+                               DEEA9402101E44F10071DD21 /* Sparrow.h */,
+                       );
+                       path = Classes;
+                       sourceTree = "<group>";
+               };
+               19C28FACFE9D520D11CA2CBB /* Products */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DEFE4BC2101B317600E22471 /* libSparrow.a */,
+                               DEEA9335101E32A30071DD21 /* UnitTest.octest */,
+                       );
+                       name = Products;
+                       sourceTree = "<group>";
+               };
+               29B97314FDCFA39411CA2CEA /* CustomTemplate */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DEABCDE80F7ABE71003B6C9D /* UnitTests */,
+                               080E96DDFE201D6D7F000001 /* Classes */,
+                               29B97317FDCFA39411CA2CEA /* Resources */,
+                               29B97323FDCFA39411CA2CEA /* Frameworks */,
+                               19C28FACFE9D520D11CA2CBB /* Products */,
+                       );
+                       name = CustomTemplate;
+                       sourceTree = "<group>";
+               };
+               29B97317FDCFA39411CA2CEA /* Resources */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DEEA937F101E3BA20071DD21 /* UnitTests-Info.plist */,
+                       );
+                       name = Resources;
+                       sourceTree = "<group>";
+               };
+               29B97323FDCFA39411CA2CEA /* Frameworks */ = {
+                       isa = PBXGroup;
+                       children = (
+                               1D30AB110D05D00D00671497 /* Foundation.framework */,
+                               DE9CF1EB0FED3128008A32FF /* CoreGraphics.framework */,
+                               28FD15070DC6FC5B0079059D /* QuartzCore.framework */,
+                               28FD14FF0DC6FC520079059D /* OpenGLES.framework */,
+                               1DF5F4DF0D08C38300B7A737 /* UIKit.framework */,
+                               DEE63A7111AED4FA00D60321 /* OpenAL.framework */,
+                               DEE63A8511AED51400D60321 /* AudioToolbox.framework */,
+                               DE18FE4411B083B200D01F04 /* AVFoundation.framework */,
+                       );
+                       name = Frameworks;
+                       sourceTree = "<group>";
+               };
+               DE20D9C810713AD0006658C9 /* Rendering */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DE9CF1BC0FED2678008A32FF /* SPRendering.m */,
+                               DE20D9C910713B0C006658C9 /* SPRenderSupport.h */,
+                               DE20D9CA10713B0C006658C9 /* SPRenderSupport.m */,
+                       );
+                       name = Rendering;
+                       sourceTree = "<group>";
+               };
+               DE469D310F93870A00F56E91 /* Utils */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DE469D240F9386FD00F56E91 /* SPMacros.h */,
+                               DE469D250F9386FD00F56E91 /* SPMatrix.h */,
+                               DE469D260F9386FD00F56E91 /* SPMatrix.m */,
+                               DE469D270F9386FD00F56E91 /* SPPoint.h */,
+                               DE469D280F9386FD00F56E91 /* SPPoint.m */,
+                               DE469D290F9386FD00F56E91 /* SPRectangle.h */,
+                               DE469D2A0F9386FD00F56E91 /* SPRectangle.m */,
+                               DE68EA150FBB5660004DBC95 /* SPNSExtensions.h */,
+                               DE68EA160FBB5660004DBC95 /* SPNSExtensions.m */,
+                               DED9B51B10629D9F00989853 /* SPPoolObject.h */,
+                               DED9B51C10629D9F00989853 /* SPPoolObject.m */,
+                               DE33072312D2EBCD009CC5E7 /* SPUtils.h */,
+                               DE33072412D2EBCD009CC5E7 /* SPUtils.m */,
+                       );
+                       name = Utils;
+                       sourceTree = "<group>";
+               };
+               DEABCDE80F7ABE71003B6C9D /* UnitTests */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DE5286BA11F77C6200F916E8 /* SPDelayedInvocationTest.m */,
+                               DEABCF5B0F7AE187003B6C9D /* SPPointTest.m */,
+                               DE8F1E2D0F7C1F3A0085E9E4 /* SPMatrixTest.m */,
+                               DE469D6E0F938FAB00F56E91 /* SPDisplayObjectTest.m */,
+                               DEB21CF80F93C9780080D5C2 /* SPDisplayObjectContainerTest.m */,
+                               DED2B6F90FA0CF5900083578 /* SPQuadTest.m */,
+                               DED67F330FA3514C0050E779 /* SPStageTest.m */,
+                               DED67F7C0FA359F00050E779 /* SPRectangleTest.m */,
+                               DEE594490FA63BA800E3AEFC /* SPEventDispatcherTest.m */,
+                               DE75E8660FBDC57E00C64495 /* SPTweenTest.m */,
+                               DE0853A40FEC286900DAF53C /* SPImageTest.m */,
+                               DE1F9446104704440084D470 /* SPJugglerTest.m */,
+                               DEC54F6211B7765500E439B0 /* SPMovieClipTest.m */,
+                               DE05748611E915A900F3A8A4 /* SPNSExtensionsTest.m */,
+                               DE33072812D2ECB1009CC5E7 /* SPUtilsTest.m */,
+                       );
+                       path = UnitTests;
+                       sourceTree = "<group>";
+               };
+               DED85A930FB49E6D000BAA95 /* Internal */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DEDCD3CF0FADF52B0022011C /* SPTouch_Internal.h */,
+                               DEDCD44A0FADFF250022011C /* SPEvent_Internal.h */,
+                       );
+                       name = Internal;
+                       sourceTree = "<group>";
+               };
+               DED85A940FB49E90000BAA95 /* Internal */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DEDCD3AC0FADEE280022011C /* SPTouchProcessor.h */,
+                               DEDCD3AD0FADEE280022011C /* SPTouchProcessor.m */,
+                               DEDCD44E0FADFFA40022011C /* SPDisplayObject_Internal.h */,
+                               DE2AD849104A84F1001AF0A0 /* SPStage_Internal.h */,
+                       );
+                       name = Internal;
+                       sourceTree = "<group>";
+               };
+               DEDCD3B00FADF1720022011C /* Display */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DE20D9C810713AD0006658C9 /* Rendering */,
+                               DEE09D771083647D00ECC896 /* Text */,
+                               DED85A940FB49E90000BAA95 /* Internal */,
+                               DE2ED8040F6D52080012B6BA /* SPDisplayObject.h */,
+                               DE2ED8050F6D52080012B6BA /* SPDisplayObject.m */,
+                               DE2ED8080F6D53020012B6BA /* SPDisplayObjectContainer.h */,
+                               DE2ED8090F6D53020012B6BA /* SPDisplayObjectContainer.m */,
+                               DE4D6AEA0F75913D0045CBF7 /* SPSprite.h */,
+                               DE4D6AEB0F75913D0045CBF7 /* SPSprite.m */,
+                               DE2ED8580F6D54AC0012B6BA /* SPStage.h */,
+                               DE2ED8590F6D54AC0012B6BA /* SPStage.m */,
+                               DE2ED8550F6D54900012B6BA /* SPQuad.h */,
+                               DE2ED8560F6D54900012B6BA /* SPQuad.m */,
+                               DE08535C0FEC21F500DAF53C /* SPImage.h */,
+                               DE08535D0FEC21F500DAF53C /* SPImage.m */,
+                               DED85311100BC9DA0014F729 /* SPButton.h */,
+                               DED85312100BC9DA0014F729 /* SPButton.m */,
+                               DEE94E8011B43DE60000FE20 /* SPMovieClip.h */,
+                               DEE94E8111B43DE60000FE20 /* SPMovieClip.m */,
+                               DE2CC35F11EF75B600439B43 /* SPCompiledSprite.h */,
+                               DE2CC36011EF75B600439B43 /* SPCompiledSprite.m */,
+                       );
+                       name = Display;
+                       sourceTree = "<group>";
+               };
+               DEDCD3C60FADF1870022011C /* Events */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DED85A930FB49E6D000BAA95 /* Internal */,
+                               DEE593D10FA6116B00E3AEFC /* SPEvent.h */,
+                               DEE593D20FA6116B00E3AEFC /* SPEvent.m */,
+                               DEC2B6800F6D51430063AB1D /* SPEventDispatcher.h */,
+                               DEC2B6810F6D51430063AB1D /* SPEventDispatcher.m */,
+                               DEE4449C0FABA7860085D36D /* SPTouch.h */,
+                               DEE4449D0FABA7860085D36D /* SPTouch.m */,
+                               DEE444A80FABABBB0085D36D /* SPTouchEvent.h */,
+                               DEE444A90FABABBB0085D36D /* SPTouchEvent.m */,
+                               DEEEB8C80FAA2C300059D72B /* SPEnterFrameEvent.h */,
+                               DEEEB8C90FAA2C300059D72B /* SPEnterFrameEvent.m */,
+                       );
+                       name = Events;
+                       sourceTree = "<group>";
+               };
+               DEDCD3CE0FADF2100022011C /* System */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DEC2B6790F6D4CE90063AB1D /* SPView.h */,
+                               DEC2B67A0F6D4CE90063AB1D /* SPView.m */,
+                       );
+                       name = System;
+                       sourceTree = "<group>";
+               };
+               DEE09D771083647D00ECC896 /* Text */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DED4EFC90FF9439D0093AD29 /* SPTextField.h */,
+                               DED4EFCA0FF9439D0093AD29 /* SPTextField.m */,
+                               DEE09D78108364A900ECC896 /* SPBitmapFont.h */,
+                               DEE09D79108364A900ECC896 /* SPBitmapFont.m */,
+                               DEE09D7C108369AE00ECC896 /* SPBitmapChar.h */,
+                               DEE09D7D108369AE00ECC896 /* SPBitmapChar.m */,
+                       );
+                       name = Text;
+                       sourceTree = "<group>";
+               };
+               DEE202BD0FBDAD2900B8A467 /* Animation */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DEED1736108A4FE30071438F /* Internal */,
+                               DE70442A0FB618AE007F5ECC /* SPAnimatable.h */,
+                               DE7044260FB61506007F5ECC /* SPJuggler.h */,
+                               DE7044270FB61506007F5ECC /* SPJuggler.m */,
+                               DED859430FB883EE00D3D7D2 /* SPTransitions.h */,
+                               DED859440FB883EE00D3D7D2 /* SPTransitions.m */,
+                               DE7044750FB62080007F5ECC /* SPTween.h */,
+                               DE7044760FB62080007F5ECC /* SPTween.m */,
+                               DEFB1B93100926260022C117 /* SPDelayedInvocation.h */,
+                               DEFB1B94100926260022C117 /* SPDelayedInvocation.m */,
+                       );
+                       name = Animation;
+                       sourceTree = "<group>";
+               };
+               DEE63A4A11AED36700D60321 /* Audio */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DEF1731511B064A300A11DD7 /* AVFoundation */,
+                               DEF1731411B0649A00A11DD7 /* OpenAL */,
+                               DEE63A4B11AED38100D60321 /* SPAudioEngine.h */,
+                               DEE63A4C11AED38100D60321 /* SPAudioEngine.m */,
+                               DEE63A4D11AED38100D60321 /* SPSound.h */,
+                               DEE63A4E11AED38100D60321 /* SPSound.m */,
+                               DEE63A4F11AED38100D60321 /* SPSoundChannel.h */,
+                               DEE63A5011AED38100D60321 /* SPSoundChannel.m */,
+                       );
+                       name = Audio;
+                       sourceTree = "<group>";
+               };
+               DEE968F51086553D001D57E9 /* Textures */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DE0853F80FEC2CFF00DAF53C /* SPTexture.h */,
+                               DE0853F90FEC2CFF00DAF53C /* SPTexture.m */,
+                               DECF84310FF649D50026A4ED /* SPGLTexture.h */,
+                               DECF84320FF649D50026A4ED /* SPGLTexture.m */,
+                               DECF84B90FF681BA0026A4ED /* SPSubTexture.h */,
+                               DECF84BA0FF681BA0026A4ED /* SPSubTexture.m */,
+                               DE13D18912AADBF6000C77E6 /* SPRenderTexture.h */,
+                               DE13D18A12AADBF6000C77E6 /* SPRenderTexture.m */,
+                               DECF84260FF619150026A4ED /* SPTextureAtlas.h */,
+                               DECF84270FF619150026A4ED /* SPTextureAtlas.m */,
+                       );
+                       name = Textures;
+                       sourceTree = "<group>";
+               };
+               DEED1736108A4FE30071438F /* Internal */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DEED1737108A50000071438F /* SPTweenedProperty.h */,
+                               DEED1738108A50000071438F /* SPTweenedProperty.m */,
+                       );
+                       name = Internal;
+                       sourceTree = "<group>";
+               };
+               DEF1731411B0649A00A11DD7 /* OpenAL */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DEF1730C11B0645A00A11DD7 /* SPALSound.h */,
+                               DEF1730D11B0645A00A11DD7 /* SPALSound.m */,
+                               DEF1731011B0648B00A11DD7 /* SPALSoundChannel.h */,
+                               DEF1731111B0648B00A11DD7 /* SPALSoundChannel.m */,
+                       );
+                       name = OpenAL;
+                       sourceTree = "<group>";
+               };
+               DEF1731511B064A300A11DD7 /* AVFoundation */ = {
+                       isa = PBXGroup;
+                       children = (
+                               DEF1735A11B075B000A11DD7 /* SPAVSound.h */,
+                               DEF1735B11B075B000A11DD7 /* SPAVSound.m */,
+                               DEF1735E11B075BC00A11DD7 /* SPAVSoundChannel.h */,
+                               DEF1735F11B075BC00A11DD7 /* SPAVSoundChannel.m */,
+                       );
+                       name = AVFoundation;
+                       sourceTree = "<group>";
+               };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+               DEFE4BBE101B317600E22471 /* Headers */ = {
+                       isa = PBXHeadersBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               DE13D18B12AADBF6000C77E6 /* SPRenderTexture.h in Headers */,
+                               DE33072512D2EBCD009CC5E7 /* SPUtils.h in Headers */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+               DEEA9334101E32A30071DD21 /* UnitTests */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = DEEA9339101E32A40071DD21 /* Build configuration list for PBXNativeTarget "UnitTests" */;
+                       buildPhases = (
+                               DEEA9331101E32A30071DD21 /* Sources */,
+                               DEEA9332101E32A30071DD21 /* Frameworks */,
+                               DEEA9333101E32A30071DD21 /* ShellScript */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                               DEEA934D101E33ED0071DD21 /* PBXTargetDependency */,
+                       );
+                       name = UnitTests;
+                       productName = UnitTest;
+                       productReference = DEEA9335101E32A30071DD21 /* UnitTest.octest */;
+                       productType = "com.apple.product-type.bundle";
+               };
+               DEFE4BC1101B317600E22471 /* Sparrow */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = DEFE4BCC101B317A00E22471 /* Build configuration list for PBXNativeTarget "Sparrow" */;
+                       buildPhases = (
+                               DEFE4BBE101B317600E22471 /* Headers */,
+                               DEFE4BBF101B317600E22471 /* Sources */,
+                               DEFE4BC0101B317600E22471 /* Frameworks */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                       );
+                       name = Sparrow;
+                       productName = SparrowLib;
+                       productReference = DEFE4BC2101B317600E22471 /* libSparrow.a */;
+                       productType = "com.apple.product-type.library.static";
+               };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+               29B97313FDCFA39411CA2CEA /* Project object */ = {
+                       isa = PBXProject;
+                       buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Sparrow" */;
+                       compatibilityVersion = "Xcode 3.1";
+                       developmentRegion = English;
+                       hasScannedForEncodings = 1;
+                       knownRegions = (
+                               English,
+                               Japanese,
+                               French,
+                               German,
+                       );
+                       mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */;
+                       projectDirPath = "";
+                       projectRoot = "";
+                       targets = (
+                               DEFE4BC1101B317600E22471 /* Sparrow */,
+                               DEEA9334101E32A30071DD21 /* UnitTests */,
+                       );
+               };
+/* End PBXProject section */
+
+/* Begin PBXShellScriptBuildPhase section */
+               DEEA9333101E32A30071DD21 /* ShellScript */ = {
+                       isa = PBXShellScriptBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       inputPaths = (
+                       );
+                       outputPaths = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+                       shellPath = /bin/sh;
+                       shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n\n# in Xcode 3.2.4, use the following line instead:\n# \"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\" 1> /tmp/RunUnitTests.out  \n";
+               };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+               DEEA9331101E32A30071DD21 /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               DE14412E12421EA600FFA95A /* SPNSExtensionsTest.m in Sources */,
+                               DE14413612421F4700FFA95A /* SPImageTest.m in Sources */,
+                               DE14413712421F4800FFA95A /* SPDisplayObjectTest.m in Sources */,
+                               DE14413812421F4800FFA95A /* SPMovieClipTest.m in Sources */,
+                               DE14413912421F4900FFA95A /* SPRectangleTest.m in Sources */,
+                               DE14413A12421F4900FFA95A /* SPDisplayObjectContainerTest.m in Sources */,
+                               DE14413B12421F4A00FFA95A /* SPEventDispatcherTest.m in Sources */,
+                               DE14413C12421F4B00FFA95A /* SPDelayedInvocationTest.m in Sources */,
+                               DE14413D12421F4B00FFA95A /* SPMatrixTest.m in Sources */,
+                               DE14413E12421F4B00FFA95A /* SPPointTest.m in Sources */,
+                               DE14413F12421F4C00FFA95A /* SPQuadTest.m in Sources */,
+                               DE14414012421F4C00FFA95A /* SPJugglerTest.m in Sources */,
+                               DE14414112421F4D00FFA95A /* SPTweenTest.m in Sources */,
+                               DE14414212421F4E00FFA95A /* SPStageTest.m in Sources */,
+                               DE33072B12D2ED1A009CC5E7 /* SPUtilsTest.m in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               DEFE4BBF101B317600E22471 /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               DEFE4BE6101B31DF00E22471 /* SPTextureAtlas.m in Sources */,
+                               DEFE4C38101B5FB100E22471 /* SPRendering.m in Sources */,
+                               DEFE4C3A101B5FB100E22471 /* SPTouchProcessor.m in Sources */,
+                               DEFE4BE2101B31DF00E22471 /* SPMatrix.m in Sources */,
+                               DEFE4BE3101B31DF00E22471 /* SPPoint.m in Sources */,
+                               DEFE4BE4101B31DF00E22471 /* SPRectangle.m in Sources */,
+                               DEFE4BD7101B31DF00E22471 /* SPDisplayObject.m in Sources */,
+                               DEFE4BD8101B31DF00E22471 /* SPDisplayObjectContainer.m in Sources */,
+                               DEFE4BD9101B31DF00E22471 /* SPSprite.m in Sources */,
+                               DEFE4BDA101B31DF00E22471 /* SPStage.m in Sources */,
+                               DEFE4BDB101B31DF00E22471 /* SPQuad.m in Sources */,
+                               DEFE4BDC101B31DF00E22471 /* SPImage.m in Sources */,
+                               DEFE4BDD101B31DF00E22471 /* SPTextField.m in Sources */,
+                               DEFE4BDE101B31DF00E22471 /* SPTexture.m in Sources */,
+                               DEFE4BDF101B31DF00E22471 /* SPGLTexture.m in Sources */,
+                               DEFE4BE0101B31DF00E22471 /* SPSubTexture.m in Sources */,
+                               DEFE4BE1101B31DF00E22471 /* SPButton.m in Sources */,
+                               DEFE4BD2101B31DF00E22471 /* SPEvent.m in Sources */,
+                               DEFE4BD3101B31DF00E22471 /* SPEventDispatcher.m in Sources */,
+                               DEFE4BD4101B31DF00E22471 /* SPTouch.m in Sources */,
+                               DEFE4BD5101B31DF00E22471 /* SPTouchEvent.m in Sources */,
+                               DEFE4BD6101B31DF00E22471 /* SPEnterFrameEvent.m in Sources */,
+                               DEFE4BD1101B31DF00E22471 /* SPView.m in Sources */,
+                               DEFE4BCE101B31DF00E22471 /* SPTransitions.m in Sources */,
+                               DE019C391026360B00ECB0AC /* SPTween.m in Sources */,
+                               DE019C3A1026361200ECB0AC /* SPDelayedInvocation.m in Sources */,
+                               DE019C3B1026363D00ECB0AC /* SPNSExtensions.m in Sources */,
+                               DE019C3C1026364800ECB0AC /* SPJuggler.m in Sources */,
+                               DED9B51E10629D9F00989853 /* SPPoolObject.m in Sources */,
+                               DE20D9CC10713B0C006658C9 /* SPRenderSupport.m in Sources */,
+                               DEE09D7B108364A900ECC896 /* SPBitmapFont.m in Sources */,
+                               DEE09D7F108369AE00ECC896 /* SPBitmapChar.m in Sources */,
+                               DEED173A108A50000071438F /* SPTweenedProperty.m in Sources */,
+                               DEE63A5211AED38100D60321 /* SPAudioEngine.m in Sources */,
+                               DEE63A5411AED38100D60321 /* SPSound.m in Sources */,
+                               DEE63A5611AED38100D60321 /* SPSoundChannel.m in Sources */,
+                               DEF1730F11B0645A00A11DD7 /* SPALSound.m in Sources */,
+                               DEF1731311B0648B00A11DD7 /* SPALSoundChannel.m in Sources */,
+                               DEF1735D11B075B000A11DD7 /* SPAVSound.m in Sources */,
+                               DEF1736111B075BC00A11DD7 /* SPAVSoundChannel.m in Sources */,
+                               DEE94E8311B43DE60000FE20 /* SPMovieClip.m in Sources */,
+                               DEE6516B11F9DF200065207E /* SPCompiledSprite.m in Sources */,
+                               DE13D18C12AADBF6000C77E6 /* SPRenderTexture.m in Sources */,
+                               DE33072612D2EBCD009CC5E7 /* SPUtils.m in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+               DEEA934D101E33ED0071DD21 /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = DEFE4BC1101B317600E22471 /* Sparrow */;
+                       targetProxy = DEEA934C101E33ED0071DD21 /* PBXContainerItemProxy */;
+               };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+               C01FCF4F08A954540054247B /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+                               "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+                               GCC_C_LANGUAGE_STANDARD = c99;
+                               GCC_THUMB_SUPPORT = NO;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               PREBINDING = NO;
+                               "PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
+                               SDKROOT = iphoneos;
+                       };
+                       name = Debug;
+               };
+               C01FCF5008A954540054247B /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+                               "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+                               GCC_C_LANGUAGE_STANDARD = c99;
+                               GCC_THUMB_SUPPORT = NO;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               PREBINDING = NO;
+                               SDKROOT = iphoneos;
+                       };
+                       name = Release;
+               };
+               DEEA9337101E32A40071DD21 /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ALWAYS_SEARCH_USER_PATHS = NO;
+                               COPY_PHASE_STRIP = NO;
+                               FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks";
+                               GCC_DYNAMIC_NO_PIC = NO;
+                               GCC_ENABLE_FIX_AND_CONTINUE = NO;
+                               GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+                               GCC_OPTIMIZATION_LEVEL = 0;
+                               INFOPLIST_FILE = "UnitTests-Info.plist";
+                               OTHER_LDFLAGS = (
+                                       "-framework",
+                                       Foundation,
+                                       "-framework",
+                                       SenTestingKit,
+                                       "-framework",
+                                       UIKit,
+                                       "-ObjC",
+                                       "-all_load",
+                               );
+                               PREBINDING = NO;
+                               PRODUCT_NAME = UnitTest;
+                               WRAPPER_EXTENSION = octest;
+                       };
+                       name = Debug;
+               };
+               DEEA9338101E32A40071DD21 /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ALWAYS_SEARCH_USER_PATHS = NO;
+                               COPY_PHASE_STRIP = YES;
+                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks";
+                               GCC_ENABLE_FIX_AND_CONTINUE = NO;
+                               GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+                               INFOPLIST_FILE = "UnitTests-Info.plist";
+                               OTHER_LDFLAGS = (
+                                       "-framework",
+                                       Foundation,
+                                       "-framework",
+                                       SenTestingKit,
+                                       "-framework",
+                                       UIKit,
+                                       "-ObjC",
+                                       "-all_load",
+                               );
+                               PREBINDING = NO;
+                               PRODUCT_NAME = UnitTest;
+                               WRAPPER_EXTENSION = octest;
+                               ZERO_LINK = NO;
+                       };
+                       name = Release;
+               };
+               DEFE4BC3101B317700E22471 /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ALWAYS_SEARCH_USER_PATHS = NO;
+                               "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+                               COPY_PHASE_STRIP = NO;
+                               GCC_DYNAMIC_NO_PIC = NO;
+                               GCC_OPTIMIZATION_LEVEL = 0;
+                               GCC_PREPROCESSOR_DEFINITIONS = DEBUG;
+                               GCC_THUMB_SUPPORT = NO;
+                               GCC_WARN_PEDANTIC = NO;
+                               OTHER_LDFLAGS = "";
+                               PREBINDING = NO;
+                               PRODUCT_NAME = Sparrow;
+                               "PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
+                               SKIP_INSTALL = YES;
+                       };
+                       name = Debug;
+               };
+               DEFE4BC4101B317700E22471 /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ALWAYS_SEARCH_USER_PATHS = NO;
+                               "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+                               COPY_PHASE_STRIP = YES;
+                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               GCC_ENABLE_FIX_AND_CONTINUE = NO;
+                               GCC_PREPROCESSOR_DEFINITIONS = "";
+                               GCC_THUMB_SUPPORT = NO;
+                               GCC_WARN_PEDANTIC = NO;
+                               OTHER_LDFLAGS = "";
+                               PREBINDING = NO;
+                               PRODUCT_NAME = Sparrow;
+                               "PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
+                               SKIP_INSTALL = YES;
+                               ZERO_LINK = NO;
+                       };
+                       name = Release;
+               };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+               C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Sparrow" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               C01FCF4F08A954540054247B /* Debug */,
+                               C01FCF5008A954540054247B /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               DEEA9339101E32A40071DD21 /* Build configuration list for PBXNativeTarget "UnitTests" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               DEEA9337101E32A40071DD21 /* Debug */,
+                               DEEA9338101E32A40071DD21 /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               DEFE4BCC101B317A00E22471 /* Build configuration list for PBXNativeTarget "Sparrow" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               DEFE4BC3101B317700E22471 /* Debug */,
+                               DEFE4BC4101B317700E22471 /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+/* End XCConfigurationList section */
+       };
+       rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
+}
diff --git a/libs/sparrow/src/UnitTests-Info.plist b/libs/sparrow/src/UnitTests-Info.plist
new file mode 100644 (file)
index 0000000..ca50071
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>CFBundleDevelopmentRegion</key>
+       <string>English</string>
+       <key>CFBundleExecutable</key>
+       <string>${EXECUTABLE_NAME}</string>
+       <key>CFBundleIdentifier</key>
+       <string>com.incognitek.${PRODUCT_NAME:identifier}</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundlePackageType</key>
+       <string>BNDL</string>
+       <key>CFBundleSignature</key>
+       <string>????</string>
+       <key>CFBundleVersion</key>
+       <string>1.0</string>
+</dict>
+</plist>
diff --git a/libs/sparrow/src/UnitTests/SPDelayedInvocationTest.m b/libs/sparrow/src/UnitTests/SPDelayedInvocationTest.m
new file mode 100644 (file)
index 0000000..4a05fbe
--- /dev/null
@@ -0,0 +1,121 @@
+//
+//  SPDelayedInvocationTest.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 10.07.10.
+//  Copyright 2010 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "SPDelayedInvocation.h"
+#import "SPMacros.h"
+
+// -------------------------------------------------------------------------------------------------
+
+static int dummyDeallocCount = 0;
+
+@interface DummyClass : NSObject
+
+@end
+
+@implementation DummyClass
+
+- (void)dealloc
+{
+    ++dummyDeallocCount;
+    [super dealloc];
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPDelayedInvocationTest : SenTestCase 
+{
+    int mCallCount;
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPDelayedInvocationTest
+
+- (void)setUp
+{
+    mCallCount = 0;
+    dummyDeallocCount = 0;
+}
+
+- (void)simpleMethod
+{
+    ++mCallCount;
+}
+
+- (void)methodWithArgument:(DummyClass *)dummy
+{
+    [dummy description]; // just to check that dummy has not been released
+    ++mCallCount;    
+}
+
+- (void)testSimpleDelay
+{    
+    id delayedInv = [[SPDelayedInvocation alloc] initWithTarget:self delay:1.0f];
+    [delayedInv simpleMethod];
+    
+    STAssertEquals(0, mCallCount, @"Delayed Invocation triggered too soon");
+    [delayedInv advanceTime:0.5f];
+    
+    STAssertEquals(0, mCallCount, @"Delayed Invocation triggered too soon");
+    [delayedInv advanceTime:0.49f];
+    
+    STAssertEquals(0, mCallCount, @"Delayed Invocation triggered too soon");
+    
+    [delayedInv advanceTime:0.1f];
+    STAssertEquals(1, mCallCount, @"Delayed Invocation did not trigger");
+    
+    [delayedInv advanceTime:0.1f];
+    STAssertEquals(1, mCallCount, @"Delayed Invocation triggered too often");
+    
+    [delayedInv release];
+}
+
+- (void)testDelayWithArguments
+{
+    SP_CREATE_POOL(pool);
+    
+    // test dummy mechanism
+    
+    DummyClass *dummy = [[DummyClass alloc] init];
+    [dummy release];
+    STAssertEquals(1, dummyDeallocCount, @"dummy release not counted");
+    
+    // now test if retain/release cycle of delayed invocation works
+    
+    dummy = [[DummyClass alloc] init];
+    id delayedInv = [[SPDelayedInvocation alloc] initWithTarget:self delay:1.0f];
+    [delayedInv methodWithArgument:dummy];
+    
+    STAssertEquals(0, mCallCount, @"Delayed Invocation triggered too soon");
+    
+    [dummy release];
+    STAssertEquals(1, dummyDeallocCount, @"Argument not retained");
+    
+    [delayedInv advanceTime:1.0f];
+    STAssertEquals(1, mCallCount, @"Delayed Invocation did not trigger");
+    
+    SP_RELEASE_POOL(pool);
+    
+    [delayedInv release];
+    STAssertEquals(2, dummyDeallocCount, @"Argument not released");
+}
+
+@end
+
+#endif
\ No newline at end of file
diff --git a/libs/sparrow/src/UnitTests/SPDisplayObjectContainerTest.m b/libs/sparrow/src/UnitTests/SPDisplayObjectContainerTest.m
new file mode 100644 (file)
index 0000000..45962ec
--- /dev/null
@@ -0,0 +1,416 @@
+//
+//  SPDisplayObjectContainerTest.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 13.04.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPMatrix.h"
+#import "SPMacros.h"
+#import "SPPoint.h"
+#import "SPSprite.h"
+#import "SPQuad.h"
+#import "SPStage.h"
+#import "SPRectangle.h"
+#import "SPDisplayObject_Internal.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPDisplayObjectContainerTest : SenTestCase 
+{
+    int mAdded;
+    int mAddedToStage;
+    int mRemoved;
+    int mRemovedFromStage;
+    int mEventCount;
+    SPSprite *mTestSprite;
+}
+
+- (void)addQuadToSprite:(SPSprite*)sprite;
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPDisplayObjectContainerTest
+
+#define E 0.0001f
+
+- (void) setUp
+{
+    mAdded = mAddedToStage = mRemoved = mRemovedFromStage = mEventCount = 0;    
+    mTestSprite = [[SPSprite alloc] init];
+}
+
+- (void) tearDown
+{
+    [mTestSprite release];
+}
+
+- (void)testChildParentHandling
+{
+    SPSprite *parent = [[SPSprite alloc] init];
+    SPSprite *child1 = [[SPSprite alloc] init];
+    SPSprite *child2 = [[SPSprite alloc] init];
+    
+    STAssertEquals(0, parent.numChildren, @"wrong number of children");
+    STAssertNil(child1.parent, @"parent not nil");
+    
+    [parent addChild:child1];
+    STAssertEquals(1, parent.numChildren, @"wrong number of children");
+    STAssertEqualObjects(parent, child1.parent, @"invalid parent");
+    
+    [parent addChild:child2];
+    STAssertEquals(2, parent.numChildren, @"wrong number of children");
+    STAssertEqualObjects(parent, child2.parent, @"invalid parent");
+    STAssertEqualObjects(child1, [parent childAtIndex:0], @"wrong child index");
+    STAssertEqualObjects(child2, [parent childAtIndex:1], @"wrong child index");
+    
+    [parent removeChild:child1];
+    STAssertNil(child1.parent, @"parent not nil");
+    STAssertEqualObjects(child2, [parent childAtIndex:0], @"wrong child index");
+    STAssertNoThrow([child1 removeFromParent], @"exception raised");
+    
+    [child2 addChild:child1];
+    STAssertTrue([parent containsChild:child1], @"child not found");
+    STAssertTrue([parent containsChild:child2], @"child not found");
+    STAssertEqualObjects(child2, child1.parent, @"invalid parent");
+    
+    [parent addChild:child1 atIndex:0];
+    STAssertEqualObjects(parent, child1.parent, @"invalid parent");
+    STAssertFalse([child2 containsChild:child1], @"invalid connection");
+    STAssertEqualObjects(child1, [parent childAtIndex:0], @"wrong child");
+    STAssertEqualObjects(child2, [parent childAtIndex:1], @"wrong child");    
+    
+    [child2 release];
+    [child1 release];
+    [parent release];
+}
+
+- (void)testWidthAndHeight
+{
+    SPSprite *sprite = [[SPSprite alloc] init];
+    
+    SPQuad *quad1 = [[SPQuad alloc] initWithWidth:10 height:20];    
+    quad1.x = -10;
+    quad1.y = -15;
+    
+    SPQuad *quad2 = [[SPQuad alloc] initWithWidth:15 height:25];
+    quad2.x = 30;
+    quad2.y = 25;
+    
+    [sprite addChild:quad1];
+    [sprite addChild:quad2];
+    
+    STAssertTrue(SP_IS_FLOAT_EQUAL(55.0f, sprite.width), @"wrong width: %f", sprite.width);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(65.0f, sprite.height), @"wrong height: %f", sprite.height);
+    
+    quad1.rotation = PI_HALF;
+    STAssertTrue(SP_IS_FLOAT_EQUAL(75.0f, sprite.width), @"wrong width: %f", sprite.width);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(65.0f, sprite.height), @"wrong height: %f", sprite.height);
+    
+    quad1.rotation = PI;
+    STAssertTrue(SP_IS_FLOAT_EQUAL(65.0f, sprite.width), @"wrong width: %f", sprite.width);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(85.0f, sprite.height), @"wrong height: %f", sprite.height);
+    
+    [quad1 release];
+    [quad2 release];
+    [sprite release];
+}
+
+- (void)testBounds
+{
+    SPQuad *quad = [[SPQuad alloc] initWithWidth:10 height:20];
+    quad.x = -10;
+    quad.y = 10;
+    quad.rotation = PI_HALF;
+    
+    SPSprite *sprite = [[SPSprite alloc] init];
+    [sprite addChild:quad];
+    
+    SPRectangle *bounds = [sprite bounds];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(-30, bounds.x), @"wrong bounds.x: %f", bounds.x);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(10, bounds.y), @"wrong bounds.y: %f", bounds.y);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(20, bounds.width), @"wrong bounds.width: %f", bounds.width);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(10, bounds.height), @"wrong bounds.height: %f", bounds.height);    
+    
+    bounds = [sprite boundsInSpace:sprite];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(-30, bounds.x), @"wrong bounds.x: %f", bounds.x);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(10, bounds.y), @"wrong bounds.y: %f", bounds.y);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(20, bounds.width), @"wrong bounds.width: %f", bounds.width);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(10, bounds.height), @"wrong bounds.height: %f", bounds.height); 
+    
+    [sprite release];
+    [quad release];    
+}
+
+- (void)testBoundsInSpace
+{
+    SPSprite *root = [[SPSprite alloc] init];
+    
+    SPSprite *spriteA = [[SPSprite alloc] init];
+    spriteA.x = 50;
+    spriteA.y = 50;
+    [self addQuadToSprite:spriteA];
+    [root addChild:spriteA];
+    [spriteA release];
+    
+    SPSprite *spriteA1 = [[SPSprite alloc] init];
+    spriteA1.x = 150;
+    spriteA1.y = 50;
+    spriteA1.scaleX = spriteA1.scaleY = 0.5;
+    [self addQuadToSprite:spriteA1];
+    [spriteA addChild:spriteA1];
+    [spriteA1 release];
+    
+    SPSprite *spriteA11 = [[SPSprite alloc] init];
+    spriteA11.x = 25;
+    spriteA11.y = 50;
+    spriteA11.scaleX = spriteA11.scaleY = 0.5;
+    [self addQuadToSprite:spriteA11];
+    [spriteA1 addChild:spriteA11];
+    [spriteA11 release];
+    
+    SPSprite *spriteA2 = [[SPSprite alloc] init];
+    spriteA2.x = 50;
+    spriteA2.y = 150;
+    spriteA2.scaleX = spriteA2.scaleY = 0.5;
+    [self addQuadToSprite:spriteA2];
+    [spriteA addChild:spriteA2];
+    [spriteA2 release];
+    
+    SPSprite *spriteA21 = [[SPSprite alloc] init];
+    spriteA21.x = 50;
+    spriteA21.y = 25;
+    spriteA21.scaleX = spriteA21.scaleY = 0.5;
+    [self addQuadToSprite:spriteA21];
+    [spriteA2 addChild:spriteA21];    
+    [spriteA21 release];
+    
+    // ---
+    
+    SPRectangle *bounds = [spriteA21 boundsInSpace:spriteA11];
+    SPRectangle *expectedBounds = [SPRectangle rectangleWithX:-350 y:350 width:100 height:100];
+    STAssertEqualObjects(expectedBounds, bounds, @"wrong bounds: %@", bounds);    
+    
+    // now rotate as well
+    
+    spriteA11.rotation = PI/4.0f;
+    spriteA21.rotation = -PI/4.0f;
+    
+    bounds = [spriteA21 boundsInSpace:spriteA11];
+    expectedBounds = [SPRectangle rectangleWithX:0 y:394.974762 width:100 height:100];
+    STAssertEqualObjects(expectedBounds, bounds, @"wrong bounds: %@", bounds);    
+    
+    [root release];
+}
+
+- (void)testSize
+{
+    SPQuad *quad1 = [SPQuad quadWithWidth:100 height:100];
+    SPQuad *quad2 = [SPQuad quadWithWidth:100 height:100];
+    quad2.x = quad2.y = 100;
+    
+    SPSprite *sprite = [SPSprite sprite];
+    SPSprite *childSprite = [SPSprite sprite];
+    
+    [sprite addChild:childSprite];
+    [childSprite addChild:quad1];
+    [childSprite addChild:quad2];
+        
+    
+    STAssertEqualsWithAccuracy(200.0f, sprite.width, E, @"wrong width: %f", sprite.width);
+    STAssertEqualsWithAccuracy(200.0f, sprite.height, E, @"wrong height: %f", sprite.height);
+        
+    sprite.scaleX = 2;
+    sprite.scaleY = 2;
+    STAssertEqualsWithAccuracy(400.0f, sprite.width, E, @"wrong width: %f", sprite.width);
+    STAssertEqualsWithAccuracy(400.0f, sprite.height, E, @"wrong height: %f", sprite.height);    
+}
+
+- (void)addQuadToSprite:(SPSprite*)sprite
+{
+    SPQuad *quad = [[SPQuad alloc] initWithWidth:100 height:100];
+    quad.alpha = 0.2f;
+    [sprite addChild:quad];
+    [quad release];
+    return;
+}
+
+- (void)testDisplayListEvents
+{
+    SPStage *stage = [[SPStage alloc] init];
+    SPSprite *sprite = [[SPSprite alloc] init];
+    SPQuad *quad = [[SPQuad alloc] initWithWidth:20 height:20];
+    
+    [quad addEventListener:@selector(onAdded:) atObject:self forType:SP_EVENT_TYPE_ADDED];
+    [quad addEventListener:@selector(onAddedToStage:) atObject:self forType:SP_EVENT_TYPE_ADDED_TO_STAGE];
+    [quad addEventListener:@selector(onRemoved:) atObject:self forType:SP_EVENT_TYPE_REMOVED];
+    [quad addEventListener:@selector(onRemovedFromStage:) atObject:self forType:SP_EVENT_TYPE_REMOVED_FROM_STAGE];
+    
+    [sprite addChild:quad];
+    
+    STAssertEquals(1, mAdded, @"failure on event 'added'");
+    STAssertEquals(0, mRemoved, @"failure on event 'removed'");
+    STAssertEquals(0, mAddedToStage, @"failure on event 'addedToStage'");
+    STAssertEquals(0, mRemovedFromStage, @"failure on event 'removedFromStage'");
+    
+    [stage addChild:sprite];
+    
+    STAssertEquals(1, mAdded, @"failure on event 'added'");
+    STAssertEquals(0, mRemoved, @"failure on event 'removed'");
+    STAssertEquals(1, mAddedToStage, @"failure on event 'addedToStage'");
+    STAssertEquals(0, mRemovedFromStage, @"failure on event 'removedFromStage'");
+    
+    [stage removeChild:sprite];
+    
+    STAssertEquals(1, mAdded, @"failure on event 'added'");
+    STAssertEquals(0, mRemoved, @"failure on event 'removed'");
+    STAssertEquals(1, mAddedToStage, @"failure on event 'addedToStage'");
+    STAssertEquals(1, mRemovedFromStage, @"failure on event 'removedFromStage'");
+    
+    [sprite removeChild:quad];
+    
+    STAssertEquals(1, mAdded, @"failure on event 'added'");
+    STAssertEquals(1, mRemoved, @"failure on event 'removed'");
+    STAssertEquals(1, mAddedToStage, @"failure on event 'addedToStage'");
+    STAssertEquals(1, mRemovedFromStage, @"failure on event 'removedFromStage'");
+    
+    [quad removeEventListenersAtObject:self forType:SP_EVENT_TYPE_ADDED];
+    [quad removeEventListenersAtObject:self forType:SP_EVENT_TYPE_ADDED_TO_STAGE];
+    [quad removeEventListenersAtObject:self forType:SP_EVENT_TYPE_REMOVED];
+    [quad removeEventListenersAtObject:self forType:SP_EVENT_TYPE_REMOVED_FROM_STAGE];
+    
+    [quad release];
+    [sprite release];    
+    [stage release];    
+}
+
+- (void)onAdded:(SPEvent*)event { mAdded++; }
+- (void)onRemoved:(SPEvent*)event { mRemoved++; }
+- (void)onAddedToStage:(SPEvent*)event { mAddedToStage++; }
+- (void)onRemovedFromStage:(SPEvent*)event { mRemovedFromStage++; }
+
+- (void)testRemovedFromStage
+{
+    SPStage *stage = [[SPStage alloc] init];
+    [stage addChild:mTestSprite];    
+    [mTestSprite addEventListener:@selector(onTestSpriteRemovedFromStage:) atObject:self
+                          forType:SP_EVENT_TYPE_REMOVED_FROM_STAGE];    
+    [mTestSprite removeFromParent];
+    [mTestSprite removeEventListenersAtObject:self forType:SP_EVENT_TYPE_REMOVED_FROM_STAGE];        
+    [stage release];
+}
+
+- (void)onTestSpriteRemovedFromStage:(SPEvent *)event
+{
+    STAssertNotNil(mTestSprite.stage, @"stage not accessible in removed from stage event");
+}
+
+- (void)testAddExistingChild
+{
+    SPSprite *sprite = [SPSprite sprite];
+    SPQuad *quad = [SPQuad quadWithWidth:100 height:100];
+    [sprite addChild:quad];
+    STAssertNoThrow([sprite addChild:quad], @"Could not add child multiple times");
+}
+
+- (void)testRemoveAllChildren
+{
+    SPSprite *sprite = [SPSprite sprite];
+    
+    STAssertEquals(0, sprite.numChildren, @"wrong number of children");
+    [sprite removeAllChildren];
+    STAssertEquals(0, sprite.numChildren, @"wrong number of children");
+    
+    [sprite addChild:[SPQuad quadWithWidth:100 height:100]];
+    [sprite addChild:[SPQuad quadWithWidth:100 height:100]];    
+
+    STAssertEquals(2, sprite.numChildren, @"wrong number of children");
+    [sprite removeAllChildren];    
+    STAssertEquals(0, sprite.numChildren, @"remove all children did not work");    
+}
+
+- (void)testChildByName
+{
+    SPSprite *parent = [SPSprite sprite];
+    SPSprite *child1 = [SPSprite sprite];
+    SPSprite *child2 = [SPSprite sprite];
+    SPSprite *child3 = [SPSprite sprite];
+    
+    [parent addChild:child1];
+    [parent addChild:child2];
+    [parent addChild:child3];
+    
+    child1.name = @"CHILD";
+    child3.name = @"child";
+    
+    STAssertEqualObjects(child1, [parent childByName:@"CHILD"], @"wrong child returned");
+    STAssertEqualObjects(child3, [parent childByName:@"child"], @"wrong child returned");
+    STAssertNil([parent childByName:@"ChIlD"], @"return child on wrong name");
+}
+
+- (void)testDispatchEventOnChildren
+{
+    SPSprite *parent = [SPSprite sprite];
+
+    SPSprite *child1 = [[SPSprite alloc] init];
+    SPSprite *child2 = [[SPSprite alloc] init];
+    SPSprite *child3 = [[SPSprite alloc] init];
+    
+    [parent addChild:child1];
+    [parent addChild:child2];
+    [parent addChild:child3];
+    
+    child1.name = @"trigger";
+    [child1 addEventListener:@selector(onChildEvent:) atObject:self forType:@"dunno"];
+    [child2 addEventListener:@selector(onChildEvent:) atObject:self forType:@"dunno"];
+    [child3 addEventListener:@selector(onChildEvent:) atObject:self forType:@"dunno"];
+    
+    [child1 release];
+    [child2 release];
+    [child3 release];
+    
+    SPEvent *event = [SPEvent eventWithType:@"dunno"];
+    [parent dispatchEventOnChildren:event];
+    
+    // event should have dispatched to all 3 children, even if the event listener
+    // removes the children from their parent when it reaches child1. Furthermore, it should
+    // not crash.
+    
+    STAssertEquals(3, mEventCount, @"not all children received events!");
+}
+
+- (void)onChildEvent:(SPEvent *)event
+{
+    SPDisplayObject *target = (SPDisplayObject *)event.target;
+    
+    if ([target.name isEqualToString:@"trigger"])
+        [target.parent removeAllChildren];
+    
+    ++mEventCount;
+}
+
+// STAssertEquals(value, value, message, ...)
+// STAssertEqualObjects(object, object, message, ...)
+// STAssertNotNil(object, message, ...)
+// STAssertTrue(expression, message, ...)
+// STAssertFalse(expression, message, ...)
+// STAssertThrows(expression, message, ...) 
+// STAssertThrowsSpecific(expression, exception, message, ...)
+// STAssertNoThrow(expression, message, ...)
+// STFail(message, ...)
+
+@end
+
+#endif
\ No newline at end of file
diff --git a/libs/sparrow/src/UnitTests/SPDisplayObjectTest.m b/libs/sparrow/src/UnitTests/SPDisplayObjectTest.m
new file mode 100644 (file)
index 0000000..35557dd
--- /dev/null
@@ -0,0 +1,256 @@
+//
+//  SPDisplayObjectTest.h
+//  Sparrow
+//
+//  Created by Daniel Sperl on 13.04.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPMatrix.h"
+#import "SPMacros.h"
+#import "SPPoint.h"
+#import "SPSprite.h"
+#import "SPQuad.h"
+
+#define E 0.0001f
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPDisplayObjectTest : SenTestCase 
+{
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPDisplayObjectTest
+
+- (void)testRoot
+{
+    SPSprite *root = [[SPSprite alloc] init];    
+    SPSprite *child = [[SPSprite alloc] init];
+    SPSprite *grandChild = [[SPSprite alloc] init];
+    
+    [root addChild:child];
+    [child addChild:grandChild];
+    
+    STAssertEqualObjects(root, grandChild.root, @"wrong root");
+    
+    [grandChild release];
+    [child release];
+    [root release];    
+}
+
+- (void)testTransformationMatrixToSpace
+{
+    SPSprite *sprite = [SPSprite sprite];
+    SPSprite *child = [SPSprite sprite];
+    child.x = 30;
+    child.y = 20;
+    child.scaleX = 1.2f;
+    child.scaleY = 1.5f;
+    child.rotation = PI/4.0f;    
+    [sprite addChild:child];
+    
+    SPMatrix *matrix = [sprite transformationMatrixToSpace:child];    
+    SPMatrix *expectedMatrix = child.transformationMatrix;
+    [expectedMatrix invert];
+    STAssertEqualObjects(expectedMatrix, matrix, @"wrong matrix");
+
+    matrix = [child transformationMatrixToSpace:sprite];
+    STAssertEqualObjects(child.transformationMatrix, matrix, @"wrong matrix");    
+    
+    // more is tested indirectly via 'testBoundsInSpace' in DisplayObjectContainerTest
+}
+
+- (void)testTransformationMatrix
+{
+    SPSprite *sprite = [[SPSprite alloc] init];
+    sprite.x = 50;
+    sprite.y = 100;
+    sprite.rotation = PI / 4;
+    sprite.scaleX = 0.5;
+    sprite.scaleY = 1.5;
+    
+    SPMatrix *matrix = [[SPMatrix alloc] init];
+    [matrix scaleXBy:sprite.scaleX yBy:sprite.scaleY];
+    [matrix rotateBy:sprite.rotation];
+    [matrix translateXBy:sprite.x yBy:sprite.y];
+    
+    STAssertEqualObjects(sprite.transformationMatrix, matrix, @"wrong matrix");
+    
+    [sprite release];
+    [matrix release];
+}
+
+- (void)testBounds
+{
+    SPQuad *quad = [[SPQuad alloc] initWithWidth:10 height:20];
+    quad.x = -10;
+    quad.y = 10;
+    quad.rotation = PI_HALF;
+    SPRectangle *bounds = quad.bounds;
+    
+    STAssertTrue(SP_IS_FLOAT_EQUAL(-30, bounds.x), @"wrong bounds.x: %f", bounds.x);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(10, bounds.y), @"wrong bounds.y: %f", bounds.y);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(20, bounds.width), @"wrong bounds.width: %f", bounds.width);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(10, bounds.height), @"wrong bounds.height: %f", bounds.height);
+    
+    bounds = [quad boundsInSpace:quad];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(0, bounds.x), @"wrong inner bounds.x: %f", bounds.x);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(0, bounds.y), @"wrong inner bounds.y: %f", bounds.y);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(10, bounds.width), @"wrong inner bounds.width: %f", bounds.width);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(20, bounds.height), @"wrong innter bounds.height: %f", bounds.height);
+    
+    [quad release];
+}
+
+- (void)testZeroSize
+{
+    SPSprite *sprite = [SPSprite sprite];
+    STAssertEqualsWithAccuracy(1.0f, sprite.scaleX, E, @"wrong scaleX value");
+    STAssertEqualsWithAccuracy(1.0f, sprite.scaleY, E, @"wrong scaleY value");
+    
+    // sprite is empty, scaling should thus have no effect!
+    sprite.width = 100;
+    sprite.height = 200;
+    STAssertEqualsWithAccuracy(1.0f, sprite.scaleX, E, @"wrong scaleX value");
+    STAssertEqualsWithAccuracy(1.0f, sprite.scaleY, E, @"wrong scaleY value");
+    STAssertEqualsWithAccuracy(0.0f, sprite.width,  E, @"wrong width");
+    STAssertEqualsWithAccuracy(0.0f, sprite.height, E, @"wrong height");
+    
+    // setting a value to zero should be no problem -- and the original size should be remembered.
+    SPQuad *quad = [SPQuad quadWithWidth:100 height:200];
+    quad.scaleX = 0.0f;
+    quad.scaleY = 0.0f;
+    STAssertEqualsWithAccuracy(0.0f, quad.width,  E, @"wrong width");
+    STAssertEqualsWithAccuracy(0.0f, quad.height, E, @"wrong height");
+
+    quad.scaleX = 1.0f;
+    quad.scaleY = 1.0f;
+    STAssertEqualsWithAccuracy(100.0f, quad.width,  E, @"wrong width");
+    STAssertEqualsWithAccuracy(200.0f, quad.height, E, @"wrong height");
+    STAssertEqualsWithAccuracy(1.0f, quad.scaleX,   E, @"wrong scaleX value");
+    STAssertEqualsWithAccuracy(1.0f, quad.scaleY,   E, @"wrong scaleY value");
+}
+
+- (void)testLocalToGlobal
+{
+    SPSprite *sprite = [[SPSprite alloc] init];
+    sprite.x = 10;
+    sprite.y = 20;    
+    SPSprite *sprite2 = [[SPSprite alloc] init];
+    sprite2.x = 150;
+    sprite2.y = 200;    
+    [sprite addChild:sprite2];
+    
+    SPPoint *localPoint = [SPPoint pointWithX:0 y:0];
+    SPPoint *globalPoint = [sprite2 localToGlobal:localPoint];
+    SPPoint *expectedPoint = [SPPoint pointWithX:160 y:220];    
+    STAssertEqualObjects(expectedPoint, globalPoint, @"wrong global point:");    
+    
+    [sprite release];
+    [sprite2 release];
+}
+
+- (void)testGlobalToLocal
+{
+    SPSprite *sprite = [[SPSprite alloc] init];
+    sprite.x = 10;
+    sprite.y = 20;
+    SPSprite *sprite2 = [[SPSprite alloc] init];
+    sprite2.x = 150;
+    sprite2.y = 200;    
+    [sprite addChild:sprite2];
+    
+    SPPoint *globalPoint = [SPPoint pointWithX:160 y:220];
+    SPPoint *localPoint = [sprite2 globalToLocal:globalPoint];
+    SPPoint *expectedPoint = [SPPoint pointWithX:0 y:0];    
+    STAssertEqualObjects(expectedPoint, localPoint, @"wrong local point");    
+    
+    [sprite release];
+    [sprite2 release];
+}
+
+- (void)testHitTestPoint
+{
+    SPQuad *quad = [[SPQuad alloc] initWithWidth:25 height:10];
+    
+    STAssertNotNil([quad hitTestPoint:[SPPoint pointWithX:15 y:5] forTouch:YES], 
+                   @"point should be inside");
+    STAssertNotNil([quad hitTestPoint:[SPPoint pointWithX:0 y:0] forTouch:YES],  
+                   @"point should be inside");
+    STAssertNotNil([quad hitTestPoint:[SPPoint pointWithX:25 y:0] forTouch:YES], 
+                   @"point should be inside");
+    STAssertNotNil([quad hitTestPoint:[SPPoint pointWithX:25 y:10] forTouch:YES], 
+                   @"point should be inside");
+    STAssertNotNil([quad hitTestPoint:[SPPoint pointWithX:0 y:10] forTouch:YES], 
+                   @"point should be inside");
+    STAssertNil([quad hitTestPoint:[SPPoint pointWithX:-1 y:-1] forTouch:YES], 
+                @"point should be outside");    
+    STAssertNil([quad hitTestPoint:[SPPoint pointWithX:26 y:11] forTouch:YES], 
+                @"point should be outside");
+
+    quad.visible = NO;
+    STAssertNil([quad hitTestPoint:[SPPoint pointWithX:15 y:5] forTouch:YES], 
+                @"hitTest should fail, object invisible");
+        
+    quad.visible = YES;
+    quad.touchable = NO;
+    STAssertNil([quad hitTestPoint:[SPPoint pointWithX:15 y:5] forTouch:YES], 
+                @"hitTest should fail, object untouchable");    
+
+    quad.visible = NO;
+    quad.touchable = NO;
+    STAssertNotNil([quad hitTestPoint:[SPPoint pointWithX:15 y:5] forTouch:NO], 
+                @"hitTest should succeed, this is no touch test");    
+    
+    [quad release];
+}
+
+- (void)testRotation
+{
+    SPQuad *quad = [SPQuad quadWithWidth:100 height:100];
+    
+    quad.rotation = SP_D2R(400);  
+    STAssertEqualsWithAccuracy(SP_D2R(40.0f), quad.rotation, E, @"wrong angle");    
+    quad.rotation = SP_D2R(220); 
+    STAssertEqualsWithAccuracy(SP_D2R(-140.0f), quad.rotation, E, @"wrong angle");    
+    quad.rotation = SP_D2R(180);  
+    STAssertEqualsWithAccuracy(SP_D2R(180.0f), quad.rotation, E, @"wrong angle");    
+    quad.rotation = SP_D2R(-90); 
+    STAssertEqualsWithAccuracy(SP_D2R(-90.0f), quad.rotation, E, @"wrong angle");    
+    quad.rotation = SP_D2R(-179); 
+    STAssertEqualsWithAccuracy(SP_D2R(-179.0f), quad.rotation, E, @"wrong angle");    
+    quad.rotation = SP_D2R(-180); 
+    STAssertEqualsWithAccuracy(SP_D2R(-180.0f), quad.rotation, E, @"wrong angle");    
+    quad.rotation = SP_D2R(-181); 
+    STAssertEqualsWithAccuracy(SP_D2R(179.0f), quad.rotation, E, @"wrong angle");    
+    quad.rotation = SP_D2R(-300); 
+    STAssertEqualsWithAccuracy(SP_D2R(60.0f), quad.rotation, E, @"wrong angle");    
+    quad.rotation = SP_D2R(-370); 
+    STAssertEqualsWithAccuracy(SP_D2R(-10.0f), quad.rotation, E, @"wrong angle");
+}
+
+- (void)testName
+{
+    SPSprite *sprite = [SPSprite sprite];
+    STAssertNil(sprite.name, @"name not nil after initialization");
+    
+    sprite.name = @"hugo";
+    STAssertEqualObjects(@"hugo", sprite.name, @"wrong name");
+}
+
+@end
+
+#endif
\ No newline at end of file
diff --git a/libs/sparrow/src/UnitTests/SPEventDispatcherTest.m b/libs/sparrow/src/UnitTests/SPEventDispatcherTest.m
new file mode 100644 (file)
index 0000000..859303e
--- /dev/null
@@ -0,0 +1,155 @@
+//
+//  SPEventDispatcherTest.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 27.04.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPEventDispatcher.h"
+#import "SPSprite.h"
+#import "SPEvent.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPEventDispatcherTest : SenTestCase 
+{
+    int mTestCounter;
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPEventDispatcherTest
+
+#define EVENT_TYPE @"EVENT_TYPE"
+
+- (void)setUp
+{
+    mTestCounter = 0;
+}
+
+- (void)testAddAndRemoveEventListener
+{
+    SPEventDispatcher *dispatcher = [[SPEventDispatcher alloc] init];
+    [dispatcher addEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];    
+    STAssertTrue([dispatcher hasEventListenerForType:EVENT_TYPE], @"missing event listener");
+
+    [dispatcher removeEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];
+    STAssertFalse([dispatcher hasEventListenerForType:EVENT_TYPE], @"remove failed");    
+    
+    [dispatcher release];
+}
+
+- (void)testRemoveAllEventListenersOfObject
+{
+    SPEventDispatcher *dispatcher = [[SPEventDispatcher alloc] init];
+    [dispatcher addEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];    
+    STAssertTrue([dispatcher hasEventListenerForType:EVENT_TYPE], @"missing event listener");
+    
+    [dispatcher removeEventListenersAtObject:self forType:EVENT_TYPE];
+    STAssertFalse([dispatcher hasEventListenerForType:EVENT_TYPE], @"remove failed");
+    
+    [dispatcher release];
+}
+
+- (void)testSimpleEvent
+{
+    SPEventDispatcher *dispatcher = [[SPEventDispatcher alloc] init];
+    [dispatcher addEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];    
+    [dispatcher dispatchEvent:[SPEvent eventWithType:EVENT_TYPE]];    
+    STAssertEquals(1, mTestCounter, @"event listener not called");    
+    
+    [dispatcher removeEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];    
+    [dispatcher release];
+}
+
+- (void)testBubblingEvent
+{
+    SPSprite *sprite1 = [[SPSprite alloc] init];
+    [sprite1 addEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];
+    
+    SPSprite *sprite2 = [[SPSprite alloc] init];
+    [sprite2 addEventListener:@selector(stopEvent:) atObject:self forType:EVENT_TYPE];
+    
+    SPSprite *sprite3 = [[SPSprite alloc] init];
+    [sprite3 addEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];
+    
+    SPSprite *sprite4 = [[SPSprite alloc] init];
+    [sprite4 addEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];
+    
+    [sprite1 addChild:sprite2];
+    [sprite2 addChild:sprite3];
+    [sprite3 addChild:sprite4];
+    
+    [sprite4 dispatchEvent:[SPEvent eventWithType:EVENT_TYPE]];
+    
+    STAssertEquals(1, mTestCounter, @"event bubbled, but should not have");
+    mTestCounter = 0;
+    
+    [sprite4 dispatchEvent:[SPEvent eventWithType:EVENT_TYPE bubbles:YES]];
+    STAssertEquals(2, mTestCounter, @"event bubbling incorrect");
+    
+    [sprite1 removeEventListenersAtObject:self forType:EVENT_TYPE];
+    [sprite2 removeEventListenersAtObject:self forType:EVENT_TYPE];
+    [sprite3 removeEventListenersAtObject:self forType:EVENT_TYPE];
+    [sprite4 removeEventListenersAtObject:self forType:EVENT_TYPE];
+    [sprite1 release];
+    [sprite2 release];
+    [sprite3 release];
+    [sprite4 release];
+}
+
+- (void)testStopImmediatePropagation
+{
+    SPSprite *sprite = [[SPSprite alloc] init];
+    [sprite addEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];
+    [sprite addEventListener:@selector(onEvent2:) atObject:self forType:EVENT_TYPE];
+    [sprite addEventListener:@selector(stopEventImmediately:) atObject:self forType:EVENT_TYPE];
+    [sprite addEventListener:@selector(onEvent3:) atObject:self forType:EVENT_TYPE];
+    [sprite dispatchEvent:[SPEvent eventWithType:EVENT_TYPE]];    
+    
+    STAssertEquals(2, mTestCounter, @"stopEventImmediately did not work correctly");
+    
+    [sprite removeEventListenersAtObject:self forType:EVENT_TYPE];
+    [sprite release];    
+}
+
+- (void)onEvent:(SPEvent*)event
+{
+    mTestCounter++;
+}
+
+- (void)onEvent2:(SPEvent*)event
+{
+    mTestCounter *= 2;
+}
+
+- (void)onEvent3:(SPEvent*)event
+{
+    mTestCounter += 3;
+}
+
+- (void)stopEvent:(SPEvent*)event
+{
+    [event stopPropagation];
+}
+
+- (void)stopEventImmediately:(SPEvent*)event
+{
+    [event stopImmediatePropagation];
+}
+
+@end
+
+#endif
\ No newline at end of file
diff --git a/libs/sparrow/src/UnitTests/SPImageTest.m b/libs/sparrow/src/UnitTests/SPImageTest.m
new file mode 100644 (file)
index 0000000..514b243
--- /dev/null
@@ -0,0 +1,58 @@
+//
+//  untitled.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 19.06.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPImage.h"
+#import "SPPoint.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPImageTest : SenTestCase 
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPImageTest
+
+- (void)testInit
+{
+    SPImage *image = [[SPImage alloc] init];
+    STAssertEqualObjects([SPPoint pointWithX:0 y:0], [image texCoordsOfVertex:0], @"wrong tex coords!");
+    STAssertEqualObjects([SPPoint pointWithX:1 y:0], [image texCoordsOfVertex:1], @"wrong tex coords!");    
+    STAssertEqualObjects([SPPoint pointWithX:0 y:1], [image texCoordsOfVertex:2], @"wrong tex coords!");    
+    STAssertEqualObjects([SPPoint pointWithX:1 y:1], [image texCoordsOfVertex:3], @"wrong tex coords!");
+    [image release];    
+}
+
+- (void)testSetTexCoords
+{
+    SPImage *image = [[SPImage alloc] init];
+    [image setTexCoords:[SPPoint pointWithX:1 y:2] ofVertex:0];
+    [image setTexCoords:[SPPoint pointWithX:3 y:4] ofVertex:1];
+    [image setTexCoords:[SPPoint pointWithX:5 y:6] ofVertex:2];
+    [image setTexCoords:[SPPoint pointWithX:7 y:8] ofVertex:3];    
+    
+    STAssertEqualObjects([SPPoint pointWithX:1 y:2], [image texCoordsOfVertex:0], @"wrong tex coords!");    
+    STAssertEqualObjects([SPPoint pointWithX:3 y:4], [image texCoordsOfVertex:1], @"wrong tex coords!");    
+    STAssertEqualObjects([SPPoint pointWithX:5 y:6], [image texCoordsOfVertex:2], @"wrong tex coords!");    
+    STAssertEqualObjects([SPPoint pointWithX:7 y:8], [image texCoordsOfVertex:3], @"wrong tex coords!");
+    [image release];    
+}
+
+@end
+
+#endif
\ No newline at end of file
diff --git a/libs/sparrow/src/UnitTests/SPJugglerTest.m b/libs/sparrow/src/UnitTests/SPJugglerTest.m
new file mode 100644 (file)
index 0000000..2bcb26f
--- /dev/null
@@ -0,0 +1,109 @@
+//
+//  SPJugglerTest.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 27.08.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+
+#import "SPEventDispatcher.h"
+#import "SPEvent.h"
+#import "SPQuad.h"
+#import "SPTween.h"
+#import "SPJuggler.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPJugglerTest : SenTestCase 
+{
+    SPJuggler *mJuggler;
+    SPQuad *mQuad;    
+    BOOL mStartedReached;
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPJugglerTest
+
+- (void)setUp
+{
+    mStartedReached = NO;
+}
+
+- (void)testModificationWhileInEvent
+{    
+    mJuggler = [[SPJuggler alloc] init];
+    
+    SPQuad *quad = [[SPQuad alloc] initWithWidth:100 height:100];    
+    SPTween *tween = [SPTween tweenWithTarget:quad time:1.0f];
+    [tween addEventListener:@selector(onTweenCompleted:) atObject:self 
+                    forType:SP_EVENT_TYPE_TWEEN_COMPLETED];    
+    [mJuggler addObject:tween];
+    
+    [mJuggler advanceTime:0.4]; // -> 0.4 (start)
+    [mJuggler advanceTime:0.4]; // -> 0.8 (update)    
+    
+    STAssertNoThrow([mJuggler advanceTime:0.4], // -> 1.2 (complete)
+                    @"juggler could not cope with modification in tween callback");
+    
+    [mJuggler advanceTime:0.4]; // 1.6 (start of new tween)
+    STAssertTrue(mStartedReached, @"juggler ignored modification made in callback");    
+    
+    [mJuggler release];
+    [mQuad release];
+    
+    mJuggler = nil;
+    mQuad = nil;
+}
+
+- (void)testRemoveTweensWithTarget
+{
+    mJuggler = [SPJuggler juggler];
+    
+    SPQuad *quad1 = [SPQuad quadWithWidth:100 height:100];
+    SPQuad *quad2 = [SPQuad quadWithWidth:200 height:200];
+    
+    SPTween *tween1 = [SPTween tweenWithTarget:quad1 time:1.0];    
+    SPTween *tween2 = [SPTween tweenWithTarget:quad2 time:1.0];
+    
+    [tween1 animateProperty:@"rotation" targetValue:1.0f];
+    [tween2 animateProperty:@"rotation" targetValue:1.0f];
+    
+    [mJuggler addObject:tween1];
+    [mJuggler addObject:tween2];
+    
+    [mJuggler removeTweensWithTarget:quad1];    
+    [mJuggler advanceTime:1.0];
+    
+    STAssertEquals(0.0f, quad1.rotation, @"removed tween was advanced");
+    STAssertEquals(1.0f, quad2.rotation, @"wrong tween was removed");
+    
+    mJuggler = nil;
+}
+
+- (void)onTweenCompleted:(SPEvent*)event
+{
+    SPTween *tween = [SPTween tweenWithTarget:mQuad time:1.0f];        
+    [tween addEventListener:@selector(onTweenStarted:) atObject:self 
+                    forType:SP_EVENT_TYPE_TWEEN_STARTED];
+    [mJuggler addObject:tween];
+}
+
+- (void)onTweenStarted:(SPEvent*)event
+{
+    mStartedReached = YES;
+}
+
+@end
+
+#endif
\ No newline at end of file
diff --git a/libs/sparrow/src/UnitTests/SPMatrixTest.m b/libs/sparrow/src/UnitTests/SPMatrixTest.m
new file mode 100644 (file)
index 0000000..a36868b
--- /dev/null
@@ -0,0 +1,164 @@
+//
+//  SPMatrixTest.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 26.03.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPMatrix.h"
+#import "SPPoint.h"
+#import "SPMacros.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPMatrixTest : SenTestCase 
+{
+    SPMatrix *countMatrix;
+    SPMatrix *identMatrix;
+}
+
+- (BOOL)checkMatrixValues:(SPMatrix*)matrix a:(float)a b:(float)b c:(float)c d:(float)d 
+                       tx:(float)tx ty:(float)ty;
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPMatrixTest
+
+- (void) setUp
+{
+    countMatrix = [[SPMatrix alloc] initWithA:1 b:2 c:3 d:4 tx:5 ty:6];
+    identMatrix = [[SPMatrix alloc] init];
+}
+
+- (void) tearDown
+{
+    [countMatrix release];
+    [identMatrix release];
+}
+
+- (void)testInit
+{
+    BOOL isEqual = [self checkMatrixValues:countMatrix a:1.0f b:2.0f c:3.0f d:4.0f tx:5.0f ty:6.0f];
+    STAssertTrue(isEqual, @"wrong matrix: %@", countMatrix);
+    isEqual = [self checkMatrixValues:identMatrix a:1.0f b:0.0f c:0.0f d:1.0f tx:0.0f ty:0.0f];
+    STAssertTrue(isEqual, @"wrong matrix: %@", identMatrix);
+}
+
+- (void)testCopy
+{
+    SPMatrix *copy = [countMatrix copy];
+    STAssertEqualObjects(countMatrix, copy, @"copy not equal: %@", copy);
+    STAssertFalse(countMatrix == copy, @"copy is identical");
+    
+    [copy release];
+}
+
+- (void)testConcatMatrix
+{
+    SPMatrix *copy = [countMatrix copy];
+    [copy concatMatrix:identMatrix];
+    STAssertEqualObjects(countMatrix, copy, @"multiplication with identity modified matrix");
+    [copy release];
+    copy = [identMatrix copy];
+    [copy concatMatrix:countMatrix];
+    STAssertEqualObjects(countMatrix, copy, @"multiplication with identity modified matrix");
+    
+    SPMatrix *countDownMatrix = [[SPMatrix alloc] initWithA:9 b:8 c:7 d:6 tx:5 ty:4];
+    [copy concatMatrix:countDownMatrix];
+    STAssertTrue([self checkMatrixValues:copy a:23 b:20 c:55 d:48 tx:92 ty:80], 
+                 @"wrong matrix: %@", copy);
+    [countDownMatrix concatMatrix:countMatrix];
+    STAssertTrue([self checkMatrixValues:countDownMatrix a:33 b:50 c:25 d:38 tx:22 ty:32],
+                 @"wrong matrix: %@", copy);  
+    
+    [countDownMatrix release];
+    [copy release];
+}
+
+- (void)testInvert
+{
+    [countMatrix invert];
+    STAssertTrue([self checkMatrixValues:countMatrix a:-2 b:1 c:3.0f/2.0f d:-0.5f tx:1 ty:-2],
+                 @"invert produced wrong result: %@", countMatrix);
+    
+    SPMatrix *translateMatrix = [SPMatrix matrixWithIdentity];
+    [translateMatrix translateXBy:20 yBy:40];
+    [translateMatrix invert];
+    
+    STAssertTrue([self checkMatrixValues:translateMatrix a:1 b:0 c:0 d:1 tx:-20 ty:-40],
+                @"invert produced wrong result: %@", translateMatrix);
+}
+
+- (void)testTranslate
+{
+    [identMatrix translateXBy:5 yBy:7];
+    SPPoint *point = [[SPPoint alloc] initWithX:10 y:20];
+    SPPoint *tPoint = [identMatrix transformPoint:point];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(15, tPoint.x), @"wrong x value: %f", tPoint.x);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(27, tPoint.y), @"wrong y value: %f", tPoint.y);    
+    [point release];
+}
+
+- (void)testRotate
+{
+    [identMatrix rotateBy:PI/2.0f];
+    SPPoint *point = [[SPPoint alloc] initWithX:10 y:0];
+    SPPoint *rPoint = [identMatrix transformPoint:point];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(0, rPoint.x), @"wrong x value: %f", rPoint.x);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(10, rPoint.y), @"wrong y value: %f", rPoint.y);
+    
+    [identMatrix identity];
+    [identMatrix rotateBy:PI];
+    point.y = 20;
+    rPoint = [identMatrix transformPoint:point];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(-10, rPoint.x), @"wrong x value: %f", rPoint.x);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(-20, rPoint.y), @"wrong y value: %f", rPoint.y);
+    
+    [point release];
+}
+
+- (void)testScale
+{
+    [identMatrix scaleXBy:2.0 yBy:0.5];
+    SPPoint *point = [[SPPoint alloc] initWithX:10 y:20];
+    SPPoint *sPoint = [identMatrix transformPoint:point];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(20.0f, sPoint.x), @"wrong x value: %f", sPoint.x);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(10.0f, sPoint.y), @"wrong y value: %f", sPoint.y);    
+    [point release];
+}
+
+- (void)testConcatenatedTransformations
+{
+    [identMatrix rotateBy:PI/2.0f];    
+    [identMatrix scaleBy:0.5f];
+    [identMatrix translateXBy:0.0f yBy:5.0];
+    SPPoint *point = [[SPPoint alloc] initWithX:10 y:0];
+    SPPoint *ctPoint = [identMatrix transformPoint:point];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(0.0f, ctPoint.x), @"wrong x value: %f", ctPoint.x);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(10.0f, ctPoint.y), @"wrong y value: %f", ctPoint.y);    
+    [point release];
+}
+
+- (BOOL)checkMatrixValues:(SPMatrix*)matrix a:(float)a b:(float)b c:(float)c d:(float)d 
+                       tx:(float)tx ty:(float)ty
+{
+    return SP_IS_FLOAT_EQUAL(a, matrix.a) && SP_IS_FLOAT_EQUAL(b, matrix.b) &&
+           SP_IS_FLOAT_EQUAL(b, matrix.b) && SP_IS_FLOAT_EQUAL(c, matrix.c) &&
+           SP_IS_FLOAT_EQUAL(tx, matrix.tx) && SP_IS_FLOAT_EQUAL(ty, matrix.ty);
+}
+
+@end
+
+#endif
\ No newline at end of file
diff --git a/libs/sparrow/src/UnitTests/SPMovieClipTest.m b/libs/sparrow/src/UnitTests/SPMovieClipTest.m
new file mode 100644 (file)
index 0000000..4fdf3d2
--- /dev/null
@@ -0,0 +1,176 @@
+//
+//  SPMovieClipTest.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 03.06.10.
+//  Copyright 2010 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+
+#import "SPMovieClip.h"
+#import "SPTexture.h"
+
+#define E 0.0001f
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPMovieClipTest : SenTestCase 
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPMovieClipTest
+
+- (void)testFrameManipulation
+{    
+    float fps = 4.0;
+    double frameDuration = 1.0 / fps;
+    
+    SPTexture *frame0 = [SPTexture emptyTexture];
+    SPTexture *frame1 = [SPTexture emptyTexture];
+    SPTexture *frame2 = [SPTexture emptyTexture];
+    SPTexture *frame3 = [SPTexture emptyTexture];
+    
+    SPMovieClip *movie = [SPMovieClip movieWithFrame:frame0 fps:4.0f];    
+    
+    STAssertEqualsWithAccuracy(frame0.width, movie.width, E, @"wrong size");
+    STAssertEqualsWithAccuracy(frame0.height, movie.height, E, @"wrong size");
+
+    STAssertEquals(1, movie.numFrames, @"wrong number of frames");
+    STAssertEquals(0, movie.currentFrame, @"wrong start value");
+    STAssertEquals(frameDuration, movie.duration, @"wrong duration");
+    STAssertEquals(YES, movie.loop, @"wrong default value");
+    STAssertEquals(YES, movie.isPlaying, @"wrong default value");
+    STAssertEqualsWithAccuracy(frameDuration, movie.duration, E, @"wrong duration");
+    
+    [movie pause];
+    STAssertFalse(movie.isPlaying, @"property returns wrong value");
+    
+    [movie play];
+    STAssertTrue(movie.isPlaying, @"property returns wrong value");
+    
+    movie.loop = NO;
+    STAssertFalse(movie.loop, @"property returns wrong value");    
+    
+    [movie addFrame:frame1];
+    
+    STAssertEquals(2, movie.numFrames, @"wrong number of frames");
+    STAssertEqualsWithAccuracy(2 * frameDuration, movie.duration, E, @"wrong duration");
+    
+    STAssertEqualObjects(frame0, [movie frameAtIndex:0], @"wrong frame");
+    STAssertEqualObjects(frame1, [movie frameAtIndex:1], @"wrong frame");
+    
+    STAssertEqualsWithAccuracy(frameDuration, [movie durationAtIndex:0] , E, @"wrong frame duration");
+    STAssertEqualsWithAccuracy(frameDuration, [movie durationAtIndex:1] , E, @"wrong frame duration");
+    
+    STAssertNil([movie soundAtIndex:0], @"sound not nil");
+    STAssertNil([movie soundAtIndex:1], @"sound not nil");
+    
+    [movie addFrame:frame2 withDuration:0.5];
+    STAssertEqualsWithAccuracy(0.5, [movie durationAtIndex:2], E, @"wrong frame duration");
+    STAssertEqualsWithAccuracy(1.0, movie.duration, E, @"wrong duration");
+    
+    [movie insertFrame:frame3 atIndex:2]; // -> 0, 1, 3, 2
+    STAssertEquals(4, movie.numFrames, @"wrong number of frames");
+    STAssertEqualsWithAccuracy(1.0 + frameDuration, movie.duration, E, @"wrong duration");
+    STAssertEqualObjects(frame1, [movie frameAtIndex:1], @"wrong frame");
+    STAssertEqualObjects(frame3, [movie frameAtIndex:2], @"wrong frame");
+    STAssertEqualObjects(frame2, [movie frameAtIndex:3], @"wrong frame");
+    
+    [movie removeFrameAtIndex:0]; // -> 1, 3, 2
+    STAssertEquals(3, movie.numFrames, @"wrong number of frames");
+    STAssertEqualObjects(frame1, [movie frameAtIndex:0], @"wrong frame");
+    STAssertEqualsWithAccuracy(1.0, movie.duration, E, @"wrong duration");
+    
+    [movie removeFrameAtIndex:1]; // -> 1, 2
+    STAssertEquals(2, movie.numFrames, @"wrong number of frames");
+    STAssertEqualObjects(frame1, [movie frameAtIndex:0], @"wrong frame");
+    STAssertEqualObjects(frame2, [movie frameAtIndex:1], @"wrong frame");
+    STAssertEqualsWithAccuracy(0.75, movie.duration, E, @"wrong duration");
+    
+    [movie setFrame:frame3 atIndex:1];
+    STAssertEqualObjects(frame3, [movie frameAtIndex:1], @"wrong frame");    
+    
+    [movie setDuration:0.75 atIndex:1];
+    STAssertEqualsWithAccuracy(1.0, movie.duration, E, @"wrong duration");
+    
+    [movie insertFrame:frame3 atIndex:2];
+    STAssertEquals(frame3, [movie frameAtIndex:2], @"wrong frame");
+}
+
+- (void)testAdvanceTime
+{
+    float fps = 4.0;
+    double frameDuration = 1.0 / fps;
+    
+    SPTexture *frame0 = [SPTexture emptyTexture];
+    SPTexture *frame1 = [SPTexture emptyTexture];
+    SPTexture *frame2 = [SPTexture emptyTexture];
+    SPTexture *frame3 = [SPTexture emptyTexture];
+    
+    SPMovieClip *movie = [SPMovieClip movieWithFrame:frame0 fps:fps];
+    
+    [movie addFrame:frame1];
+    [movie addFrame:frame2 withDuration:0.5];
+    [movie addFrame:frame3];
+    
+    STAssertEquals(0, movie.currentFrame, @"wrong current frame");
+    [movie advanceTime:frameDuration / 2.0];
+    STAssertEquals(0, movie.currentFrame, @"wrong current frame");
+    [movie advanceTime:frameDuration];
+    STAssertEquals(1, movie.currentFrame, @"wrong current frame");
+    [movie advanceTime:frameDuration];
+    STAssertEquals(2, movie.currentFrame, @"wrong current frame");
+    [movie advanceTime:frameDuration];
+    STAssertEquals(2, movie.currentFrame, @"wrong current frame");
+    [movie advanceTime:frameDuration];
+    STAssertEquals(3, movie.currentFrame, @"wrong current frame");
+    [movie advanceTime:frameDuration];
+    STAssertEquals(0, movie.currentFrame, @"movie did not loop");
+    
+    movie.loop = NO;
+    [movie advanceTime:movie.duration + frameDuration];
+    STAssertEquals(3, movie.currentFrame, @"movie looped");
+    
+    movie.currentFrame = 0;
+    STAssertEquals(0, movie.currentFrame, @"wrong current frame");
+    [movie advanceTime:frameDuration * 1.1];
+    STAssertEquals(1, movie.currentFrame, @"wrong current frame");
+}
+
+- (void)testChangeFps
+{
+    NSArray *frames = [NSArray arrayWithObjects:[SPTexture emptyTexture], [SPTexture emptyTexture],
+                       [SPTexture emptyTexture], nil];
+        
+    SPMovieClip *movie = [SPMovieClip movieWithFrames:frames fps:4.0f];    
+    STAssertEquals(4.0f, movie.fps, @"wrong fps");
+    
+    movie.fps = 3.0f;
+    STAssertEquals(3.0f, movie.fps, @"wrong fps");    
+    STAssertEqualsWithAccuracy(1.0 / 3.0, [movie durationAtIndex:0], E, @"wrong frame duration");
+    STAssertEqualsWithAccuracy(1.0 / 3.0, [movie durationAtIndex:1], E, @"wrong frame duration");
+    STAssertEqualsWithAccuracy(1.0 / 3.0, [movie durationAtIndex:2], E, @"wrong frame duration");
+    
+    [movie setDuration:1.0 atIndex:1];
+    STAssertEqualsWithAccuracy(1.0, [movie durationAtIndex:1], E, @"wrong frame duration");
+    
+    movie.fps = 6.0f;
+    STAssertEqualsWithAccuracy(0.5,       [movie durationAtIndex:1], E, @"wrong frame duration");
+    STAssertEqualsWithAccuracy(1.0 / 6.0, [movie durationAtIndex:0], E, @"wrong frame duration");
+    
+    movie.fps = 0.0f;
+    STAssertEqualsWithAccuracy(0.0f, movie.fps, E, @"wrong fps");
+}
+
+@end
+
+#endif
\ No newline at end of file
diff --git a/libs/sparrow/src/UnitTests/SPNSExtensionsTest.m b/libs/sparrow/src/UnitTests/SPNSExtensionsTest.m
new file mode 100644 (file)
index 0000000..92663da
--- /dev/null
@@ -0,0 +1,38 @@
+//
+//  SPNSExtensionsTests.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 10.07.10.
+//  Copyright 2010 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+
+#import "SPNSExtensions.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPNSExtensionsTest : SenTestCase 
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPNSExtensionsTest
+
+- (void)testStringByAppendingSuffixToFilename
+{    
+    NSString *filename = @"path/file.ext";
+    NSString *expandedFilename = [filename stringByAppendingSuffixToFilename:@"@2x"];
+    STAssertEqualObjects(@"path/file@2x.ext", expandedFilename, @"Appending suffix did not work!");    
+}
+
+@end
+
+#endif
\ No newline at end of file
diff --git a/libs/sparrow/src/UnitTests/SPPointTest.m b/libs/sparrow/src/UnitTests/SPPointTest.m
new file mode 100644 (file)
index 0000000..ce28648
--- /dev/null
@@ -0,0 +1,226 @@
+//
+//  SPPointTest.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 25.03.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPPoint.h"
+#import "SPMacros.h"
+
+#define E 0.0001f
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPPointTest :  SenTestCase  
+{
+    SPPoint *mP1;
+    SPPoint *mP2;
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPPointTest
+
+- (void) setUp
+{
+    mP1 = [[SPPoint alloc] initWithX:2 y:3];
+    mP2 = [[SPPoint alloc] initWithX:4 y:1];    
+}
+
+- (void) tearDown
+{
+    [mP1 release];
+    [mP2 release];
+}
+
+- (void)testInit
+{
+    SPPoint *point = [[SPPoint alloc] init];
+    STAssertEquals(0.0f, point.x, @"x is not zero");
+    STAssertEquals(0.0f, point.y, @"y is not zero");
+    [point release];
+}
+
+- (void)testInitWithXandY
+{
+    SPPoint *point = [[SPPoint alloc] initWithX:3 y:4];
+    STAssertEquals(3.0f, point.x, @"wrong x value");
+    STAssertEquals(4.0f, point.y, @"wrong y value");
+    [point release];
+}
+
+- (void)testLength
+{
+    SPPoint *point = [[SPPoint alloc] initWithX:-4 y:3];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(5.0f, point.length), @"wrong length");
+    point.x = 0;
+    point.y = 0;
+    STAssertEquals(0.0f, point.length, @"wrong length");
+    [point release];    
+}
+
+- (void)testAngle
+{    
+    SPPoint *point = [[SPPoint alloc] initWithX:10 y:0];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(0.0f, point.angle), @"wrong angle: %f", point.angle);
+    point.y = 10;
+    STAssertTrue(SP_IS_FLOAT_EQUAL(PI/4.0f, point.angle), @"wrong angle: %f", point.angle);
+    point.x = 0;
+    STAssertTrue(SP_IS_FLOAT_EQUAL(PI/2.0f, point.angle), @"wrong angle: %f", point.angle);
+    point.x = -10;
+    STAssertTrue(SP_IS_FLOAT_EQUAL(3*PI/4.0f, point.angle), @"wrong angle: %f", point.angle);
+    point.y = 0;
+    STAssertTrue(SP_IS_FLOAT_EQUAL(PI, point.angle), @"wrong angle: %f", point.angle);
+    point.y = -10;
+    STAssertTrue(SP_IS_FLOAT_EQUAL(-3*PI/4.0f, point.angle), @"wrong angle: %f", point.angle);
+    point.x = 0;
+    STAssertTrue(SP_IS_FLOAT_EQUAL(-PI/2.0f, point.angle), @"wrong angle: %f", point.angle);
+    point.x = 10;
+    STAssertTrue(SP_IS_FLOAT_EQUAL(-PI/4.0f, point.angle), @"wrong angle: %f", point.angle);
+    [point release];    
+}
+
+- (void)testAddPoint
+{
+    SPPoint *result = [mP1 addPoint:mP2];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(6.0f, result.x), @"wrong x value");
+    STAssertTrue(SP_IS_FLOAT_EQUAL(4.0f, result.y), @"wrong y value");
+}
+
+- (void)testSubtractPoint
+{
+    SPPoint *result = [mP1 subtractPoint:mP2];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(-2.0f, result.x), @"wrong x value");
+    STAssertTrue(SP_IS_FLOAT_EQUAL(2.0f, result.y), @"wrong y value");
+}
+
+- (void)testScale
+{
+    SPPoint *point = [SPPoint pointWithX:0.0f y:0.0f];
+    point = [point scaleBy:100.0f];
+    STAssertEqualsWithAccuracy(point.x, 0.0f, E, @"wrong x value");
+    STAssertEqualsWithAccuracy(point.y, 0.0f, E, @"wrong y value");
+    
+    point = [SPPoint pointWithX:1.0f y:2.0f];
+    float origLength = point.length;
+    point = [point scaleBy:2.0f];
+    float scaledLength = point.length;
+    STAssertEqualsWithAccuracy(point.x, 2.0f, E, @"wrong x value");
+    STAssertEqualsWithAccuracy(point.y, 4.0f, E, @"wrong y value");
+    STAssertEqualsWithAccuracy(origLength * 2.0f, scaledLength, E, @"wrong length");
+}
+
+- (void)testNormalize
+{
+    SPPoint *result = [mP1 normalize];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(1.0f, result.length), @"wrong length");
+    STAssertTrue(SP_IS_FLOAT_EQUAL(mP1.angle, result.angle), @"wrong angle");
+    SPPoint *origin = [[SPPoint alloc] init];
+    STAssertThrows([origin normalize], @"origin cannot be normalized!");
+    [origin release];
+}
+
+- (void)testClone
+{
+    SPPoint *result = [mP1 copy];
+    STAssertEquals(mP1.x, result.x, @"wrong x value");
+    STAssertEquals(mP1.y, result.y, @"wrong y value");
+    STAssertFalse(result == mP1, @"object should not be identical");
+    STAssertEqualObjects(mP1, result, @"objects should be equal");
+    [result release];
+}
+
+- (void)testIsEqual
+{
+    STAssertFalse([mP1 isEqual:mP2], @"should not be equal");    
+    SPPoint *p3 = [[SPPoint alloc] initWithX:mP1.x y:mP1.y];
+    STAssertTrue([mP1 isEqual:p3], @"should be equal");
+    p3.x += 0.0000001;
+    p3.y -= 0.0000001;
+    STAssertTrue([mP1 isEqual:p3], @"should be equal, as difference is smaller than epsilon");
+    [p3 release];
+}
+
+- (void)testDistance
+{
+    SPPoint *p3 = [[SPPoint alloc] initWithX:5 y:0];
+    SPPoint *p4 = [[SPPoint alloc] initWithX:5 y:5];
+    float distance = [SPPoint distanceFromPoint:p3 toPoint:p4];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(5.0f, distance), @"wrong distance");
+    p3.y = -5;
+    distance = [SPPoint distanceFromPoint:p3 toPoint:p4];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(10.0f, distance), @"wrong distance");
+    [p3 release];
+    [p4 release];
+}
+
+- (void)testPolarPoint
+{
+    float angle = 5.0 * PI / 4.0;
+    float negAngle = -(2*PI - angle);
+    float length = 2.0f;
+    SPPoint *p3 = [SPPoint pointWithPolarLength:length angle:angle];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(length, p3.length), @"wrong length");
+    STAssertTrue(SP_IS_FLOAT_EQUAL(negAngle, p3.angle), @"wrong angle");
+    STAssertTrue(SP_IS_FLOAT_EQUAL(-cosf(angle-PI)*length, p3.x), @"wrong x");
+    STAssertTrue(SP_IS_FLOAT_EQUAL(-sinf(angle-PI)*length, p3.y), @"wrong y");    
+}
+
+- (void)testInterpolate
+{
+    SPPoint *interpolation;
+    
+    interpolation = [SPPoint interpolateFromPoint:mP1 toPoint:mP2 ratio:0.25f];
+    STAssertEqualsWithAccuracy(interpolation.x, 2.5f, E, @"wrong interpolated x");
+    STAssertEqualsWithAccuracy(interpolation.y, 2.5f, E, @"wrong interpolated y");
+
+    interpolation = [SPPoint interpolateFromPoint:mP1 toPoint:mP2 ratio:-0.25f];
+    STAssertEqualsWithAccuracy(interpolation.x, 1.5f, E, @"wrong interpolated x");
+    STAssertEqualsWithAccuracy(interpolation.y, 3.5f, E, @"wrong interpolated y");
+
+    interpolation = [SPPoint interpolateFromPoint:mP1 toPoint:mP2 ratio:1.25f];
+    STAssertEqualsWithAccuracy(interpolation.x, 4.5f, E, @"wrong interpolated x");
+    STAssertEqualsWithAccuracy(interpolation.y, 0.5f, E, @"wrong interpolated y");
+    
+    SPPoint *p1 = [SPPoint pointWithX:2.0f y:1.0f];
+    SPPoint *p2 = [SPPoint pointWithX:-2.0f y:-1.0f];
+    
+    interpolation = [SPPoint interpolateFromPoint:p1 toPoint:p2 ratio:0.5f];    
+    STAssertEqualsWithAccuracy(interpolation.x, 0.0f, E, @"wrong interpolated x");
+    STAssertEqualsWithAccuracy(interpolation.y, 0.0f, E, @"wrong interpolated y");
+    
+    interpolation = [SPPoint interpolateFromPoint:p1 toPoint:p2 ratio:0.0f];
+    STAssertEqualsWithAccuracy(interpolation.x, 2.0f, E, @"wrong interpolated x");
+    STAssertEqualsWithAccuracy(interpolation.y, 1.0f, E, @"wrong interpolated y");
+    
+    interpolation = [SPPoint interpolateFromPoint:p1 toPoint:p2 ratio:1.0f];
+    STAssertEqualsWithAccuracy(interpolation.x, -2.0f, E, @"wrong interpolated x");
+    STAssertEqualsWithAccuracy(interpolation.y, -1.0f, E, @"wrong interpolated y");
+}
+
+// STAssertEquals(value, value, message, ...)
+// STAssertEqualObjects(object, object, message, ...)
+// STAssertNotNil(object, message, ...)
+// STAssertTrue(expression, message, ...)
+// STAssertFalse(expression, message, ...)
+// STAssertThrows(expression, message, ...) 
+// STAssertThrowsSpecific(expression, exception, message, ...)
+// STAssertNoThrow(expression, message, ...)
+// STFail(message, ...)
+
+@end
+
+#endif
\ No newline at end of file
diff --git a/libs/sparrow/src/UnitTests/SPQuadTest.m b/libs/sparrow/src/UnitTests/SPQuadTest.m
new file mode 100644 (file)
index 0000000..330c190
--- /dev/null
@@ -0,0 +1,70 @@
+//
+//  SPQuadTest.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 23.04.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+#import <math.h>
+
+#import "SPMatrix.h"
+#import "SPMacros.h"
+#import "SPPoint.h"
+#import "SPSprite.h"
+#import "SPQuad.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPQuadTest : SenTestCase 
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPQuadTest
+
+- (void)testProperties
+{
+    float width = 30.0f;
+    float height = 20.0f;
+    float x = 3;
+    float y = 2;
+    
+    SPQuad *quad = [[SPQuad alloc] initWithWidth:width height:height];
+    quad.x = x; 
+    quad.y = y;
+    
+    STAssertTrue(SP_IS_FLOAT_EQUAL(x, quad.x), @"wrong x");
+    STAssertTrue(SP_IS_FLOAT_EQUAL(y, quad.y), @"wrong y");
+    STAssertTrue(SP_IS_FLOAT_EQUAL(width, quad.width), @"wrong width");
+    STAssertTrue(SP_IS_FLOAT_EQUAL(height, quad.height), @"wrong height");
+    
+    [quad release];
+}
+
+- (void)testWidth
+{
+    float width = 30;
+    float height = 40;
+    float angle = SP_D2R(45.0f);
+    SPQuad *quad = [[SPQuad alloc] initWithWidth:width height:height];
+    quad.rotation = angle;
+
+    float expectedWidth = cosf(angle) * (width + height);
+    STAssertTrue(SP_IS_FLOAT_EQUAL(expectedWidth, quad.width), @"wrong width: %f", quad.width);
+    
+    [quad release];
+}
+
+@end
+
+#endif
\ No newline at end of file
diff --git a/libs/sparrow/src/UnitTests/SPRectangleTest.m b/libs/sparrow/src/UnitTests/SPRectangleTest.m
new file mode 100644 (file)
index 0000000..80aa1bd
--- /dev/null
@@ -0,0 +1,103 @@
+//
+//  SPRectangleTest.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 25.04.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPMacros.h"
+#import "SPPoint.h"
+#import "SPRectangle.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPRectangleTest : SenTestCase 
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPRectangleTest
+
+- (void)testInit
+{
+    SPRectangle *rect = [[SPRectangle alloc] initWithX:10 y:20 width:30 height:40];
+    STAssertTrue(SP_IS_FLOAT_EQUAL(10, rect.x), @"wrong x");
+    STAssertTrue(SP_IS_FLOAT_EQUAL(20, rect.y), @"wrong y");
+    STAssertTrue(SP_IS_FLOAT_EQUAL(30, rect.width), @"wrong width");
+    STAssertTrue(SP_IS_FLOAT_EQUAL(40, rect.height), @"wrong height");    
+    [rect release];
+}
+
+- (void)testContainsPoint
+{
+    SPRectangle *rect = [SPRectangle rectangleWithX:10 y:20 width:30 height:40];
+    STAssertFalse([rect containsPoint:[SPPoint pointWithX:0 y:0]], @"point inside");
+    STAssertTrue([rect containsPoint:[SPPoint pointWithX:15 y:25]], @"point not inside");
+    STAssertTrue([rect containsPoint:[SPPoint pointWithX:10 y:20]], @"point not inside");
+    STAssertTrue([rect containsPoint:[SPPoint pointWithX:40 y:60]], @"point not inside");
+}
+
+- (void)testContainsRect
+{
+    SPRectangle *rect = [SPRectangle rectangleWithX:-5 y:-10 width:10 height:20];
+    
+    SPRectangle *overlapRect = [SPRectangle rectangleWithX:-10 y:-15 width:10 height:10];
+    SPRectangle *identRect = [SPRectangle rectangleWithX:-5 y:-10 width:10 height:20];
+    SPRectangle *outsideRect = [SPRectangle rectangleWithX:10 y:10 width:10 height:10];
+    SPRectangle *touchingRect = [SPRectangle rectangleWithX:5 y:0 width:10 height:10];
+    SPRectangle *insideRect = [SPRectangle rectangleWithX:0 y:0 width:1 height:2];
+    
+    STAssertFalse([rect containsRectangle:overlapRect], @"overlapping, not inside");
+    STAssertTrue([rect containsRectangle:identRect], @"identical, should be inside");
+    STAssertFalse([rect containsRectangle:outsideRect], @"should be outside");
+    STAssertFalse([rect containsRectangle:touchingRect], @"touching, should be outside");
+    STAssertTrue([rect containsRectangle:insideRect], @"should be inside");    
+}
+
+- (void)testIntersectionWithRectangle
+{
+    SPRectangle *rect = [SPRectangle rectangleWithX:-5 y:-10 width:10 height:20];
+
+    SPRectangle *overlapRect = [SPRectangle rectangleWithX:-10 y:-15 width:10 height:10];
+    SPRectangle *identRect = [SPRectangle rectangleWithX:-5 y:-10 width:10 height:20];
+    SPRectangle *outsideRect = [SPRectangle rectangleWithX:10 y:10 width:10 height:10];
+    SPRectangle *touchingRect = [SPRectangle rectangleWithX:5 y:0 width:10 height:10];
+    SPRectangle *insideRect = [SPRectangle rectangleWithX:0 y:0 width:1 height:2];
+    
+    STAssertEqualObjects([SPRectangle rectangleWithX:-5 y:-10 width:5 height:5],
+                         [rect intersectionWithRectangle:overlapRect], @"wrong intersection shape");
+    STAssertEqualObjects(rect, [rect intersectionWithRectangle:identRect], @"wrong intersection shape");
+    STAssertEqualObjects([SPRectangle rectangleWithX:0 y:0 width:0 height:0], 
+                         [rect intersectionWithRectangle:outsideRect], @"intersection should be empty");
+    STAssertEqualObjects([SPRectangle rectangleWithX:5 y:0 width:0 height:10],
+                         [rect intersectionWithRectangle:touchingRect], @"wrong intersection shape");
+    STAssertEqualObjects(insideRect, [rect intersectionWithRectangle:insideRect],
+                         @"wrong intersection shape");
+}
+
+- (void)testUniteWithRectangle
+{
+    SPRectangle *rect = [SPRectangle rectangleWithX:-5 y:-10 width:10 height:20];
+    
+    SPRectangle *topLeftRect = [SPRectangle rectangleWithX:-15 y:-20 width:5 height:5];
+    SPRectangle *innerRect = [SPRectangle rectangleWithX:-5 y:-5 width:10 height:10];
+    
+    STAssertEqualObjects([SPRectangle rectangleWithX:-15 y:-20 width:20 height:30],
+                         [rect uniteWithRectangle:topLeftRect], @"wrong union");
+    STAssertEqualObjects(rect, [rect uniteWithRectangle:innerRect], @"wrong union");    
+}
+
+@end
+
+#endif
\ No newline at end of file
diff --git a/libs/sparrow/src/UnitTests/SPStageTest.m b/libs/sparrow/src/UnitTests/SPStageTest.m
new file mode 100644 (file)
index 0000000..38ae0fc
--- /dev/null
@@ -0,0 +1,47 @@
+//
+//  SPStageTest.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 25.04.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPMatrix.h"
+#import "SPMacros.h"
+#import "SPPoint.h"
+#import "SPSprite.h"
+#import "SPStage.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPStageTest : SenTestCase 
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPStageTest
+
+- (void)testForbiddenProperties
+{
+    SPStage *stage = [[SPStage alloc] init];
+    STAssertThrows([stage setX:10], @"allowed to set x coordinate of stage");
+    STAssertThrows([stage setY:10], @"allowed to set y coordinate of stage");
+    STAssertThrows([stage setScaleX:2.0], @"allowed to scale stage");
+    STAssertThrows([stage setScaleY:2.0], @"allowed to scale stage");
+    STAssertThrows([stage setRotation:PI], @"allowed to rotate stage");
+    [stage release];
+}
+
+@end
+
+#endif
\ No newline at end of file
diff --git a/libs/sparrow/src/UnitTests/SPTweenTest.m b/libs/sparrow/src/UnitTests/SPTweenTest.m
new file mode 100644 (file)
index 0000000..3cf1c09
--- /dev/null
@@ -0,0 +1,254 @@
+//
+//  SPTweenerTest.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 15.05.09.
+//  Copyright 2009 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+#import "SPEventDispatcher.h"
+#import "SPEvent.h"
+#import "SPQuad.h"
+#import "SPTween.h"
+#import "SPMacros.h"
+
+#define E 0.0001f
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPTweenTest : SenTestCase 
+{
+  @private
+    int mStartedCount;
+    int mUpdatedCount;
+    int mCompletedCount;
+}
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPTweenTest
+
+- (void) setUp
+{
+    mStartedCount = mUpdatedCount = mCompletedCount = 0;
+}
+
+- (void)testBasicTween
+{    
+    float startX = 10.0f;
+    float startY = 20.0f;
+    float endX = 100.0f;
+    float endY = 200.0f;
+    float startAlpha = 1.0f;
+    float endAlpha = 0.0f;
+    float totalTime = 2.0f;
+    
+    SPQuad *quad = [SPQuad quadWithWidth:100 height:100];
+    quad.x = startX;
+    quad.y = startY;
+    quad.alpha = startAlpha;
+    
+    SPTween *tween = [SPTween tweenWithTarget:quad time:totalTime transition:SP_TRANSITION_LINEAR];
+    [tween animateProperty:@"x" targetValue:endX];
+    [tween animateProperty:@"y" targetValue:endY];
+    [tween animateProperty:@"alpha" targetValue:endAlpha];    
+    [tween addEventListener:@selector(onTweenStarted:) atObject:self forType:SP_EVENT_TYPE_TWEEN_STARTED];
+    [tween addEventListener:@selector(onTweenUpdated:) atObject:self forType:SP_EVENT_TYPE_TWEEN_UPDATED];    
+    [tween addEventListener:@selector(onTweenCompleted:) atObject:self forType:SP_EVENT_TYPE_TWEEN_COMPLETED];    
+    
+    STAssertEqualsWithAccuracy(startX, quad.x, E, @"wrong x");
+    STAssertEqualsWithAccuracy(startY, quad.y, E, @"wrong y");
+    STAssertEqualsWithAccuracy(startAlpha, quad.alpha, E, @"wrong alpha");        
+    STAssertEquals(0, mStartedCount, @"start event dispatched too soon");
+    
+    [tween advanceTime: totalTime/3.0];   
+    STAssertEqualsWithAccuracy(startX + (endX-startX)/3.0f, quad.x, E, @"wrong x: %f", quad.x);
+    STAssertEqualsWithAccuracy(startY + (endY-startY)/3.0f, quad.y, E, @"wrong y");
+    STAssertEqualsWithAccuracy(startAlpha + (endAlpha-startAlpha)/3.0f, quad.alpha, E, @"wrong alpha");
+    STAssertEquals(1, mStartedCount, @"missing start event");
+    STAssertEquals(1, mUpdatedCount, @"missing update event");
+    STAssertEquals(0, mCompletedCount, @"completed event dispatched too soon");
+    
+    [tween advanceTime: totalTime/3.0];   
+    STAssertEqualsWithAccuracy(startX + 2.0f*(endX-startX)/3.0f, quad.x, E, @"wrong x: %f", quad.x);
+    STAssertEqualsWithAccuracy(startY + 2.0f*(endY-startY)/3.0f, quad.y, E, @"wrong y");
+    STAssertEqualsWithAccuracy(startAlpha + 2.0f*(endAlpha-startAlpha)/3.0f, quad.alpha, E, @"wrong alpha");
+    STAssertEquals(1, mStartedCount, @"too many start events dipatched");
+    STAssertEquals(2, mUpdatedCount, @"missing update event");
+    STAssertEquals(0, mCompletedCount, @"completed event dispatched too soon");
+    
+    [tween advanceTime: totalTime/3.0];
+    STAssertEqualsWithAccuracy(endX, quad.x, E, @"wrong x: %f", quad.x);
+    STAssertEqualsWithAccuracy(endY, quad.y, E, @"wrong y");
+    STAssertEqualsWithAccuracy(endAlpha, quad.alpha, E, @"wrong alpha");
+    STAssertEquals(1, mStartedCount, @"too many start events dispatched");
+    STAssertEquals(3, mUpdatedCount, @"missing update event");
+    STAssertEquals(1, mCompletedCount, @"missing completed event");
+    
+    [tween removeEventListenersAtObject:self forType:SP_EVENT_TYPE_TWEEN_STARTED];
+    [tween removeEventListenersAtObject:self forType:SP_EVENT_TYPE_TWEEN_UPDATED];    
+    [tween removeEventListenersAtObject:self forType:SP_EVENT_TYPE_TWEEN_COMPLETED];    
+}
+
+- (void)onTweenStarted:(SPEvent*)event
+{
+    mStartedCount++;
+}
+
+- (void)onTweenUpdated:(SPEvent*)event
+{
+    mUpdatedCount++;
+}
+
+- (void)onTweenCompleted:(SPEvent*)event
+{
+    mCompletedCount++;
+}
+
+- (void)testSequentialTweens
+{
+    float startPos = 0.0f;
+    float targetPos = 50.0f;
+    SPQuad *quad = [SPQuad quadWithWidth:100 height:100];
+    
+    // 2 tweens should move object up, then down
+    SPTween *tween1 = [SPTween tweenWithTarget:quad time:1];
+    [tween1 animateProperty:@"y" targetValue:targetPos];
+    
+    SPTween *tween2 = [SPTween tweenWithTarget:quad time:1];
+    [tween2 animateProperty:@"y" targetValue:startPos];
+    tween2.delay = 1;
+    
+    [tween1 advanceTime:1];
+    STAssertEquals(targetPos, quad.y, @"wrong y value");
+    
+    [tween2 advanceTime:1];
+    STAssertEquals(targetPos, quad.y, @"second tween changed y value on start");
+                   
+    [tween2 advanceTime:0.5];
+    STAssertEqualsWithAccuracy((targetPos - startPos)/2.0f, quad.y, E, 
+                 @"second tween moves object the wrong way");
+    
+    [tween2 advanceTime:0.5];
+    STAssertEquals(startPos, quad.y, @"second tween moved to wrong y position");
+}
+
+- (void)testTweenFromZero
+{
+    SPQuad *quad = [SPQuad quadWithWidth:100 height:100];
+    quad.scaleX = 0.0f;
+    SPTween *tween = [SPTween tweenWithTarget:quad time:1.0f];
+    [tween animateProperty:@"scaleX" targetValue:1.0f];
+    
+    [tween advanceTime:0.0f];    
+    STAssertEqualsWithAccuracy(0.0f, quad.width, E, @"wrong x value");
+    
+    [tween advanceTime:0.5f];
+    STAssertEqualsWithAccuracy(50.0f, quad.width, E, @"wrong x value");
+    
+    [tween advanceTime:0.5f];
+    STAssertEqualsWithAccuracy(100.0f, quad.width, E, @"wrong x value");
+}
+
+- (void)testTweenWithRepeatingLoop
+{
+    float startX = 100.0f;    
+    float deltaX = 50.0f;
+    float totalTime = 2.0f;
+    
+    SPQuad *quad = [SPQuad quadWithWidth:100 height:100];
+    quad.x = startX;
+    
+    SPTween *tween = [SPTween tweenWithTarget:quad time:totalTime transition:SP_TRANSITION_LINEAR];
+    [tween animateProperty:@"x" targetValue:startX + deltaX];
+    tween.loop = SPLoopTypeRepeat;
+    
+    [tween advanceTime:0.0];
+    STAssertEqualsWithAccuracy(startX, quad.x, E, @"wrong x value");
+    
+    [tween advanceTime:totalTime / 2.0];
+    STAssertEqualsWithAccuracy(startX + 0.5f * deltaX, quad.x, E, @"wrong x value");
+    
+    [tween advanceTime:totalTime / 2.0];
+    STAssertEqualsWithAccuracy(startX, quad.x, E, @"wrong x value");
+    
+    [tween advanceTime:totalTime / 2.0];
+    STAssertEqualsWithAccuracy(startX + 0.5f * deltaX, quad.x, E, @"wrong x value");
+    
+    [tween advanceTime:totalTime / 2.0];
+    STAssertEqualsWithAccuracy(startX, quad.x, E, @"wrong x value");
+}
+
+- (void)testTweenWithReversingLoop
+{
+    float startX = 100.0f;    
+    float deltaX = 50.0f;
+    float totalTime = 2.0f;
+    
+    SPQuad *quad = [SPQuad quadWithWidth:100 height:100];
+    quad.x = startX;
+    
+    SPTween *tween = [SPTween tweenWithTarget:quad time:totalTime transition:SP_TRANSITION_LINEAR];
+    [tween animateProperty:@"x" targetValue:startX + deltaX];
+    tween.loop = SPLoopTypeReverse;
+    
+    [tween advanceTime:0.0];
+    STAssertEqualsWithAccuracy(startX, quad.x, E, @"wrong x value");
+    
+    [tween advanceTime:totalTime / 2.0];
+    STAssertEqualsWithAccuracy(startX + 0.5f * deltaX, quad.x, E, @"wrong x value");
+    
+    [tween advanceTime:totalTime / 2.0];
+    STAssertEqualsWithAccuracy(startX + deltaX, quad.x, E, @"wrong x value");
+    
+    [tween advanceTime:totalTime / 2.0];
+    STAssertEqualsWithAccuracy(startX + 0.5f * deltaX, quad.x, E, @"wrong x value");
+    
+    [tween advanceTime:totalTime / 2.0];
+    STAssertEqualsWithAccuracy(startX, quad.x, E, @"wrong x value");
+}
+
+- (void)makeTweenWithTime:(double)time andAdvanceBy:(double)advanceTime
+{
+    SPQuad *quad = [SPQuad quadWithWidth:100 height:100];
+    SPTween *tween = [SPTween tweenWithTarget:quad time:time];
+    [tween animateProperty:@"x" targetValue:100.0f];
+    [tween addEventListener:@selector(onTweenStarted:) atObject:self forType:SP_EVENT_TYPE_TWEEN_STARTED];
+    [tween addEventListener:@selector(onTweenUpdated:) atObject:self forType:SP_EVENT_TYPE_TWEEN_UPDATED];    
+    [tween addEventListener:@selector(onTweenCompleted:) atObject:self forType:SP_EVENT_TYPE_TWEEN_COMPLETED]; 
+    
+    [tween advanceTime:advanceTime];
+    
+    STAssertEquals(1, mUpdatedCount, @"short tween did not call onUpdate");
+    STAssertEquals(1, mStartedCount, @"short tween did not call onStarted");
+    STAssertEquals(1, mCompletedCount, @"short tween did not call onCompleted");
+    
+    [tween removeEventListenersAtObject:self forType:SP_EVENT_TYPE_TWEEN_STARTED];
+    [tween removeEventListenersAtObject:self forType:SP_EVENT_TYPE_TWEEN_UPDATED];    
+    [tween removeEventListenersAtObject:self forType:SP_EVENT_TYPE_TWEEN_COMPLETED]; 
+}
+
+- (void)testShortTween
+{
+    [self makeTweenWithTime:0.1f andAdvanceBy:0.1f];
+}
+
+- (void)testZeroTween
+{
+    [self makeTweenWithTime:0.0f andAdvanceBy:0.1f];
+}
+
+@end
+
+#endif
\ No newline at end of file
diff --git a/libs/sparrow/src/UnitTests/SPUtilsTest.m b/libs/sparrow/src/UnitTests/SPUtilsTest.m
new file mode 100644 (file)
index 0000000..a34108f
--- /dev/null
@@ -0,0 +1,65 @@
+//
+//  SPUtilsTest.m
+//  Sparrow
+//
+//  Created by Daniel Sperl on 04.01.11.
+//  Copyright 2011 Incognitek. All rights reserved.
+//
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the Simplified BSD License.
+//
+
+#import <Availability.h>
+#ifdef __IPHONE_3_0
+
+#import <SenTestingKit/SenTestingKit.h>
+
+#import "SPUtils.h"
+
+// -------------------------------------------------------------------------------------------------
+
+@interface SPUtilsTest : SenTestCase 
+
+@end
+
+// -------------------------------------------------------------------------------------------------
+
+@implementation SPUtilsTest
+
+- (void)testGetNextPowerOfTwo
+{   
+    STAssertEquals(1, [SPUtils nextPowerOfTwo:0], @"wrong power of two");
+    STAssertEquals(1, [SPUtils nextPowerOfTwo:1], @"wrong power of two");
+    STAssertEquals(2, [SPUtils nextPowerOfTwo:2], @"wrong power of two");
+    STAssertEquals(4, [SPUtils nextPowerOfTwo:3], @"wrong power of two");
+    STAssertEquals(4, [SPUtils nextPowerOfTwo:4], @"wrong power of two");
+    STAssertEquals(8, [SPUtils nextPowerOfTwo:5], @"wrong power of two");
+    STAssertEquals(8, [SPUtils nextPowerOfTwo:6], @"wrong power of two");
+    STAssertEquals(256, [SPUtils nextPowerOfTwo:129], @"wrong power of two");
+    STAssertEquals(256, [SPUtils nextPowerOfTwo:255], @"wrong power of two");
+    STAssertEquals(256, [SPUtils nextPowerOfTwo:256], @"wrong power of two");    
+}
+
+- (void)testGetRandomFloat
+{
+    for (int i=0; i<20; ++i)
+    {
+        float rnd = [SPUtils randomFloat];
+        STAssertTrue(rnd >= 0.0f, @"random number too small");
+        STAssertTrue(rnd < 1.0f,  @"random number too big");        
+    }    
+}
+
+- (void)testGetRandomInt
+{
+    for (int i=0; i<20; ++i)
+    {
+        int rnd = [SPUtils randomIntBetween:5 and:10];
+        STAssertTrue(rnd >= 5, @"random number too small");
+        STAssertTrue(rnd < 10, @"random number too big");        
+    }    
+}
+
+@end
+
+#endif
\ No newline at end of file
diff --git a/libs/sparrow/util/atlas_generator/README b/libs/sparrow/util/atlas_generator/README
new file mode 100644 (file)
index 0000000..4a3e22e
--- /dev/null
@@ -0,0 +1,54 @@
+----------------------------------------------------------------------------------------------------
+The Sparrow Atlas Generator
+----------------------------------------------------------------------------------------------------
+
+The atlas generator is a Ruby script that arranges several images in one big atlas-texture. 
+Using a textures atlas is important for the performance of your game, and it's very easy
+to use it in Sparrow.
+
+----------------------------------------------------------------------------------------------------
+Installation
+----------------------------------------------------------------------------------------------------
+
+1) Requirement: "ImageMagick"
+
+First, you need the command line tool "ImageMagick" (http://www.imagemagick.org).
+The recommended way to install it is via the packaging system "MacPorts" (http://www.macports.org/)
+
+After installing MacPorts, open a terminal and type:
+
+  sudo port install ImageMagick
+
+Now, go for a coffee, this will take a while ;-)
+
+2) Requirement: "QuickMagick"
+
+Now, we need a ruby gem that is used to work with ImageMagick. It is called "QuickMagick", and 
+is installed the following way:
+
+  sudo gem install quick_magick
+
+3) Make the script executable:
+
+To allow the script to execute, we need to tell OS X that it can be executed:
+
+  chmod u+x generate_atlas.rb
+
+4) That's it!
+
+----------------------------------------------------------------------------------------------------
+Usage
+----------------------------------------------------------------------------------------------------
+
+To find out how the script works, have a look at the help screen.
+
+./generate_atlas.rb --help
+
+Here is simple usage example:
+
+./generate_atlas.rb input/*.png output/atlas.xml
+
+Some options:
+
+./generate_atlas.rb --scale 0.5 --padding 2 --sharpen --maxsize 2048x2048 *.png atlas.xml
+
diff --git a/libs/sparrow/util/atlas_generator/generate_atlas.rb b/libs/sparrow/util/atlas_generator/generate_atlas.rb
new file mode 100755 (executable)
index 0000000..7da374a
--- /dev/null
@@ -0,0 +1,255 @@
+#!/usr/bin/env ruby
+
+#
+#  generate_atlas.rb
+#  Sparrow
+#
+#  Created by Daniel Sperl on 14.06.2010
+#  Copyright 2010 Incognitek. All rights reserved.
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the Simplified BSD License.
+#
+
+require 'rubygems'
+require 'ftools'
+require 'quick_magick'
+require 'optparse'
+require 'ostruct'
+require 'rexml/document'
+
+include REXML
+include QuickMagick
+
+# --------------------------------------------------------------------------------------------------
+# --- worker classes -------------------------------------------------------------------------------
+# --------------------------------------------------------------------------------------------------
+
+class TextureNode
+  attr_accessor :image
+  attr_reader :rect
+  
+  def initialize(x, y, width, height)
+    @rect = Rectangle.new(x, y, width, height)
+  end  
+  
+  def insert_image(image, scale, padding)
+    if @image.nil?
+      img_width = (image.width * scale.to_f + padding).to_i
+      img_height = (image.height * scale.to_f + padding).to_i
+      if (img_width <= @rect.width and img_height <= @rect.height)
+        @image = image
+        @children = []
+        rest_width = @rect.width - img_width
+        rest_height = @rect.height - img_height        
+        if (rest_width > rest_height)
+          @children << TextureNode.new(@rect.x, @rect.y + img_height, 
+                                       img_width, @rect.height - img_height)
+          @children << TextureNode.new(@rect.x + img_width, @rect.y,
+                                       @rect.width - img_width, @rect.height)
+        else
+          @children << TextureNode.new(@rect.x + img_width, @rect.y,
+                                       @rect.width - img_width, img_height)
+          @children << TextureNode.new(@rect.x, @rect.y + img_height,
+                                       @rect.width, @rect.height - img_height)
+        end
+        return self
+      else
+        return nil
+      end
+    else
+      new_node = @children[0].insert_image(image, scale, padding)
+      unless (new_node.nil?)
+        return new_node
+      else
+        return @children[1].insert_image(image, scale, padding)
+      end
+    end    
+  end
+  
+  def image_name
+    image.nil? ? nil : File.basename(image.image_filename, File.extname(image.image_filename))
+  end
+  
+end
+
+class Rectangle
+  attr_reader :x, :y, :width, :height  
+  def initialize(x, y, width, height)
+    @x = x
+    @y = y
+    @width = width
+    @height = height    
+  end
+end
+
+# --------------------------------------------------------------------------------------------------
+# --- option parsing -------------------------------------------------------------------------------
+# --------------------------------------------------------------------------------------------------
+
+options = OpenStruct.new
+options.scale = 1
+options.padding = 1
+options.sharpen = false
+options.maxsize = [1024, 1024]
+options.verbose = true
+option_parser = OptionParser.new do |opts|
+
+  opts.banner = "Usage: #{File.basename(__FILE__)} [options] source_images target_file"
+  opts.separator ""
+  opts.separator "Options:" 
+  opts.on('-s', '--scale FACTOR', Float, 'Scale textures') do |factor|
+    options.scale = factor
+  end
+  opts.on('-p', '--padding DISTANCE', Integer, 'Padding of subtextures (default: 1)') do |distance|
+    options.padding = distance
+  end
+  opts.on('-r', '--sharpen', 'Sharpen images') do
+    options.sharpen = true
+  end
+  
+  opts.on('-m', '--maxsize WIDTHxHEIGHT', 'Maximum atlas size (default: 1024x1024)') do |size|
+    options.maxsize = size.split('x').collect { |v| v.to_i }    
+  end
+  
+  opts.on("-v", "--[no-]verbose", "Run verbosely (default: verbose)") do |verbose|
+    options.verbose = verbose
+  end
+  opts.on_tail('-h', '--help', 'Show this message') do
+    puts opts
+    exit
+  end
+end
+
+if (ARGV.length < 2)
+  puts option_parser
+  exit
+else
+  option_parser.parse!
+end
+
+@verbose = options.verbose
+def vputs(text)
+  puts text if @verbose
+end
+
+# --------------------------------------------------------------------------------------------------
+# --- image processing -----------------------------------------------------------------------------
+# --------------------------------------------------------------------------------------------------
+
+# get commandline-arguments
+source_files = ARGV.clone
+target_file = source_files.pop
+
+if source_files.count == 0
+  puts "No images found!"
+  exit
+end
+
+images = source_files.collect { |file| Image.read(file).first }
+images.sort! { |i, j| (j.width * j.height) <=> (i.width * i.height) }
+
+vputs "Found #{images.count} images."
+
+# Start with a small atlas and make it bigger until all textures fit.
+
+image_nodes = []
+current_width, current_height = 32, 32
+loop_count = 0
+textures_fit = false
+
+until textures_fit do
+  textures_fit = true
+  root_node = TextureNode.new(0, 0, current_width + options.padding, current_height + options.padding)
+  image_nodes = []
+
+  images.each do |image|
+    new_node = root_node.insert_image(image, options.scale, options.padding)
+    if new_node.nil? then
+      textures_fit = false
+      break
+    else
+      image_nodes << new_node
+    end
+  end
+  
+  unless textures_fit
+    loop_count += 1
+    if loop_count % 3 < 2
+      current_width *= 2
+    else
+      current_width /= 2
+      current_height *= 2
+    end
+  end
+end
+
+if current_width > options.maxsize[0] or current_height > options.maxsize[1]
+  puts "Error: Textures did not fit into a #{options.maxsize[0]}x#{options.maxsize[1]} image"
+  exit
+else
+  vputs "Arranged images in #{current_width}x#{current_height} atlas."
+end
+
+vputs "Drawing atlas ..."
+
+atlas_image = Image::solid(current_width, current_height, :transparent)
+
+# Use box filter for downscaling by whole number (creates sharper output)
+atlas_image.append_to_operators 'filter', 'Box' if (options.scale == 0.5 || options.scale == 0.25)
+
+# Draw all images into atlas
+image_nodes.each do |node|
+  atlas_image.draw_image :over, node.rect.x, node.rect.y, 
+                                (node.image.width * options.scale).to_i, 
+                                (node.image.height * options.scale).to_i, 
+                                "{" + node.image.image_filename + "}"
+end
+
+# Apply sharpening filter if requested
+atlas_image.append_to_operators 'sharpen', 1 if (options.sharpen)
+
+# Save output to correct path
+target_path = File.dirname(target_file)
+File.makedirs target_path
+atlas_name  = File.basename(target_file, File.extname(target_file))
+atlas_image_path = File.join(target_path, atlas_name) + ".png" 
+atlas_image.save(atlas_image_path)
+
+puts "Saved #{atlas_image_path}"
+
+# --------------------------------------------------------------------------------------------------
+# --- XML creation ---------------------------------------------------------------------------------
+# --------------------------------------------------------------------------------------------------
+
+atlas_xml_path = File.join(target_path, atlas_name) + ".xml"
+
+xml_doc = Document.new
+xml_doc << XMLDecl.default
+root = Element.new "TextureAtlas"
+root.attributes["imagePath"] = atlas_name + ".png"
+
+image_nodes.each do |node|
+  subnode = Element.new "SubTexture"
+  subnode.attributes["name"] = File.basename(node.image_name)
+  subnode.attributes["x"] = node.rect.x
+  subnode.attributes["y"] = node.rect.y
+  subnode.attributes["width"] = node.image.width * options.scale.to_f
+  subnode.attributes["height"] = node.image.height * options.scale.to_f
+  root << subnode
+end
+
+xml_doc << root
+
+File.open(atlas_xml_path, "w") do |file|
+  xml_doc.write file, 2
+end
+
+puts "Saved #{atlas_xml_path}"
\ No newline at end of file
diff --git a/libs/sparrow/util/hiero2sparrow/README b/libs/sparrow/util/hiero2sparrow/README
new file mode 100644 (file)
index 0000000..d5b0f9d
--- /dev/null
@@ -0,0 +1,16 @@
+--- hiero2sparrow.rb ---
+
+This Ruby script converts Bitmap Font files created with the "Hiero"-Tool:
+-> http://slick.cokeandcode.com/
+to an XML format. That's the format Sparrow expects for its Bitmap Fonts.
+
+As an alternative, you can use the Bitmap Font Generator from AngelCode:
+-> http://www.angelcode.com/products/bmfont/
+It can export this xml format directly. However, this tool works only in MS Windows.
+
+Usage: hiero2sparrow.rb input.fnt [output.fnt]
+
+- The output parameter is optional. If omitted, the xml data overwrites the input file.
+
+A tip when you use Hiero: 
+Add at least 1 pixel padding to the "up" and "left" directions!
\ No newline at end of file
diff --git a/libs/sparrow/util/hiero2sparrow/hiero2sparrow.rb b/libs/sparrow/util/hiero2sparrow/hiero2sparrow.rb
new file mode 100755 (executable)
index 0000000..38a32c2
--- /dev/null
@@ -0,0 +1,82 @@
+#!/usr/bin/env ruby
+
+#
+#  hiero2sparrow.rb
+#  Sparrow
+#
+#  Created by Daniel Sperl on 11.02.2010
+#  Copyright 2010 Incognitek. All rights reserved.
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the Simplified BSD License.
+#
+
+#  This script converts Bitmap Font files created with the "Hiero"-Tool to the 
+#  format that is expected by Sparrow. See README for more information.
+
+require "rexml/document"
+
+SPACE_MARKER = '#SPACE#'
+
+if $*.count == 0
+  puts "Usage: hiero2sparrow.rb input.fnt [output.fnt]"
+  exit
+end
+
+# get commandline-arguments
+input_file_path = $*[0]
+output_file_path = $*.count >= 2 ? $*[1] : input_file_path
+
+if !File.exist?(input_file_path)
+  puts "File #{input_file_path} not found!"
+  exit
+end
+
+xml_doc = REXML::Document.new
+xml_doc << REXML::XMLDecl.new
+
+root_element = xml_doc.add_element "font"
+current_parent = root_element
+pages_parent = nil
+chars_parent = nil
+num_chars = 0
+
+puts "Parsing #{input_file_path} ..."
+
+IO.foreach(input_file_path) do |line|    
+  
+  # replace spaces within quotes
+  line.gsub!(/"(\S*)(\s+)(\S*")/) { |match| $1 + SPACE_MARKER + $3 }  
+
+  line_parts = line.split /\s+/    
+  element_name = line_parts.shift  
+
+  next if element_name == "chars"  
+  
+  if element_name == "page"
+    pages_parent ||= root_element.add_element "pages"
+    current_parent = pages_parent
+  elsif element_name == "char"
+    chars_parent ||= root_element.add_element "chars"
+    current_parent = chars_parent
+    num_chars += 1
+  end
+  
+  current_element = current_parent.add_element element_name
+  
+  line_parts.each do |part|
+    name, value = part.split("=")
+    current_element.attributes[name] = value.gsub('"', "").gsub(SPACE_MARKER, " ")
+  end
+    
+end
+
+chars_parent.attributes["count"] = num_chars
+
+puts "Saving output to #{output_file_path} ..."
+
+File.open output_file_path, 'w+' do |file|
+  xml_doc.write file, 2
+end
+
+puts "Finished successfully."
diff --git a/libs/sparrow/util/packer2sparrow/README b/libs/sparrow/util/packer2sparrow/README
new file mode 100644 (file)
index 0000000..9a1cc08
--- /dev/null
@@ -0,0 +1,11 @@
+--- packer2sparrow.rb ---
+
+This Ruby script converts texture atlas files created with the "Packer"-Tool:
+-> http://slick.cokeandcode.com/
+to the XML format that Sparrow expects.
+
+Usage: packer2sparrow input.xml image.png [output.xml]
+
+- The image parameter will be saved in the XML-file, and should be the path to the image on the 
+  iPhone,relative to the atlas XML.
+- The output parameter is optional. If omitted, the xml data overwrites the input file.
diff --git a/libs/sparrow/util/packer2sparrow/packer2sparrow.rb b/libs/sparrow/util/packer2sparrow/packer2sparrow.rb
new file mode 100644 (file)
index 0000000..b7c3e11
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/bin/env ruby
+
+#
+#  packer2sparrow.rb
+#  Sparrow
+#
+#  Created by Daniel Sperl on 11.02.2010
+#  Copyright 2010 Incognitek. All rights reserved.
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the Simplified BSD License.
+#
+
+#  This script converts atlas XML files created with the "Packer"-Tool to the 
+#  format that is expected by Sparrow. See README for more information.
+
+if $*.count < 2
+  puts "Usage: packer2sparrow.rb input.xml image.png [output.xml]"
+  exit
+end
+
+# get commandline-arguments
+input_file_path = $*[0]
+image_file_path = $*[1]
+output_file_path = $*.count >= 3 ? $*[2] : input_file_path
+
+if !File.exist?(input_file_path)
+  puts "File #{input_file_path} not found!"
+  exit
+end
+
+puts "Parsing #{input_file_path} ..."
+
+contents = IO.read input_file_path
+  
+contents.gsub! "<sheet>", "<TextureAtlas imagePath='#{image_file_path}'>"
+contents.gsub! "<sprite ", "<SubTexture "
+contents.gsub! /.png"/, '"'
+contents.gsub! "</sheet>", "</TextureAtlas>"
+
+puts "Saving output to #{output_file_path} ..."
+
+File.open output_file_path, 'w+' do |file|
+  file << contents
+end
+
+puts "Finished successfully."
diff --git a/libs/sparrow/util/texture_scaler/README b/libs/sparrow/util/texture_scaler/README
new file mode 100644 (file)
index 0000000..fa6de85
--- /dev/null
@@ -0,0 +1,35 @@
+----------------------------------------------------------------------------------------------------
+The Sparrow Texture Scaler
+----------------------------------------------------------------------------------------------------
+
+Use this Ruby script to prepare your images for iPhone 4's Retina display.
+
+Create your graphics for the high resolution display. As recommended by Apple, append the suffix
+"@2x" to the images filenames, e.g.: texture@2x.png
+
+Now use this script to scale them down to the low resolution display, removing the suffix, and
+sharpening them (optionally).
+
+----------------------------------------------------------------------------------------------------
+Installation
+----------------------------------------------------------------------------------------------------
+
+This script has the same requirements as the atlas generator. Look in the atlas generator's 
+README file for instructions.
+
+----------------------------------------------------------------------------------------------------
+Usage
+----------------------------------------------------------------------------------------------------
+
+To find out how the script works, have a look at the help screen.
+
+./scale_textures.rb --help
+
+Here is simple usage example:
+
+./scale_textures.rb input/*@2x.png output/
+
+Automatic sharpening:
+
+./scale_textures.rb --sharpen input/*@2x.png output/
+
diff --git a/libs/sparrow/util/texture_scaler/scale_textures.rb b/libs/sparrow/util/texture_scaler/scale_textures.rb
new file mode 100755 (executable)
index 0000000..5bc1802
--- /dev/null
@@ -0,0 +1,96 @@
+#!/usr/bin/env ruby
+
+#
+#  scale_textures.rb
+#  Sparrow
+#
+#  Created by Daniel Sperl on 09.07.2010
+#  Copyright 2010 Incognitek. All rights reserved.
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the Simplified BSD License.
+#
+
+require 'rubygems'
+require 'ftools'
+require 'quick_magick'
+require 'optparse'
+require 'ostruct'
+
+include QuickMagick
+
+# --------------------------------------------------------------------------------------------------
+# --- option parsing -------------------------------------------------------------------------------
+# --------------------------------------------------------------------------------------------------
+
+options = OpenStruct.new
+options.sharpen = false
+options.scale = 0.5
+options.purge_suffix = "@2x"
+options.append_suffix = ""
+
+option_parser = OptionParser.new do |opts|
+
+  opts.banner = "Usage: #{File.basename(__FILE__)} [options] source_images target_path"
+  opts.separator ""
+  opts.separator "Options:" 
+  opts.on('-s', '--scale FACTOR', Float, 'Scale textures (default: 0.5)') do |factor|
+    options.scale = factor
+  end
+  opts.on('-r', '--sharpen', 'Sharpen images') do
+    options.sharpen = true
+  end
+  
+  opts.on('-p', '--purge_suffix SUFFIX', 'Remove suffix on copies (default: @2x)') do |suffix|
+    options.purge_suffix = suffix
+  end
+  
+  opts.on("-a", "--append_suffix SUFFIX", "Add suffix to copies") do |suffix|
+    options.append_suffix = suffix
+  end
+  opts.on_tail('-h', '--help', 'Show this message') do
+    puts opts
+    exit
+  end
+end
+
+if (ARGV.length < 2)
+  puts option_parser
+  exit
+else
+  option_parser.parse!
+end
+
+# --------------------------------------------------------------------------------------------------
+# --- image processing -----------------------------------------------------------------------------
+# --------------------------------------------------------------------------------------------------
+
+source_files = ARGV.clone
+target_path = source_files.pop
+
+images = source_files.collect { |file| Image.read(file).first }
+
+if images.count == 0
+  puts "No images found!"
+  exit
+else
+  File.makedirs target_path
+end
+
+images.each do |image|
+  extname = File.extname(image.image_filename)
+  basename = File.basename(image.image_filename, extname)
+  basename.gsub!(options.purge_suffix, "") if basename.end_with? options.purge_suffix
+  fullpath = File.join(target_path, basename + options.append_suffix + extname)
+  
+  image.resize "#{options.scale * 100}%"
+  image.append_to_operators 'filter', 'Box' if (options.scale == 0.5 || options.scale == 0.25)  
+  image.append_to_operators 'sharpen', 1    if (options.sharpen)
+  image.save fullpath
+  puts "Saved #{fullpath}"
+end
index ed4a241..18a38f9 100644 (file)
@@ -7,6 +7,7 @@
        objects = {
 
 /* Begin PBXBuildFile section */
+               49132ED31372483800DFB46D /* libSparrow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 49132ED21372483700DFB46D /* libSparrow.a */; };
                499668C713692E2D006E8125 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 499668C613692E2D006E8125 /* UIKit.framework */; };
                499668C913692E2D006E8125 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 499668C813692E2D006E8125 /* Foundation.framework */; };
                499668CB13692E2D006E8125 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 499668CA13692E2D006E8125 /* CoreGraphics.framework */; };
@@ -23,7 +24,6 @@
                499668F613692E2D006E8125 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 499668F413692E2D006E8125 /* InfoPlist.strings */; };
                499668F913692E2D006E8125 /* tanksTests.h in Resources */ = {isa = PBXBuildFile; fileRef = 499668F813692E2D006E8125 /* tanksTests.h */; };
                499668FB13692E2D006E8125 /* tanksTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 499668FA13692E2D006E8125 /* tanksTests.m */; };
-               49966911136930E8006E8125 /* libSparrow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 49966910136930E8006E8125 /* libSparrow.a */; };
                49966917136930E8006E8125 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49966912136930E8006E8125 /* AudioToolbox.framework */; };
                49966918136930E8006E8125 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49966913136930E8006E8125 /* AVFoundation.framework */; };
                49966919136930E8006E8125 /* OpenAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49966914136930E8006E8125 /* OpenAL.framework */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
+               49132ECC1372459400DFB46D /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 49132EC41372459300DFB46D /* Sparrow.xcodeproj */;
+                       proxyType = 2;
+                       remoteGlobalIDString = DEFE4BC2101B317600E22471;
+                       remoteInfo = Sparrow;
+               };
+               49132ECE1372459400DFB46D /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 49132EC41372459300DFB46D /* Sparrow.xcodeproj */;
+                       proxyType = 2;
+                       remoteGlobalIDString = DEEA9335101E32A30071DD21;
+                       remoteInfo = UnitTests;
+               };
+               49132ED01372482D00DFB46D /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 49132EC41372459300DFB46D /* Sparrow.xcodeproj */;
+                       proxyType = 1;
+                       remoteGlobalIDString = DEFE4BC1101B317600E22471;
+                       remoteInfo = Sparrow;
+               };
                499668EF13692E2D006E8125 /* PBXContainerItemProxy */ = {
                        isa = PBXContainerItemProxy;
                        containerPortal = 499668B913692E2D006E8125 /* Project object */;
@@ -43,6 +64,8 @@
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXFileReference section */
+               49132EC41372459300DFB46D /* Sparrow.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Sparrow.xcodeproj; path = ../libs/sparrow/src/Sparrow.xcodeproj; sourceTree = "<group>"; };
+               49132ED21372483700DFB46D /* libSparrow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libSparrow.a; sourceTree = SOURCE_ROOT; };
                499668C213692E2D006E8125 /* tanks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tanks.app; sourceTree = BUILT_PRODUCTS_DIR; };
                499668C613692E2D006E8125 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
                499668C813692E2D006E8125 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
@@ -65,8 +88,6 @@
                499668F713692E2D006E8125 /* tanksTests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "tanksTests-Prefix.pch"; sourceTree = "<group>"; };
                499668F813692E2D006E8125 /* tanksTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tanksTests.h; sourceTree = "<group>"; };
                499668FA13692E2D006E8125 /* tanksTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = tanksTests.m; sourceTree = "<group>"; };
-               4996690413692FB1006E8125 /* Sparrow.xcodeproj */ = {isa = PBXFileReference; name = Sparrow.xcodeproj; path = ../../../ios/sparrow/src/Sparrow.xcodeproj; sourceTree = SPARROW_SRC; };
-               49966910136930E8006E8125 /* libSparrow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libSparrow.a; sourceTree = SOURCE_ROOT; };
                49966912136930E8006E8125 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
                49966913136930E8006E8125 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
                49966914136930E8006E8125 /* OpenAL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenAL.framework; path = System/Library/Frameworks/OpenAL.framework; sourceTree = SDKROOT; };
                        isa = PBXFrameworksBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               49132ED31372483800DFB46D /* libSparrow.a in Frameworks */,
                                49966917136930E8006E8125 /* AudioToolbox.framework in Frameworks */,
                                49966918136930E8006E8125 /* AVFoundation.framework in Frameworks */,
                                49966919136930E8006E8125 /* OpenAL.framework in Frameworks */,
                                4996691A136930E8006E8125 /* OpenGLES.framework in Frameworks */,
                                4996691B136930E8006E8125 /* QuartzCore.framework in Frameworks */,
-                               49966911136930E8006E8125 /* libSparrow.a in Frameworks */,
                                499668C713692E2D006E8125 /* UIKit.framework in Frameworks */,
                                499668C913692E2D006E8125 /* Foundation.framework in Frameworks */,
                                499668CB13692E2D006E8125 /* CoreGraphics.framework in Frameworks */,
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+               49132EC51372459300DFB46D /* Products */ = {
+                       isa = PBXGroup;
+                       children = (
+                               49132ECD1372459400DFB46D /* libSparrow.a */,
+                               49132ECF1372459400DFB46D /* UnitTest.octest */,
+                       );
+                       name = Products;
+                       sourceTree = "<group>";
+               };
                499668B713692E2D006E8125 = {
                        isa = PBXGroup;
                        children = (
-                               4996690413692FB1006E8125 /* Sparrow.xcodeproj */,
+                               49132EC41372459300DFB46D /* Sparrow.xcodeproj */,
                                499668CC13692E2D006E8125 /* tanks */,
                                499668F113692E2D006E8125 /* tanksTests */,
                                499668C513692E2D006E8125 /* Frameworks */,
                499668C513692E2D006E8125 /* Frameworks */ = {
                        isa = PBXGroup;
                        children = (
+                               49132ED21372483700DFB46D /* libSparrow.a */,
                                49966912136930E8006E8125 /* AudioToolbox.framework */,
                                49966913136930E8006E8125 /* AVFoundation.framework */,
                                49966914136930E8006E8125 /* OpenAL.framework */,
                                49966915136930E8006E8125 /* OpenGLES.framework */,
                                49966916136930E8006E8125 /* QuartzCore.framework */,
-                               49966910136930E8006E8125 /* libSparrow.a */,
                                499668C613692E2D006E8125 /* UIKit.framework */,
                                499668C813692E2D006E8125 /* Foundation.framework */,
                                499668CA13692E2D006E8125 /* CoreGraphics.framework */,
                        buildRules = (
                        );
                        dependencies = (
+                               49132ED11372482D00DFB46D /* PBXTargetDependency */,
                        );
                        name = tanks;
                        productName = tanks;
                        mainGroup = 499668B713692E2D006E8125;
                        productRefGroup = 499668C313692E2D006E8125 /* Products */;
                        projectDirPath = "";
+                       projectReferences = (
+                               {
+                                       ProductGroup = 49132EC51372459300DFB46D /* Products */;
+                                       ProjectRef = 49132EC41372459300DFB46D /* Sparrow.xcodeproj */;
+                               },
+                       );
                        projectRoot = "";
                        targets = (
                                499668C113692E2D006E8125 /* tanks */,
                };
 /* End PBXProject section */
 
+/* Begin PBXReferenceProxy section */
+               49132ECD1372459400DFB46D /* libSparrow.a */ = {
+                       isa = PBXReferenceProxy;
+                       fileType = archive.ar;
+                       path = libSparrow.a;
+                       remoteRef = 49132ECC1372459400DFB46D /* PBXContainerItemProxy */;
+                       sourceTree = BUILT_PRODUCTS_DIR;
+               };
+               49132ECF1372459400DFB46D /* UnitTest.octest */ = {
+                       isa = PBXReferenceProxy;
+                       fileType = wrapper.cfbundle;
+                       path = UnitTest.octest;
+                       remoteRef = 49132ECE1372459400DFB46D /* PBXContainerItemProxy */;
+                       sourceTree = BUILT_PRODUCTS_DIR;
+               };
+/* End PBXReferenceProxy section */
+
 /* Begin PBXResourcesBuildPhase section */
                499668C013692E2D006E8125 /* Resources */ = {
                        isa = PBXResourcesBuildPhase;
 /* End PBXSourcesBuildPhase section */
 
 /* Begin PBXTargetDependency section */
+               49132ED11372482D00DFB46D /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       name = Sparrow;
+                       targetProxy = 49132ED01372482D00DFB46D /* PBXContainerItemProxy */;
+               };
                499668F013692E2D006E8125 /* PBXTargetDependency */ = {
                        isa = PBXTargetDependency;
                        target = 499668C113692E2D006E8125 /* tanks */;
                499668FF13692E2D006E8125 /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
-                               ALWAYS_SEARCH_USER_PATHS = NO;
+                               ALWAYS_SEARCH_USER_PATHS = YES;
                                COPY_PHASE_STRIP = NO;
                                GCC_DYNAMIC_NO_PIC = NO;
                                GCC_PRECOMPILE_PREFIX_HEADER = YES;
                                GCC_PREFIX_HEADER = "tanks/tanks-Prefix.pch";
                                INFOPLIST_FILE = "tanks/tanks-Info.plist";
                                LIBRARY_SEARCH_PATHS = (
-                                       "$(inherited)",
+                                       "../libs/**",
                                        "\"$(SRCROOT)\"",
                                );
                                OTHER_LDFLAGS = (
                                        "-all_load",
                                );
                                PRODUCT_NAME = "$(TARGET_NAME)";
-                               USER_HEADER_SEARCH_PATHS = "${SPARROW_SRC}/**";
+                               USER_HEADER_SEARCH_PATHS = "../libs/**";
                                WRAPPER_EXTENSION = app;
                        };
                        name = Debug;
                4996690013692E2D006E8125 /* Release */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
-                               ALWAYS_SEARCH_USER_PATHS = NO;
+                               ALWAYS_SEARCH_USER_PATHS = YES;
                                COPY_PHASE_STRIP = YES;
                                GCC_PRECOMPILE_PREFIX_HEADER = YES;
                                GCC_PREFIX_HEADER = "tanks/tanks-Prefix.pch";
                                INFOPLIST_FILE = "tanks/tanks-Info.plist";
                                LIBRARY_SEARCH_PATHS = (
-                                       "$(inherited)",
+                                       "../libs/**",
                                        "\"$(SRCROOT)\"",
                                );
                                OTHER_LDFLAGS = (
                                        "-all_load",
                                );
                                PRODUCT_NAME = "$(TARGET_NAME)";
-                               USER_HEADER_SEARCH_PATHS = "${SPARROW_SRC}/**";
+                               USER_HEADER_SEARCH_PATHS = "../libs/**";
                                VALIDATE_PRODUCT = YES;
                                WRAPPER_EXTENSION = app;
                        };
                                4996690013692E2D006E8125 /* Release */,
                        );
                        defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
                };
                4996690113692E2D006E8125 /* Build configuration list for PBXNativeTarget "tanksTests" */ = {
                        isa = XCConfigurationList;
                                4996690313692E2D006E8125 /* Release */,
                        );
                        defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
                };
 /* End XCConfigurationList section */
        };