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