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