From 72d018ea047efbea81bbfd2918f909358b12ddef Mon Sep 17 00:00:00 2001 From: Terry Cavanagh Date: Fri, 2 Dec 2022 18:19:58 +0100 Subject: [PATCH] Update mobile version to mobile v2.2.1 The android version just got a much needed update to fix some resolution issues on devices with cutouts. It turns out the mobile source was actually pretty out of date, like 3 versions out of date! This commit brings it up to date. All the changes have just been about keeping the game running on modern devices, though. The biggest change was adding the Starling library to the project, which made the game GPU powered and sped the whole thing up. --- mobile_version/application.xml | 176 +- mobile_version/src/EditorDataclass.as | 5 - mobile_version/src/EmbeddedAssets.as | 12 + mobile_version/src/LevelMetaData.as | 1 - mobile_version/src/Load.as | 16 + mobile_version/src/Main.as | 1238 ++++----- mobile_version/src/Preloader.as | 310 --- mobile_version/src/blockclass.as | 7 +- .../src/com/adobe/utils/AGALMiniAssembler.as | 805 ++++++ mobile_version/src/device.as | 2 + mobile_version/src/dwgraphicsclass.as | 2229 +++++++++-------- mobile_version/src/edentitiesclass.as | 5 - mobile_version/src/editor.as | 148 +- mobile_version/src/edlevelclass.as | 5 - mobile_version/src/entclass.as | 7 +- mobile_version/src/entityclass.as | 65 +- mobile_version/src/gameclass.as | 18 +- mobile_version/src/helpclass.as | 4 +- mobile_version/src/includes/input.as | 18 +- mobile_version/src/includes/logic.as | 2 +- mobile_version/src/includes/render.as | 203 +- mobile_version/src/includes/scripts.as | 4 +- .../src/includes/terminalscripts.as | 10 +- mobile_version/src/mapclass.as | 13 +- mobile_version/src/musicclass.as | 5 +- mobile_version/src/platformclass.as | 3 - mobile_version/src/saveclass.as | 5 +- mobile_version/src/scoreclass.as | 30 +- mobile_version/src/scriptclass.as | 9 +- .../src/starling/animation/DelayedCall.as | 140 ++ .../src/starling/animation/IAnimatable.as | 30 + .../src/starling/animation/Juggler.as | 396 +++ .../src/starling/animation/Transitions.as | 233 ++ .../src/starling/animation/Tween.as | 451 ++++ .../src/starling/assets/AssetFactory.as | 122 + .../src/starling/assets/AssetFactoryHelper.as | 124 + .../src/starling/assets/AssetManager.as | 1034 ++++++++ .../src/starling/assets/AssetReference.as | 56 + .../src/starling/assets/AssetType.as | 40 + .../src/starling/assets/AtfTextureFactory.as | 73 + .../starling/assets/BitmapTextureFactory.as | 167 ++ .../src/starling/assets/ByteArrayFactory.as | 30 + .../src/starling/assets/DataLoader.as | 138 + .../src/starling/assets/JsonFactory.as | 40 + .../src/starling/assets/SoundFactory.as | 60 + .../src/starling/assets/XmlFactory.as | 84 + mobile_version/src/starling/core/Starling.as | 1051 ++++++++ .../src/starling/core/StatsDisplay.as | 160 ++ .../src/starling/core/starling_internal.as | 22 + .../src/starling/display/BlendMode.as | 136 + mobile_version/src/starling/display/Button.as | 455 ++++ .../src/starling/display/ButtonState.as | 33 + mobile_version/src/starling/display/Canvas.as | 121 + .../src/starling/display/DisplayObject.as | 1111 ++++++++ .../display/DisplayObjectContainer.as | 508 ++++ mobile_version/src/starling/display/Image.as | 493 ++++ mobile_version/src/starling/display/Mesh.as | 332 +++ .../src/starling/display/MeshBatch.as | 300 +++ .../src/starling/display/MovieClip.as | 479 ++++ mobile_version/src/starling/display/Quad.as | 209 ++ mobile_version/src/starling/display/Sprite.as | 27 + .../src/starling/display/Sprite3D.as | 372 +++ mobile_version/src/starling/display/Stage.as | 350 +++ .../src/starling/errors/AbstractClassError.as | 23 + .../starling/errors/AbstractMethodError.as | 22 + .../starling/errors/MissingContextError.as | 23 + .../src/starling/errors/NotSupportedError.as | 23 + .../src/starling/events/EnterFrameEvent.as | 34 + mobile_version/src/starling/events/Event.as | 190 ++ .../src/starling/events/EventDispatcher.as | 215 ++ .../src/starling/events/KeyboardEvent.as | 88 + .../src/starling/events/ResizeEvent.as | 44 + mobile_version/src/starling/events/Touch.as | 240 ++ .../src/starling/events/TouchEvent.as | 218 ++ .../src/starling/events/TouchMarker.as | 104 + .../src/starling/events/TouchPhase.as | 53 + .../src/starling/events/TouchProcessor.as | 481 ++++ .../src/starling/filters/BlurFilter.as | 305 +++ .../src/starling/filters/ColorMatrixFilter.as | 318 +++ .../src/starling/filters/CompositeFilter.as | 327 +++ .../starling/filters/DisplacementMapFilter.as | 355 +++ .../src/starling/filters/DropShadowFilter.as | 172 ++ .../src/starling/filters/FilterChain.as | 168 ++ .../src/starling/filters/FilterHelper.as | 215 ++ .../src/starling/filters/FragmentFilter.as | 634 +++++ .../src/starling/filters/GlowFilter.as | 125 + .../src/starling/filters/IFilterHelper.as | 49 + mobile_version/src/starling/geom/Polygon.as | 613 +++++ .../src/starling/rendering/BatchProcessor.as | 218 ++ .../src/starling/rendering/BatchToken.as | 77 + .../src/starling/rendering/Effect.as | 381 +++ .../src/starling/rendering/FilterEffect.as | 147 ++ .../src/starling/rendering/IndexData.as | 552 ++++ .../src/starling/rendering/MeshEffect.as | 145 ++ .../src/starling/rendering/Painter.as | 909 +++++++ .../src/starling/rendering/Program.as | 104 + .../src/starling/rendering/RenderState.as | 400 +++ .../src/starling/rendering/VertexData.as | 1129 +++++++++ .../starling/rendering/VertexDataAttribute.as | 47 + .../starling/rendering/VertexDataFormat.as | 275 ++ .../src/starling/styles/DistanceFieldStyle.as | 684 +++++ .../src/starling/styles/MeshStyle.as | 436 ++++ .../src/starling/text/BitmapChar.as | 86 + .../src/starling/text/BitmapFont.as | 548 ++++ .../src/starling/text/ITextCompositor.as | 29 + .../src/starling/text/MiniBitmapFont.as | 314 +++ mobile_version/src/starling/text/TextField.as | 555 ++++ .../src/starling/text/TextFieldAutoSize.as | 36 + .../src/starling/text/TextFormat.as | 236 ++ .../src/starling/text/TextOptions.as | 87 + .../src/starling/text/TrueTypeCompositor.as | 187 ++ .../src/starling/textures/AtfData.as | 94 + .../starling/textures/ConcretePotTexture.as | 140 ++ .../textures/ConcreteRectangleTexture.as | 54 + .../src/starling/textures/ConcreteTexture.as | 269 ++ .../starling/textures/ConcreteVideoTexture.as | 98 + .../src/starling/textures/RenderTexture.as | 336 +++ .../src/starling/textures/SubTexture.as | 183 ++ .../src/starling/textures/Texture.as | 753 ++++++ .../src/starling/textures/TextureAtlas.as | 221 ++ .../src/starling/textures/TextureOptions.as | 100 + .../src/starling/textures/TextureSmoothing.as | 36 + mobile_version/src/starling/utils/Align.as | 55 + .../src/starling/utils/AssetManager.as | 1302 ++++++++++ mobile_version/src/starling/utils/Color.as | 117 + mobile_version/src/starling/utils/MathUtil.as | 124 + .../src/starling/utils/MatrixUtil.as | 382 +++ .../src/starling/utils/MeshSubset.as | 46 + mobile_version/src/starling/utils/MeshUtil.as | 93 + mobile_version/src/starling/utils/Padding.as | 105 + mobile_version/src/starling/utils/Pool.as | 143 ++ .../src/starling/utils/RectangleUtil.as | 246 ++ .../src/starling/utils/RenderUtil.as | 275 ++ .../src/starling/utils/ScaleMode.as | 30 + .../src/starling/utils/StringUtil.as | 95 + .../src/starling/utils/SystemUtil.as | 197 ++ mobile_version/src/starling/utils/deg2rad.as | 18 + mobile_version/src/starling/utils/execute.as | 42 + mobile_version/src/starling/utils/rad2deg.as | 18 + mobile_version/src/textboxclass.as | 17 +- 140 files changed, 30533 insertions(+), 2409 deletions(-) create mode 100644 mobile_version/src/EmbeddedAssets.as create mode 100644 mobile_version/src/Load.as delete mode 100644 mobile_version/src/Preloader.as create mode 100644 mobile_version/src/com/adobe/utils/AGALMiniAssembler.as create mode 100644 mobile_version/src/starling/animation/DelayedCall.as create mode 100644 mobile_version/src/starling/animation/IAnimatable.as create mode 100644 mobile_version/src/starling/animation/Juggler.as create mode 100644 mobile_version/src/starling/animation/Transitions.as create mode 100644 mobile_version/src/starling/animation/Tween.as create mode 100644 mobile_version/src/starling/assets/AssetFactory.as create mode 100644 mobile_version/src/starling/assets/AssetFactoryHelper.as create mode 100644 mobile_version/src/starling/assets/AssetManager.as create mode 100644 mobile_version/src/starling/assets/AssetReference.as create mode 100644 mobile_version/src/starling/assets/AssetType.as create mode 100644 mobile_version/src/starling/assets/AtfTextureFactory.as create mode 100644 mobile_version/src/starling/assets/BitmapTextureFactory.as create mode 100644 mobile_version/src/starling/assets/ByteArrayFactory.as create mode 100644 mobile_version/src/starling/assets/DataLoader.as create mode 100644 mobile_version/src/starling/assets/JsonFactory.as create mode 100644 mobile_version/src/starling/assets/SoundFactory.as create mode 100644 mobile_version/src/starling/assets/XmlFactory.as create mode 100644 mobile_version/src/starling/core/Starling.as create mode 100644 mobile_version/src/starling/core/StatsDisplay.as create mode 100644 mobile_version/src/starling/core/starling_internal.as create mode 100644 mobile_version/src/starling/display/BlendMode.as create mode 100644 mobile_version/src/starling/display/Button.as create mode 100644 mobile_version/src/starling/display/ButtonState.as create mode 100644 mobile_version/src/starling/display/Canvas.as create mode 100644 mobile_version/src/starling/display/DisplayObject.as create mode 100644 mobile_version/src/starling/display/DisplayObjectContainer.as create mode 100644 mobile_version/src/starling/display/Image.as create mode 100644 mobile_version/src/starling/display/Mesh.as create mode 100644 mobile_version/src/starling/display/MeshBatch.as create mode 100644 mobile_version/src/starling/display/MovieClip.as create mode 100644 mobile_version/src/starling/display/Quad.as create mode 100644 mobile_version/src/starling/display/Sprite.as create mode 100644 mobile_version/src/starling/display/Sprite3D.as create mode 100644 mobile_version/src/starling/display/Stage.as create mode 100644 mobile_version/src/starling/errors/AbstractClassError.as create mode 100644 mobile_version/src/starling/errors/AbstractMethodError.as create mode 100644 mobile_version/src/starling/errors/MissingContextError.as create mode 100644 mobile_version/src/starling/errors/NotSupportedError.as create mode 100644 mobile_version/src/starling/events/EnterFrameEvent.as create mode 100644 mobile_version/src/starling/events/Event.as create mode 100644 mobile_version/src/starling/events/EventDispatcher.as create mode 100644 mobile_version/src/starling/events/KeyboardEvent.as create mode 100644 mobile_version/src/starling/events/ResizeEvent.as create mode 100644 mobile_version/src/starling/events/Touch.as create mode 100644 mobile_version/src/starling/events/TouchEvent.as create mode 100644 mobile_version/src/starling/events/TouchMarker.as create mode 100644 mobile_version/src/starling/events/TouchPhase.as create mode 100644 mobile_version/src/starling/events/TouchProcessor.as create mode 100644 mobile_version/src/starling/filters/BlurFilter.as create mode 100644 mobile_version/src/starling/filters/ColorMatrixFilter.as create mode 100644 mobile_version/src/starling/filters/CompositeFilter.as create mode 100644 mobile_version/src/starling/filters/DisplacementMapFilter.as create mode 100644 mobile_version/src/starling/filters/DropShadowFilter.as create mode 100644 mobile_version/src/starling/filters/FilterChain.as create mode 100644 mobile_version/src/starling/filters/FilterHelper.as create mode 100644 mobile_version/src/starling/filters/FragmentFilter.as create mode 100644 mobile_version/src/starling/filters/GlowFilter.as create mode 100644 mobile_version/src/starling/filters/IFilterHelper.as create mode 100644 mobile_version/src/starling/geom/Polygon.as create mode 100644 mobile_version/src/starling/rendering/BatchProcessor.as create mode 100644 mobile_version/src/starling/rendering/BatchToken.as create mode 100644 mobile_version/src/starling/rendering/Effect.as create mode 100644 mobile_version/src/starling/rendering/FilterEffect.as create mode 100644 mobile_version/src/starling/rendering/IndexData.as create mode 100644 mobile_version/src/starling/rendering/MeshEffect.as create mode 100644 mobile_version/src/starling/rendering/Painter.as create mode 100644 mobile_version/src/starling/rendering/Program.as create mode 100644 mobile_version/src/starling/rendering/RenderState.as create mode 100644 mobile_version/src/starling/rendering/VertexData.as create mode 100644 mobile_version/src/starling/rendering/VertexDataAttribute.as create mode 100644 mobile_version/src/starling/rendering/VertexDataFormat.as create mode 100644 mobile_version/src/starling/styles/DistanceFieldStyle.as create mode 100644 mobile_version/src/starling/styles/MeshStyle.as create mode 100644 mobile_version/src/starling/text/BitmapChar.as create mode 100644 mobile_version/src/starling/text/BitmapFont.as create mode 100644 mobile_version/src/starling/text/ITextCompositor.as create mode 100644 mobile_version/src/starling/text/MiniBitmapFont.as create mode 100644 mobile_version/src/starling/text/TextField.as create mode 100644 mobile_version/src/starling/text/TextFieldAutoSize.as create mode 100644 mobile_version/src/starling/text/TextFormat.as create mode 100644 mobile_version/src/starling/text/TextOptions.as create mode 100644 mobile_version/src/starling/text/TrueTypeCompositor.as create mode 100644 mobile_version/src/starling/textures/AtfData.as create mode 100644 mobile_version/src/starling/textures/ConcretePotTexture.as create mode 100644 mobile_version/src/starling/textures/ConcreteRectangleTexture.as create mode 100644 mobile_version/src/starling/textures/ConcreteTexture.as create mode 100644 mobile_version/src/starling/textures/ConcreteVideoTexture.as create mode 100644 mobile_version/src/starling/textures/RenderTexture.as create mode 100644 mobile_version/src/starling/textures/SubTexture.as create mode 100644 mobile_version/src/starling/textures/Texture.as create mode 100644 mobile_version/src/starling/textures/TextureAtlas.as create mode 100644 mobile_version/src/starling/textures/TextureOptions.as create mode 100644 mobile_version/src/starling/textures/TextureSmoothing.as create mode 100644 mobile_version/src/starling/utils/Align.as create mode 100644 mobile_version/src/starling/utils/AssetManager.as create mode 100644 mobile_version/src/starling/utils/Color.as create mode 100644 mobile_version/src/starling/utils/MathUtil.as create mode 100644 mobile_version/src/starling/utils/MatrixUtil.as create mode 100644 mobile_version/src/starling/utils/MeshSubset.as create mode 100644 mobile_version/src/starling/utils/MeshUtil.as create mode 100644 mobile_version/src/starling/utils/Padding.as create mode 100644 mobile_version/src/starling/utils/Pool.as create mode 100644 mobile_version/src/starling/utils/RectangleUtil.as create mode 100644 mobile_version/src/starling/utils/RenderUtil.as create mode 100644 mobile_version/src/starling/utils/ScaleMode.as create mode 100644 mobile_version/src/starling/utils/StringUtil.as create mode 100644 mobile_version/src/starling/utils/SystemUtil.as create mode 100644 mobile_version/src/starling/utils/deg2rad.as create mode 100644 mobile_version/src/starling/utils/execute.as create mode 100644 mobile_version/src/starling/utils/rad2deg.as diff --git a/mobile_version/application.xml b/mobile_version/application.xml index fcafcbbd..353e1ec7 100644 --- a/mobile_version/application.xml +++ b/mobile_version/application.xml @@ -1,73 +1,105 @@ - - - com.distractionware.vvvvvvmobile - 1.02 - mobileDevice - VVVVVV - VVVVVV - - - - - -]]> - - - UIStatusBarStyle -UIStatusBarStyleBlackOpaque -UIRequiresPersistentWiFi -NO -UIPrerenderedIcon - -UIApplicationExitsOnSuspend - -UIDeviceFamily - - - 1 - - 2 -]]> - standard - - - VVVVVV - vvvvvv.swf - true - landscape - gpu - standard - true - true - - - icons/icon_48.png - icons/icon_57.png - icons/icon_72.png - icons/icon_76.png - icons/icon_96.png - icons/icon_114.png - icons/icon_120.png - icons/icon_144.png - icons/icon_152.png - icons/icon_512.png - icons/icon_1024.png - - - - com.milkmangames.extensions.GameCenter - - + + + com.distractionware.vvvvvvmobile + 2.2.1 + mobileDevice + VVVVVV + VVVVVV + + + + + + + + + + + + + + + + + + + + + + ]]> + true + + + UIStatusBarStyle + UIStatusBarStyleBlackOpaque + UIRequiresPersistentWiFi + NO + UIPrerenderedIcon + + UIApplicationExitsOnSuspend + + + UIDeviceFamily + + + 1 + + + + + + ]]> + high + + + + VVVVVV + vvvvvv.swf + true + true + standard + true + landscape + direct + + + + + icons/icon_48.png + icons/icon_57.png + icons/icon_72.png + icons/icon_76.png + icons/icon_96.png + icons/icon_114.png + icons/icon_120.png + icons/icon_144.png + icons/icon_152.png + icons/icon_512.png + icons/icon_1024.png + + + + + + \ No newline at end of file diff --git a/mobile_version/src/EditorDataclass.as b/mobile_version/src/EditorDataclass.as index ad88845e..b7766a5b 100644 --- a/mobile_version/src/EditorDataclass.as +++ b/mobile_version/src/EditorDataclass.as @@ -1,9 +1,4 @@ package { - import flash.display.*; - import flash.geom.*; - import flash.events.*; - import flash.net.*; - public class EditorDataclass{ public function EditorDataclass():void { clear(); diff --git a/mobile_version/src/EmbeddedAssets.as b/mobile_version/src/EmbeddedAssets.as new file mode 100644 index 00000000..350a2338 --- /dev/null +++ b/mobile_version/src/EmbeddedAssets.as @@ -0,0 +1,12 @@ +package { + public class EmbeddedAssets { + /* PNG texture */ + [Embed(source = "../data/vvvvvv_graphics.png")] + public static const vvvvvv_graphics:Class; + + /* XML file */ + [Embed(source = "../data/vvvvvv_graphics.xml", + mimeType = "application/octet-stream")] + public static const vvvvvv_graphics_xml:Class; + } +} \ No newline at end of file diff --git a/mobile_version/src/LevelMetaData.as b/mobile_version/src/LevelMetaData.as index bea5ef71..ac8f3779 100644 --- a/mobile_version/src/LevelMetaData.as +++ b/mobile_version/src/LevelMetaData.as @@ -1,5 +1,4 @@ package { - import flash.display.*; import flash.geom.*; import flash.events.*; import flash.net.*; diff --git a/mobile_version/src/Load.as b/mobile_version/src/Load.as new file mode 100644 index 00000000..5e08380d --- /dev/null +++ b/mobile_version/src/Load.as @@ -0,0 +1,16 @@ +package{ + import flash.display.Sprite; + import starling.core.Starling; + + [SWF(backgroundColor="#000000", frameRate="30")] + public class Load extends Sprite{ + private var _starling:Starling; + + public function Load() { + Starling.multitouchEnabled = true; + + _starling = new Starling(Main, stage); + _starling.start(); + } + } +} \ No newline at end of file diff --git a/mobile_version/src/Main.as b/mobile_version/src/Main.as index a010f250..2e1e3ba2 100644 --- a/mobile_version/src/Main.as +++ b/mobile_version/src/Main.as @@ -1,36 +1,35 @@ -package{ - import flash.display.*; +package { import flash.geom.*; - import flash.events.*; - import flash.net.*; - import flash.media.*; - import flash.text.*; - import flash.ui.ContextMenu; - import flash.ui.ContextMenuItem; - import flash.events.ContextMenuEvent; + import flash.net.*; + import flash.display.Screen; + import flash.display.StageAlign; + import flash.display.StageQuality; + import flash.display.StageScaleMode; + import flash.system.Capabilities; + import starling.core.Starling; + import starling.display.Image; + import starling.text.BitmapFont; + import starling.text.TextField; + import starling.core.StatsDisplay; + import starling.events.Event; + import starling.utils.Color; + import starling.textures.*; + import starling.display.*; + import flash.filesystem.File; + import starling.events.KeyboardEvent; import flash.ui.Keyboard; - import flash.ui.Mouse; + import starling.utils.AssetManager; import bigroom.input.KeyPoll; - import flash.system.fscommand; - import flash.system.Capabilities; + //import com.mesmotronic.ane.AndroidFullScreen; //This doesn't seem to be needed anymore + import flash.display.StageDisplayState; + import flash.media.*; + + import flash.utils.getTimer; import flash.utils.Timer; - //import com.mesmotronic.ane.AndroidFullScreen; + import flash.events.TimerEvent; - //import com.sociodox.theminer.TheMiner; //Profiler - - //Real value - //[SWF(width = "1136", height = "768", frameRate = "30", backgroundColor = "#000000")] //Set the size and color of the Flash file - //Big frame for big androids! - //[SWF(width = "3000", height = "2000", frameRate = "30", backgroundColor = "#000000")] //Set the size and color of the Flash file - [SWF(frameRate = "30", backgroundColor = "#000000")] //Set the size and color of the Flash file - //iPad test - //[SWF(width = "1024", height = "768", frameRate="60", backgroundColor = "#000000")] //Set the size and color of the Flash file - //iPhone test - //[SWF(width = "480", height = "320", frameRate="30", backgroundColor = "#000000")] //Set the size and color of the Flash file - //[SWF(width = "960", height = "640", frameRate="30", backgroundColor = "#000000")] //Set the size and color of the Flash file - - public class Main extends Sprite { + public class Main extends Sprite{ static public var BLOCK:Number = 0; static public var TRIGGER:Number = 1; static public var DAMAGE:Number = 2; @@ -49,7 +48,11 @@ public var EDITORMODE:int = 8; public var CONTROLTUTORIALMODE:int = 9; - public function Main():void { + public var addedtwice:Boolean = false; + + public function Main() { + super(); + if (stage) gameinit(); else addEventListener(Event.ADDED_TO_STAGE, gameinit); } @@ -57,565 +60,395 @@ private function gameinit(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, gameinit); // entry point - var tempbmp:Bitmap; - - this.mouseEnabled = false; - this.mouseChildren = false; + if (addedtwice) return; //I don't think I actually need this, but... I have a hunch + addedtwice = true; //Ok: quick security check to make sure it doesn't get posted about - if (sitelock()) { - key = new KeyPoll(stage); + key = new KeyPoll(Starling.current.nativeStage); + + //immersivemode = AndroidFullScreen.isSupported; + stage.addEventListener(Event.RESIZE, function(e:Event):void{ + androidresize(); + }); + + device.deviceresolution = device.ANDROID; + + editor.init(); //Load in all the levels + + //General game variables + obj.init(); + help.init(); + + /*obj.createblock(BLOCK,20,200,128,16); + obj.createblock(BLOCK,130,170,128,16); + obj.createblock(BLOCK,240,150,128,16); + obj.createblock(BLOCK,240,20,128,16); + obj.createblock(BLOCK,130,40,128,16); + obj.createblock(BLOCK,20,60,128,16);*/ + + //Input + key.definestickrange(device.xres / 2, 0, 6); + + SoundMixer.soundTransform = new SoundTransform(1); + + music.currentsong = -1; music.musicfade = 0;//no music, no amb + music.initefchannels(); music.currentefchan = 0; + music.nicechange = -1; + + music.numplays = 0; + music.musicchan.push(new music_1()); // 0: Level Complete + music.musicchan.push(new vmaintheme_hq()); // 1: VVVVVV Main Theme (Pushing Forward) + music.musicchan.push(new vtempo_hq()); // 2: VVVVVV Tempo Theme (Positive Force) + music.musicchan.push(new vpfa_hq()); // 3: Potential for Anything + music.musicchan.push(new passionforexploring()); // 4: UU Brothers Instrumental + music.musicchan.push(new souleye_intermission()); // 5: Jingle: Intermission + music.musicchan.push(new presentingvvvvvv()); // 6: Jingle: Menu Loop + music.musicchan.push(new music_2()); // 7: Jingle: Game Complete + music.musicchan.push(new configmegamix_hq()); // 8: Config Megamix + music.musicchan.push(new posreverse()); // 9: Tempo Theme, Reversed + music.musicchan.push(new poppot()); // 10: Extra + music.musicchan.push(new pipedream_hq()); // 11: Highscore + music.musicchan.push(new pressurecooker_hq()); // 12: Pressure Cooker + music.musicchan.push(new pacedenergy()); // NEW 13: Paced Energy + music.musicchan.push(new piercingthesky()); // NEW 14: Piercing the Sky + music.musicchan.push(new predestinedfateremix()); // NEW 15: Predestined Fate Remix + + music.efchan.push(new ef_0()); + music.efchan.push(new ef_1()); + music.efchan.push(new ef_2()); + music.efchan.push(new ef_3()); + music.efchan.push(new ef_4()); + music.efchan.push(new ef_5()); + music.efchan.push(new ef_6()); + music.efchan.push(new ef_7()); + music.efchan.push(new ef_8()); + music.efchan.push(new ef_9()); + music.efchan.push(new ef_10()); + music.efchan.push(new ef_11()); + music.efchan.push(new ef_12()); + music.efchan.push(new ef_13()); + music.efchan.push(new ef_14()); + music.efchan.push(new ef_15()); + music.efchan.push(new ef_16()); + music.efchan.push(new ef_17()); + music.efchan.push(new ef_18()); + music.efchan.push(new ef_19()); + music.efchan.push(new ef_20()); + music.efchan.push(new ef_21()); + music.efchan.push(new ef_22()); + music.efchan.push(new ef_23()); + music.efchan.push(new ef_24()); + music.efchan.push(new ef_25()); + music.efchan.push(new ef_26()); + music.efchan.push(new ef_27()); - //ANDROID SPECIFIC CODE HERE - /* - if (AndroidFullScreen.isSupported) { - if (!AndroidFullScreen.immersiveMode()){ - stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE; - device.xres = flash.system.Capabilities.screenResolutionX; - device.yres = flash.system.Capabilities.screenResolutionY; - immersivemode = false; - }else { - device.xres = stage.stageWidth; - device.yres = stage.stageHeight; - immersivemode = true; - } - }else { - */ - stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE; - device.xres = flash.system.Capabilities.screenResolutionX; - device.yres = flash.system.Capabilities.screenResolutionY; - immersivemode = false; - //} + /* + Graphics Init + */ + dwgfx.init(stage); + //Load assets + dwgfx.starlingassets = new AssetManager(); + dwgfx.starlingassets.enqueue(EmbeddedAssets); + + Starling.current.nativeStage.scaleMode = StageScaleMode.NO_SCALE; + Starling.current.nativeStage.align = StageAlign.TOP_LEFT; + Starling.current.nativeStage.quality = StageQuality.LOW; + + //TO DO: orientation code + game = new gameclass(dwgfx, map, obj, help, music); - /* - if (immersivemode) { - stage.addEventListener(Event.RESIZE, androidresize); - } - */ - /* - trace("NOTE: REMEMBER TO FIX RESOLUTION SUPPORT BEFORE BUILDING"); - device.xres = 1024; - device.yres = 768; - device.deviceresolution = device.IPAD; - */ - //Testing:Let's be an ipad - /* - device.xres = 1024; - device.yres = 768; - device.deviceresolution = device.IPAD; - */ - /* - device.xres = 480; - device.yres = 320; - device.deviceresolution = device.IPHONE; - */ - /* - device.xres = 960; - device.yres = 640; - device.deviceresolution = device.IPHONE; - */ - - editor.init(); //Load in all the levels - - if (device.xres < device.yres) { - //Switch them! - t = device.yres; - device.yres = device.xres; - device.xres = t; - } - - if (device.yres < 768) { - device.deviceresolution = device.IPHONE; - }else { - device.deviceresolution = device.IPAD; - } - - //Manually enable androids here! Mostly just changed buttons sizes - // device.deviceresolution = device.ANDROID; - - //General game variables - obj.init(); - help.init(); - - /*obj.createblock(BLOCK,20,200,128,16); - obj.createblock(BLOCK,130,170,128,16); - obj.createblock(BLOCK,240,150,128,16); - obj.createblock(BLOCK,240,20,128,16); - obj.createblock(BLOCK,130,40,128,16); - obj.createblock(BLOCK,20,60,128,16);*/ - - //Input - key.definestickrange(device.xres / 2, 0, 6); - - SoundMixer.soundTransform = new SoundTransform(1); - - music.currentsong = -1; music.musicfade = 0;//no music, no amb - music.initefchannels(); music.currentefchan = 0; - music.nicechange = -1; - - music.numplays = 0; - music.musicchan.push(new music_1()); // 0: Level Complete - music.musicchan.push(new vmaintheme_hq()); // 1: VVVVVV Main Theme (Pushing Forward) - music.musicchan.push(new vtempo_hq()); // 2: VVVVVV Tempo Theme (Positive Force) - music.musicchan.push(new vpfa_hq()); // 3: Potential for Anything - music.musicchan.push(new passionforexploring()); // 4: UU Brothers Instrumental - music.musicchan.push(new souleye_intermission()); // 5: Jingle: Intermission - music.musicchan.push(new presentingvvvvvv()); // 6: Jingle: Menu Loop - music.musicchan.push(new music_2()); // 7: Jingle: Game Complete - music.musicchan.push(new configmegamix_hq()); // 8: Config Megamix - music.musicchan.push(new posreverse()); // 9: Tempo Theme, Reversed - music.musicchan.push(new poppot()); // 10: Extra - music.musicchan.push(new pipedream_hq()); // 11: Highscore - music.musicchan.push(new pressurecooker_hq()); // 12: Pressure Cooker - music.musicchan.push(new pacedenergy()); // NEW 13: Paced Energy - music.musicchan.push(new piercingthesky()); // NEW 14: Piercing the Sky - music.musicchan.push(new predestinedfateremix()); // NEW 15: Predestined Fate Remix - - music.efchan.push(new ef_0()); - music.efchan.push(new ef_1()); - music.efchan.push(new ef_2()); - music.efchan.push(new ef_3()); - music.efchan.push(new ef_4()); - music.efchan.push(new ef_5()); - music.efchan.push(new ef_6()); - music.efchan.push(new ef_7()); - music.efchan.push(new ef_8()); - music.efchan.push(new ef_9()); - music.efchan.push(new ef_10()); - music.efchan.push(new ef_11()); - music.efchan.push(new ef_12()); - music.efchan.push(new ef_13()); - music.efchan.push(new ef_14()); - music.efchan.push(new ef_15()); - music.efchan.push(new ef_16()); - music.efchan.push(new ef_17()); - music.efchan.push(new ef_18()); - music.efchan.push(new ef_19()); - music.efchan.push(new ef_20()); - music.efchan.push(new ef_21()); - music.efchan.push(new ef_22()); - music.efchan.push(new ef_23()); - music.efchan.push(new ef_24()); - music.efchan.push(new ef_25()); - music.efchan.push(new ef_26()); - music.efchan.push(new ef_27()); - - /* - Graphics Init - */ - //First we init the class and add its display list to the main display list - - dwgfx.buttonimg.push(new BitmapData(92, 30, true, 0x00000000)); - dwgfx.buttonimg.push(new BitmapData(92, 30, true, 0x00000000)); - dwgfx.buttonimg.push(new BitmapData(40, 40, true, 0x00000000)); - dwgfx.buttonimg.push(new BitmapData(40, 40, true, 0x00000000)); - tempbmp = new im_button_0(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addbutton(); - tempbmp = new im_button_1(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addbutton(); - tempbmp = new im_button_2(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addbutton(); - tempbmp = new im_button_3(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addbutton(); - tempbmp = new im_button_4(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addbutton(); - tempbmp = new im_button_5(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addbutton(); - tempbmp = new im_button_6(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addbutton(); - tempbmp = new im_button_7(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addbutton(); - tempbmp = new im_button_8(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addbutton(); - tempbmp = new im_button_9(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addbutton(); - tempbmp = new im_button_10(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addbutton(); - tempbmp = new im_button_11(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addbutton(); - tempbmp = new im_button_12(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addbutton(); - - dwgfx.init(); - - //We load all our graphics in: - tempbmp = new im_tiles(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.maketilearray(); - tempbmp = new im_tiles2(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.maketile2array(); - tempbmp = new im_tiles3(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.maketile3array(); - tempbmp = new im_sprites(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.makespritearray(); - tempbmp = new im_flipsprites(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.makeflipspritearray(); - tempbmp = new im_bfont(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.makebfont(); - tempbmp = new im_bfontmask(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.makebfontmask(); - tempbmp = new im_teleporter(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.maketelearray(); - tempbmp = new im_entcolours(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.makeentcolourarray(); - //Load in the images - tempbmp = new im_image0(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addimage(); // 0 - tempbmp = new im_image1(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addimage(); // 1 (this is the minimap!) - tempbmp = new im_image2(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addimage(); // 2 - tempbmp = new im_image3(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addimage(); // 3 - tempbmp = new im_image4(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addimage(); // 4 - tempbmp = new im_image5(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addimage(); // 5 - tempbmp = new im_image6(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addimage(); // 6 - tempbmp = new im_image7(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addimage(); // 7 - tempbmp = new im_image8(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addimage(); // 8 - tempbmp = new im_image9(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addimage(); // 9 - tempbmp = new im_image10(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addimage(); // 10 - - tempbmp = new im_image1(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addimage(); // Minimap - tempbmp = new im_image1(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addimage(); // Minimap - - tempbmp = new im_mobileimage1(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addmobileimage(); - tempbmp = new im_mobileimage2(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addmobileimage(); - tempbmp = new im_mobileimage3(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addmobileimage(); - tempbmp = new im_mobileimage4(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addmobileimage(); - tempbmp = new im_mobileimage5(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addmobileimage(); - tempbmp = new im_mobileimage6(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addmobileimage(); - tempbmp = new im_mobileimage7(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addmobileimage(); - tempbmp = new im_mobileimage8(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addmobileimage(); - tempbmp = new im_mobileimage9(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addmobileimage(); - - tempbmp = new im_imgplayerlevel0(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - tempbmp = new im_imgplayerlevel1(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - tempbmp = new im_imgplayerlevel2(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - tempbmp = new im_imgplayerlevel3(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - tempbmp = new im_imgplayerlevel4(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - tempbmp = new im_imgplayerlevel5(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - tempbmp = new im_imgplayerlevel6(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - tempbmp = new im_imgplayerlevel7(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - tempbmp = new im_imgplayerlevel8(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - tempbmp = new im_imgplayerlevel9(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - tempbmp = new im_imgplayerlevel10(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - tempbmp = new im_imgplayerlevel11(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - tempbmp = new im_imgplayerlevel12(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - tempbmp = new im_imgplayerlevel13(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - tempbmp = new im_imgplayerlevel14(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - tempbmp = new im_imgplayerlevel15(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - tempbmp = new im_imgplayerlevel16(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.addplayerlevelimage(); - - //Now that the graphics are loaded, init the background buffer - dwgfx.buffer=new BitmapData(320,240,false,0x000000); - - stage.scaleMode = StageScaleMode.NO_SCALE; - stage.align = StageAlign.TOP_LEFT; - stage.quality = StageQuality.LOW; - - //EXACT FIT - dwgfx.screensizemultiplier = device.yres / 240; - dwgfx.screen.width = 320 * dwgfx.screensizemultiplier; - dwgfx.screen.height = device.yres; - - dwgfx.screen.x = (device.xres / 2) - (320 * dwgfx.screensizemultiplier / 2); - dwgfx.screen.y = 0; - - dwgfx.initbuttonpositions(); - - //PIXEL PERFECT - /* - dwgfx.screensizemultiplier = int(device.yres / 240); - dwgfx.screen.width = 320 * dwgfx.screensizemultiplier; - dwgfx.screen.height = 240 * dwgfx.screensizemultiplier; - - dwgfx.screenoffx = (device.xres / 2) - (320 * dwgfx.screensizemultiplier / 2); - dwgfx.screenoffy = (device.yres / 2) - (240 * dwgfx.screensizemultiplier / 2); - dwgfx.screen.x = dwgfx.screenoffx; - dwgfx.screen.y = dwgfx.screenoffy; - */ - - addChild(dwgfx); - //stage.addChild(new TheMiner()); //Profiler - - - //Iphone orientation fix - var startOrientation:String = stage.orientation; - if (startOrientation == StageOrientation.DEFAULT || startOrientation == StageOrientation.UPSIDE_DOWN){ - //stage.setOrientation(StageOrientation.ROTATED_RIGHT); - stage.setAspectRatio(StageAspectRatio.LANDSCAPE); - }else{ - stage.setOrientation(startOrientation); - } - stage.addEventListener(StageOrientationEvent.ORIENTATION_CHANGING, orientationChangeListener); - - game = new gameclass(dwgfx, map, obj, help, music); - - - map.ypos = (700-29) * 8; - map.bypos = map.ypos / 2; - map.cameramode = 0; - - - ///Test Start: - //-- Comment this for real start - /* - gamestate = GAMEMODE; - /*map.finalmode = true; //Enable final level mode - //map.finalx = 41; map.finaly = 52; //Midpoint - //map.finalx = 48; map.finaly = 52; //Just before the tower + map.ypos = (700-29) * 8; + map.bypos = map.ypos / 2; + map.cameramode = 0; + + + ///Test Start: + //-- Comment this for real start + /* + gamestate = GAMEMODE; + /*map.finalmode = true; //Enable final level mode + //map.finalx = 41; map.finaly = 52; //Midpoint + //map.finalx = 48; map.finaly = 52; //Just before the tower + map.finalx = 46; map.finaly = 54; //Current + //map.finalstretch = true; + map.final_colormode = true; + map.final_mapcol = 0; + map.final_colorframe = 0; + */ + /* + game.starttest(obj, music); + obj.createentity(game, game.savex, game.savey, 0); //In this game, constant, never destroyed + map.gotoroom(game.saverx, game.savery, dwgfx, game, obj, music); + music.play(1); + */ + //game.crewstats[1] = true; + //game.crewstats[2] = true; + //game.crewstats[3] = true; + //game.crewstats[4] = true; + //game.crewstats[5] = true; + //script.load("intro"); + + //crew member test + //obj.createentity(game, game.savex - 10, game.savey - 10, 14); + //game.companion = 6; //different rules for different members + + + + //-- + //Uncomment this if testing the final level + /* + map.finalmode = true; //Enable final level mode map.finalx = 46; map.finaly = 54; //Current - //map.finalstretch = true; + //map.finalx = 41; map.finaly = 52; //Midpoint + map.final_colormode = false; map.final_mapcol = 0; map.final_colorframe = 0; + */ + //And this if after the midpoint: + /* + map.finalx = 52; map.finaly = 53; //Current + map.finalstretch = true; map.final_colormode = true; map.final_mapcol = 0; map.final_colorframe = 0; - */ - /* - game.starttest(obj, music); - obj.createentity(game, game.savex, game.savey, 0); //In this game, constant, never destroyed - map.gotoroom(game.saverx, game.savery, dwgfx, game, obj, music); - music.play(1); - */ - //game.crewstats[1] = true; - //game.crewstats[2] = true; - //game.crewstats[3] = true; - //game.crewstats[4] = true; - //game.crewstats[5] = true; - //script.load("intro"); - - //crew member test - //obj.createentity(game, game.savex - 10, game.savey - 10, 14); - //game.companion = 6; //different rules for different members - - - - //-- - //Uncomment this if testing the final level - /* - map.finalmode = true; //Enable final level mode - map.finalx = 46; map.finaly = 54; //Current - //map.finalx = 41; map.finaly = 52; //Midpoint - map.final_colormode = false; map.final_mapcol = 0; map.final_colorframe = 0; - */ - //And this if after the midpoint: - /* - map.finalx = 52; map.finaly = 53; //Current - map.finalstretch = true; - map.final_colormode = true; - map.final_mapcol = 0; - map.final_colorframe = 0; - //map.background = 6; - */ - //-- Jumping right in - /* - game.gamestate = GAMEMODE; - game.starttest(obj, music); - //game.loadquick(map, obj, music); - obj.createentity(game, game.savex, game.savey, 0); //In this game, constant, never destroyed - map.gotoroom(game.saverx, game.savery, dwgfx, game, obj, music); - music.play(4); - //fscommand("quit"); - - game.crewstats[1] = true; - //game.crewstats[2] = true; - game.crewstats[3] = true; - game.crewstats[4] = true; - //game.crewstats[5] = true; - - map.showtargets = true; - map.showteleporters = true; - //map.showtrinkets = true; - - //obj.flags[34] = 1; - //obj.flags[20] = 1; - //obj.flags[67] = 1; //Game complete + //map.background = 6; + */ + //-- Jumping right in + /* + game.gamestate = GAMEMODE; + game.starttest(obj, music); + //game.loadquick(map, obj, music); + obj.createentity(game, game.savex, game.savey, 0); //In this game, constant, never destroyed + map.gotoroom(game.saverx, game.savery, dwgfx, game, obj, music); + music.play(4); + //fscommand("quit"); + + game.crewstats[1] = true; + //game.crewstats[2] = true; + game.crewstats[3] = true; + game.crewstats[4] = true; + //game.crewstats[5] = true; + + map.showtargets = true; + map.showteleporters = true; + //map.showtrinkets = true; + + //obj.flags[34] = 1; + //obj.flags[20] = 1; + //obj.flags[67] = 1; //Game complete + for (i = 0; i < 20; i++) { + obj.collect[i] = true; + } + game.trinkets = 19; obj.collect[18] = false; + game.stat_trinkets = 20; + //obj.altstates = 1; + + //initilise map info + for (j = 0; j < 20; j++) { for (i = 0; i < 20; i++) { - obj.collect[i] = true; + map.explored[i + (j * 20)] = 1; } - game.trinkets = 19; obj.collect[18] = false; - game.stat_trinkets = 20; - //obj.altstates = 1; - - //initilise map info - for (j = 0; j < 20; j++) { - for (i = 0; i < 20; i++) { - map.explored[i + (j * 20)] = 1; - } - } - */ - //obj.entities[obj.getplayer()].size = 13; - - //game.gamestate = 6; - - //game.intimetrail = true; game.timetrialcountdown = 0; - //game.nodeathmode = true; - //dwgfx.flipmode = true; - //game.nocutscenes = true; - //map.invincibility = true; - //stage.frameRate = 24; - //game.colourblindmode = true; - //game.noflashingmode = true; - //for intermission 2 test - //game.lastsaved = 3; - /* - game.swnmode = true; - game.swngame = 2; - game.swndelay = 120; - game.swntimer = 60 * 30; - */ - //script.load("intermission_2"); - - - //for intermission 1 test - /* - game.companion = 11; - game.supercrewmate = true; - game.scmprogress = 0; - game.scmprogress = 10; - game.lastsaved = 4; - music.play(8); - */ - //game.sfpsmode = true; //Run at 60 FPS - //stage.frameRate = 60; // doesn't work, it's a bit more complex than this - will have to do a double sized window instead... - - //dwgfx.flipmode = true; - - //Testing some unlock stuff - /* - game.unlock[9] = true; //Space Station Intro Time Trial - game.unlock[10] = true; //Lab Time Trial - game.unlock[11] = true; //Tower Time Trial - game.unlock[12] = true; //Space Station 2 Time Trial - game.unlock[13] = true; //Warp Zone Time Trial - game.unlock[14] = true; //Final Level Time Trial - - game.unlock[17] = true; //No death mode - game.unlock[18] = true; //Flip Mode - - */ - //-- Normal start, with no fancy stuff! - - /* - game.gamestate = CLICKTOSTART; - dwgfx.createtextbox(" Click to Start ", 96, 107, 164, 164, 255); - dwgfx.textboxcenter(); - */ - //No click to start any more! Instead go right to the menu - - //INIT GRAPHIC MODE HERE - game.loadstats(map, dwgfx); - - //updategraphicsmode(game, dwgfx); - - game.gamestate = TITLEMODE; - - game.menustart = false; - game.mainmenu = 0; - - /*if (game.quicksummary != "") { - game.mainmenu = 2; - } - if (game.telesummary != "") { - game.mainmenu = 1; - }*/ //what the hell is that for - - - /* - game.gamestate = 6; - game.menustart = true; - map.ypos = (700-29) * 8; - map.bypos = map.ypos / 2; - map.cameramode = 0; - */ - //--Everything after this is screen recording - - //ok, screenrecording here - //don't record anything (comment both next parts) - - game.recording = 0; - - if(game.recording==1){ - trace("warning! recording input!"); - }else if(game.recording==2){ - trace("playing back input!"); - game.recordstring = "blahblahblah"; - help.toclipboard(game.recordstring); - - game.initplayback(); - game.playbackpos = 5; - game.savex = game.playback[0]; game.savey = game.playback[1]; - game.saverx = game.playback[2]; game.savery = game.playback[3]; - game.savegc = game.playback[4]; game.savedir = 1; - } - - //addEventListener(Event.ENTER_FRAME, mainloop); - - // start the tick-timer, which updates roughly every 4 milliseconds - _timer.addEventListener(TimerEvent.TIMER, mainloop); - _timer.start(); - }else { - dwgfx.init(); - addChild(dwgfx); - //We load the font in: - tempbmp = new im_bfont(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.makebfont(); - tempbmp = new im_bfontmask(); dwgfx.buffer = tempbmp.bitmapData; dwgfx.makebfontmask(); - //Now that the graphics are loaded, init the background buffer - dwgfx.buffer = new BitmapData(320, 240, false, 0x000000); - - addEventListener(Event.ENTER_FRAME, lockedloop); } - } - - public function visit_distractionware(e:Event):void{ - var distractionware_link:URLRequest = new URLRequest( "http://www.distractionware.com" ); - navigateToURL( distractionware_link, "_blank" ); - } - - public function visit_sponsor(e:Event):void{ - var sponsor_link:URLRequest = new URLRequest( "http://www.kongregate.com/?gamereferral=dontlookback" ); - navigateToURL( sponsor_link, "_blank" ); - } - - public function visit_sponsor_logo():void{ - var sponsor_link:URLRequest = new URLRequest( "http://www.kongregate.com/?gamereferral=dontlookback" ); - navigateToURL( sponsor_link, "_blank" ); - } - - /* - public function androidresize(e:Event):void { - if (immersivemode) { - device.xres = stage.stageWidth; - device.yres = stage.stageHeight; - }else{ - device.xres = flash.system.Capabilities.screenResolutionX; - device.yres = flash.system.Capabilities.screenResolutionY; + */ + //obj.entities[obj.getplayer()].size = 13; + + //game.gamestate = 6; + + //game.intimetrail = true; game.timetrialcountdown = 0; + //game.nodeathmode = true; + //dwgfx.flipmode = true; + //game.nocutscenes = true; + //map.invincibility = true; + //stage.frameRate = 24; + //game.colourblindmode = true; + //game.noflashingmode = true; + //for intermission 2 test + //game.lastsaved = 3; + /* + game.swnmode = true; + game.swngame = 2; + game.swndelay = 120; + game.swntimer = 60 * 30; + */ + //script.load("intermission_2"); + + + //for intermission 1 test + /* + game.companion = 11; + game.supercrewmate = true; + game.scmprogress = 0; + game.scmprogress = 10; + game.lastsaved = 4; + music.play(8); + */ + //game.sfpsmode = true; //Run at 60 FPS + //stage.frameRate = 60; // doesn't work, it's a bit more complex than this - will have to do a double sized window instead... + + //dwgfx.flipmode = true; + + //Testing some unlock stuff + /* + game.unlock[9] = true; //Space Station Intro Time Trial + game.unlock[10] = true; //Lab Time Trial + game.unlock[11] = true; //Tower Time Trial + game.unlock[12] = true; //Space Station 2 Time Trial + game.unlock[13] = true; //Warp Zone Time Trial + game.unlock[14] = true; //Final Level Time Trial + + game.unlock[17] = true; //No death mode + game.unlock[18] = true; //Flip Mode + + */ + //-- Normal start, with no fancy stuff! + + /* + game.gamestate = CLICKTOSTART; + dwgfx.createtextbox(" Click to Start ", 96, 107, 164, 164, 255); + dwgfx.textboxcenter(); + */ + //No click to start any more! Instead go right to the menu + + //INIT GRAPHIC MODE HERE + game.loadstats(map, dwgfx); + //updategraphicsmode(game, dwgfx); + + game.gamestate = TITLEMODE; + + game.menustart = false; + game.mainmenu = 0; + + /*if (game.quicksummary != "") { + game.mainmenu = 2; } - if (device.xres < device.yres) { - //Switch them! - t = device.yres; - device.yres = device.xres; - device.xres = t; + if (game.telesummary != "") { + game.mainmenu = 1; + }*/ //what the hell is that for + + + /* + game.gamestate = 6; + game.menustart = true; + map.ypos = (700-29) * 8; + map.bypos = map.ypos / 2; + map.cameramode = 0; + */ + //--Everything after this is screen recording + + //ok, screenrecording here + //don't record anything (comment both next parts) + + game.recording = 0; + + if(game.recording==1){ + trace("warning! recording input!"); + }else if(game.recording==2){ + trace("playing back input!"); + game.recordstring = "blahblahblah"; + help.toclipboard(game.recordstring); + + game.initplayback(); + game.playbackpos = 5; + game.savex = game.playback[0]; game.savey = game.playback[1]; + game.saverx = game.playback[2]; game.savery = game.playback[3]; + game.savegc = game.playback[4]; game.savedir = 1; } - key.definestickrange(device.xres / 2, 0, 6); + //addEventListener(Event.ENTER_FRAME, mainloop); - dwgfx.screensizemultiplier = device.yres / 240; - dwgfx.screen.width = 320 * dwgfx.screensizemultiplier; - dwgfx.screen.height = device.yres; - - dwgfx.screen.x = (device.xres / 2) - (320 * dwgfx.screensizemultiplier / 2); - dwgfx.screen.y = 0; - - dwgfx.initbuttonpositions(); - } - */ - - public function orientationChangeListener(e:StageOrientationEvent):void{ - if (e.afterOrientation == StageOrientation.DEFAULT || e.afterOrientation == StageOrientation.UPSIDE_DOWN) { - e.preventDefault(); - } - } - - public function lockedloop(e:Event):void { - dwgfx.backbuffer.lock(); - - dwgfx.bprint(5, 110, "Sorry! This game can only be", 196-help.glow, 196-help.glow, 255-help.glow, true); - dwgfx.bprint(5, 120, "played on thelettervsixtim.es", 196-help.glow, 196-help.glow, 255-help.glow, true); - dwgfx.render(); - dwgfx.backbuffer.unlock(); - - help.updateglow(); + dwgfx.starlingassets.loadQueue(function(ratio:Number):void { + trace(ratio); + if (ratio == 1){ + waitforassetstoload(); + } + }); } - public function setstage(w:int, h:int):void { - //stage.stageWidth = w; - //stage.stageHeight = h; + public function waitforassetstoload():void { + //Wait till this has loaded the texture before processing + dwgfx.button_texture.push(new RenderTexture(92, 30)); + dwgfx.button_texture.push(new RenderTexture(92, 30)); + dwgfx.button_texture.push(new RenderTexture(40, 40)); + dwgfx.button_texture.push(new RenderTexture(40, 40)); + dwgfx.addbutton("mobilebuttons/map"); + dwgfx.addbutton("mobilebuttons/back"); + dwgfx.addbutton("mobilebuttons/talk_1"); + dwgfx.addbutton("mobilebuttons/talk_2"); + dwgfx.addbutton("mobilebuttons/use_1"); + dwgfx.addbutton("mobilebuttons/use_2"); + dwgfx.addbutton("mobilebuttons/teleport_1"); + dwgfx.addbutton("mobilebuttons/teleport_2"); + dwgfx.addbutton("mobilebuttons/controls_1"); + dwgfx.addbutton("mobilebuttons/controls_2"); + dwgfx.addbutton("mobilebuttons/gamecenter"); + dwgfx.addbutton("mobilebuttons/button_left"); + dwgfx.addbutton("mobilebuttons/button_right"); + dwgfx.initbuttonstuff(); + + //We load all our graphics in: + dwgfx.maketilearray(); + dwgfx.maketile2array(); + dwgfx.maketile3array(); + dwgfx.makespritearray(); + dwgfx.makeflipspritearray(); + dwgfx.maketelearray(); + dwgfx.makeentcolourarray(); + + //Load in the images + dwgfx.addimage("levelcomplete"); // 0 + dwgfx.addimage("minimap"); // 1 (this is the minimap!) + dwgfx.addimage("covered"); // 2 + dwgfx.addimage("elephant"); // 3 + dwgfx.addimage("gamecomplete"); // 4 + dwgfx.addimage("fliplevelcomplete"); // 5 + dwgfx.addimage("flipgamecomplete"); // 6 + dwgfx.addimage("site"); // 7 + dwgfx.addimage("site2"); // 8 + dwgfx.addimage("site3"); // 9 + dwgfx.addimage("ending"); // 10 + + dwgfx.addimage("minimap"); // Minimap + dwgfx.addimage_rendertexture("minimap"); // Minimap + + dwgfx.addmobileimage("controls/touchscreen"); + dwgfx.addmobileimage("controls/lefthand_off"); + dwgfx.addmobileimage("controls/lefthand_near"); + dwgfx.addmobileimage("controls/lefthand_far"); + dwgfx.addmobileimage("controls/righthand_off"); + dwgfx.addmobileimage("controls/righthand_near"); + dwgfx.addmobileimage("controls/righthand_far"); + dwgfx.addmobileimage("controls/arrowleft"); + dwgfx.addmobileimage("controls/arrowright"); + + dwgfx.addplayerlevelimage("playerlevels/playerlevel_0"); + dwgfx.addplayerlevelimage("playerlevels/playerlevel_1"); + dwgfx.addplayerlevelimage("playerlevels/playerlevel_2"); + dwgfx.addplayerlevelimage("playerlevels/playerlevel_3"); + dwgfx.addplayerlevelimage("playerlevels/playerlevel_4"); + dwgfx.addplayerlevelimage("playerlevels/playerlevel_5"); + dwgfx.addplayerlevelimage("playerlevels/playerlevel_6"); + dwgfx.addplayerlevelimage("playerlevels/playerlevel_7"); + dwgfx.addplayerlevelimage("playerlevels/playerlevel_8"); + dwgfx.addplayerlevelimage("playerlevels/playerlevel_9"); + dwgfx.addplayerlevelimage("playerlevels/playerlevel_10"); + dwgfx.addplayerlevelimage("playerlevels/playerlevel_11"); + dwgfx.addplayerlevelimage("playerlevels/playerlevel_12"); + dwgfx.addplayerlevelimage("playerlevels/playerlevel_13"); + dwgfx.addplayerlevelimage("playerlevels/playerlevel_14"); + dwgfx.addplayerlevelimage("playerlevels/playerlevel_15"); + dwgfx.addplayerlevelimage("playerlevels/playerlevel_16"); + + var c64fontbm:BitmapFont = new BitmapFont(dwgfx.starlingassets.getTexture("c64/c64_0"), + XML(new c64font_xml)); + TextField.registerCompositor(c64fontbm, "c64"); + + _timer.addEventListener(TimerEvent.TIMER, mainloop); + _timer.start(); + + androidresize(); } public function sitelock():Boolean { - //No preloader for Kong version - var currUrl:String = stage.loaderInfo.url.toLowerCase(); - //chat.kongregate.com - if ((currUrl.indexOf("ile:///") <= 0) || (currUrl.indexOf("http") == 0)){ - //if ((currUrl.indexOf("distractionware.com/games") <= 0) && (currUrl.indexOf("thelettervsixtim.es/secretarea") <= 0)){ - return true; - //return false; - }else{ - return true; - } + //we don't care about this on mobile + return true; } public function input():void { @@ -741,9 +574,9 @@ if (game.platform.wakeupcall > 0) { game.platform.wakeupcall--; if (game.platform.wakeupcall == 0) { - if(immersivemode){ - //AndroidFullScreen.immersiveMode(); - } + //if(immersivemode){ + // AndroidFullScreen.immersiveMode(); + //} } } @@ -768,52 +601,54 @@ } } - public function render():void { - if (!game.infocus) { - dwgfx.backbuffer.lock(); - /* - dwgfx.bprint(5, 110, "Game paused", 196 - help.glow, 255 - help.glow, 196 - help.glow, true); - dwgfx.bprint(5, 120, "[click to resume]", 196 - help.glow, 255 - help.glow, 196 - help.glow, true); - dwgfx.bprint(5, 230, "Press M to mute in game", 164 - help.glow, 196 - help.glow, 164 - help.glow, true); - */ - dwgfx.render(); - dwgfx.backbuffer.unlock(); - }else { - switch(game.gamestate){ - case TITLEMODE: - titlerender(key, dwgfx, map, game, obj, help); - break; - case GAMEMODE: - if (map.towermode) { - towerrender(key, dwgfx, game, map, obj, help); - }else{ - gamerender(key, dwgfx, game, map, obj, help); - } - break; - case CONTROLTUTORIALMODE: - controltutorialrender(key, dwgfx, game, map, obj, help); - break; - case MAPMODE: - maprender(key, dwgfx, game, map, obj, help); - break; - case TELEPORTERMODE: - teleporterrender(key, dwgfx, game, map, obj, help); - break; - case GAMECOMPLETE: - gamecompleterender(key, dwgfx, game, obj, help); - break; - case GAMECOMPLETE2: - gamecompleterender2(key, dwgfx, game, obj, help); - break; - case CLICKTOSTART: - dwgfx.backbuffer.lock(); - //dwgfx.bprint(5, 115, "[Click to start]", 196 - help.glow, 196 - help.glow, 255 - help.glow, true); - dwgfx.drawgui(help); - dwgfx.render(); - dwgfx.backbuffer.unlock(); - break; + public function dorender():void { + dwgfx.backbuffer.drawBundled(function():void { + if (!game.infocus) { + //dwgfx.backbuffer.lock(); + /* + dwgfx.bprint(5, 110, "Game paused", 196 - help.glow, 255 - help.glow, 196 - help.glow, true); + dwgfx.bprint(5, 120, "[click to resume]", 196 - help.glow, 255 - help.glow, 196 - help.glow, true); + dwgfx.bprint(5, 230, "Press M to mute in game", 164 - help.glow, 196 - help.glow, 164 - help.glow, true); + */ + dwgfx.render(); + //dwgfx.backbuffer.unlock(); + }else { + switch(game.gamestate){ + case TITLEMODE: + titlerender(key, dwgfx, map, game, obj, help); + break; + case GAMEMODE: + if (map.towermode) { + towerrender(key, dwgfx, game, map, obj, help); + }else{ + gamerender(key, dwgfx, game, map, obj, help); + } + break; + case CONTROLTUTORIALMODE: + controltutorialrender(key, dwgfx, game, map, obj, help); + break; + case MAPMODE: + maprender(key, dwgfx, game, map, obj, help); + break; + case TELEPORTERMODE: + teleporterrender(key, dwgfx, game, map, obj, help); + break; + case GAMECOMPLETE: + gamecompleterender(key, dwgfx, game, map, obj, help); + break; + case GAMECOMPLETE2: + gamecompleterender2(key, dwgfx, game, obj, help); + break; + case CLICKTOSTART: + //dwgfx.backbuffer.lock(); + //dwgfx.bprint(5, 115, "[Click to start]", 196 - help.glow, 196 - help.glow, 255 - help.glow, true); + dwgfx.drawgui(help); + dwgfx.render(); + //dwgfx.backbuffer.unlock(); + break; + } } - } + }); } public function mainloop(e:TimerEvent):void { @@ -829,82 +664,45 @@ logic(); if (key.hasclicked) key.click = false; } - render(); + dorender(); e.updateAfterEvent(); } } - public function setzoom(t:int, dwgfx:dwgraphicsclass):void { - /* - switch(t) { - case 1: - dwgfx.screen.width = 320; - dwgfx.screen.height = 240; - dwgfx.screen.x = (640 - 320) / 2; - dwgfx.screen.y = (480 - 240) / 2; - break; - case 2: - dwgfx.screen.width = 640; - dwgfx.screen.height = 480; - dwgfx.screen.x = 0; - dwgfx.screen.y = 0; - break; - case 3: - dwgfx.screen.width = 960; - dwgfx.screen.height = 720; - dwgfx.screen.x = (640 - 960) / 2; - dwgfx.screen.y = (480 - 720) / 2; - break; - case 4: - dwgfx.screen.width = 1280; - dwgfx.screen.height = 960; - dwgfx.screen.x = (640 - 1280) / 2; - dwgfx.screen.y = (480 - 960) / 2; - break; - } - */ + public function androidresize():void { + /*if(immersivemode){ + AndroidFullScreen.stage = Starling.current.nativeStage; // Set this to your app's stage + AndroidFullScreen.fullScreen(); + + device.xres = AndroidFullScreen.immersiveWidth; + device.yres = AndroidFullScreen.immersiveHeight; + }else {*/ + Starling.current.nativeStage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE; + if(!device.localtesting){ + //device.xres = flash.system.Capabilities.screenResolutionX; + //device.yres = flash.system.Capabilities.screenResolutionY; + device.xres = Screen.mainScreen.safeArea.width; + device.yres = Screen.mainScreen.safeArea.height; + }else{ + device.xres = 1280; + device.yres = 800; + } + //} + + if (device.xres < device.yres) { + //Switch them! + var t:int = device.yres; + device.yres = device.xres; + device.xres = t; + } + key.definestickrange(device.xres / 2, 0, 6); + + dwgfx.updatescreen(device.xres, device.yres); + if(dwgfx.buttonsready) dwgfx.initbuttonpositions(); } public function updategraphicsmode(game:gameclass, dwgfx:dwgraphicsclass):void { - /* - swfStage = stage; - - if (game.advanced_mode) { //advanced graphics mode - //Screen Smooting - dwgfx.screen.smoothing = game.advanced_smoothing; - - //Scaling - if(game.advanced_scaling==0){ - swfStage.scaleMode = StageScaleMode.SHOW_ALL; - setzoom(2, dwgfx); - }else{ - swfStage.scaleMode = StageScaleMode.NO_SCALE; //Turn Scaling off - setzoom(game.advanced_scaling, dwgfx); - } - - stage.fullScreenSourceRect = null; - //Fullscreen - if (game.fullscreen) { - stage.displayState = StageDisplayState.FULL_SCREEN; - Mouse.hide(); - }else { - stage.displayState = StageDisplayState.NORMAL; - Mouse.show(); - } - }else{ - swfStage.scaleMode = StageScaleMode.SHOW_ALL; - if (game.fullscreen) { - dwgfx.screen.smoothing = true; - stage.fullScreenSourceRect = new Rectangle(0, 0, 640, 480); - stage.displayState = StageDisplayState.FULL_SCREEN; - Mouse.hide(); - }else { - dwgfx.screen.smoothing = false; - stage.displayState = StageDisplayState.NORMAL; - Mouse.show(); - } - } - */ + //ghost of a function that once was } // Timer information (a shout out to ChevyRay for the implementation) @@ -916,6 +714,7 @@ private var _delta:Number = 0; private var _timer:Timer = new Timer(4); + public var dwgfx:dwgraphicsclass = new dwgraphicsclass(); public var music:musicclass = new musicclass(); public var help:helpclass = new helpclass(); @@ -932,75 +731,12 @@ public var i:int, j:int, k:int, temp:int, tempx:int, tempy:int, tempstring:String; public var tr:int, tg:int, tb:int, t:int; public var tvel:Number; - public var swfStage:Stage; public var immersivemode:Boolean; - //Embedded resources: - //Graphics - [Embed(source = '../data/graphics/tiles.png')] private var im_tiles:Class; - [Embed(source = '../data/graphics/tiles2.png')] private var im_tiles2:Class; - [Embed(source = '../data/graphics/tiles3.png')] private var im_tiles3:Class; - [Embed(source = '../data/graphics/sprites.png')] private var im_sprites:Class; - [Embed(source = '../data/graphics/flipsprites.png')] private var im_flipsprites:Class; - [Embed(source = '../data/graphics/font.png')] private var im_bfont:Class; - [Embed(source = '../data/graphics/fontmask.png')] private var im_bfontmask:Class; - [Embed(source = '../data/graphics/teleporter.png')] private var im_teleporter:Class; - [Embed(source = '../data/graphics/entcolours.png')] private var im_entcolours:Class; - [Embed(source = '../data/graphics/levelcomplete.png')] private var im_image0:Class; - [Embed(source = '../data/graphics/minimap.png')] private var im_image1:Class; - [Embed(source = '../data/graphics/covered.png')] private var im_image2:Class; - [Embed(source = '../data/graphics/elephant.png')] private var im_image3:Class; - [Embed(source = '../data/graphics/gamecomplete.png')] private var im_image4:Class; - [Embed(source = '../data/graphics/fliplevelcomplete.png')] private var im_image5:Class; - [Embed(source = '../data/graphics/flipgamecomplete.png')] private var im_image6:Class; - [Embed(source = '../data/graphics/site.png')] private var im_image7:Class; - [Embed(source = '../data/graphics/site2.png')] private var im_image8:Class; - [Embed(source = '../data/graphics/site3.png')] private var im_image9:Class; - [Embed(source = '../data/graphics/ending.png')] private var im_image10:Class; + [Embed(source="../data/c64.fnt", mimeType="application/octet-stream")] + public static const c64font_xml:Class; - [Embed(source = '../data/graphics/controls/touchscreen.png')] private var im_mobileimage1:Class; - [Embed(source = '../data/graphics/controls/lefthand_off.png')] private var im_mobileimage2:Class; - [Embed(source = '../data/graphics/controls/lefthand_near.png')] private var im_mobileimage3:Class; - [Embed(source = '../data/graphics/controls/lefthand_far.png')] private var im_mobileimage4:Class; - [Embed(source = '../data/graphics/controls/righthand_off.png')] private var im_mobileimage5:Class; - [Embed(source = '../data/graphics/controls/righthand_near.png')] private var im_mobileimage6:Class; - [Embed(source = '../data/graphics/controls/righthand_far.png')] private var im_mobileimage7:Class; - [Embed(source = '../data/graphics/controls/arrowleft.png')] private var im_mobileimage8:Class; - [Embed(source = '../data/graphics/controls/arrowright.png')] private var im_mobileimage9:Class; - //Playerlevel images - [Embed(source = '../data/graphics/playerlevels/playerlevel_0.png')] private var im_imgplayerlevel0:Class; - [Embed(source = '../data/graphics/playerlevels/playerlevel_1.png')] private var im_imgplayerlevel1:Class; - [Embed(source = '../data/graphics/playerlevels/playerlevel_2.png')] private var im_imgplayerlevel2:Class; - [Embed(source = '../data/graphics/playerlevels/playerlevel_3.png')] private var im_imgplayerlevel3:Class; - [Embed(source = '../data/graphics/playerlevels/playerlevel_4.png')] private var im_imgplayerlevel4:Class; - [Embed(source = '../data/graphics/playerlevels/playerlevel_5.png')] private var im_imgplayerlevel5:Class; - [Embed(source = '../data/graphics/playerlevels/playerlevel_6.png')] private var im_imgplayerlevel6:Class; - [Embed(source = '../data/graphics/playerlevels/playerlevel_7.png')] private var im_imgplayerlevel7:Class; - [Embed(source = '../data/graphics/playerlevels/playerlevel_8.png')] private var im_imgplayerlevel8:Class; - [Embed(source = '../data/graphics/playerlevels/playerlevel_9.png')] private var im_imgplayerlevel9:Class; - [Embed(source = '../data/graphics/playerlevels/playerlevel_10.png')] private var im_imgplayerlevel10:Class; - [Embed(source = '../data/graphics/playerlevels/playerlevel_11.png')] private var im_imgplayerlevel11:Class; - [Embed(source = '../data/graphics/playerlevels/playerlevel_12.png')] private var im_imgplayerlevel12:Class; - [Embed(source = '../data/graphics/playerlevels/playerlevel_13.png')] private var im_imgplayerlevel13:Class; - [Embed(source = '../data/graphics/playerlevels/playerlevel_14.png')] private var im_imgplayerlevel14:Class; - [Embed(source = '../data/graphics/playerlevels/playerlevel_15.png')] private var im_imgplayerlevel15:Class; - [Embed(source = '../data/graphics/playerlevels/playerlevel_16.png')] private var im_imgplayerlevel16:Class; - //iOS Buttons - [Embed(source = '../data/graphics/mobilebuttons/map.png')] private var im_button_0:Class; - [Embed(source = '../data/graphics/mobilebuttons/back.png')] private var im_button_1:Class; - [Embed(source = '../data/graphics/mobilebuttons/talk_1.png')] private var im_button_2:Class; - [Embed(source = '../data/graphics/mobilebuttons/talk_2.png')] private var im_button_3:Class; - [Embed(source = '../data/graphics/mobilebuttons/use_1.png')] private var im_button_4:Class; - [Embed(source = '../data/graphics/mobilebuttons/use_2.png')] private var im_button_5:Class; - [Embed(source = '../data/graphics/mobilebuttons/teleport_1.png')] private var im_button_6:Class; - [Embed(source = '../data/graphics/mobilebuttons/teleport_2.png')] private var im_button_7:Class; - [Embed(source = '../data/graphics/mobilebuttons/controls_1.png')] private var im_button_8:Class; - [Embed(source = '../data/graphics/mobilebuttons/controls_2.png')] private var im_button_9:Class; - [Embed(source = '../data/graphics/mobilebuttons/gamecenter.png')] private var im_button_10:Class; - [Embed(source = '../data/graphics/mobilebuttons/button_left.png')] private var im_button_11:Class; - [Embed(source = '../data/graphics/mobilebuttons/button_right.png')] private var im_button_12:Class; - - //Music + //Music [Embed(source = '../data/music/levelcomplete.mp3')] private var music_1:Class; [Embed(source = '../data/music/endgame.mp3')] private var music_2:Class; //Sound effects @@ -1033,4 +769,4 @@ [Embed(source = '../data/sounds/trophy.mp3')] private var ef_26:Class; [Embed(source = '../data/sounds/rescue.mp3')] private var ef_27:Class; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/mobile_version/src/Preloader.as b/mobile_version/src/Preloader.as deleted file mode 100644 index 286678d1..00000000 --- a/mobile_version/src/Preloader.as +++ /dev/null @@ -1,310 +0,0 @@ -//Big thanks to Muku for his help with working out how to do a preloader in FlashDevelop! - -package { - import flash.display.*; - import flash.geom.*; - import flash.events.*; - import flash.net.*; - import flash.utils.getDefinitionByName; - import flash.system.fscommand; - import flash.ui.ContextMenu; - import flash.ui.ContextMenuItem; - //import com.kongregate.as3.client.KongregateAPI; - - //Mochi and Kongregate stuff is more or less ready here - - public dynamic class Preloader extends MovieClip { - public function Preloader() { - fscommand("trapallkeys", "true"); - if (stage.root.loaderInfo.url.search(/.swf$/) >= 0) { - //fscommand("showmenu", "false"); - } - stage.showDefaultContextMenu = (stage.root.loaderInfo.url.search(/.swf$/) >= 0); - - //For the offline version, manually change this - if (checksite()) { - adson = false; - }else{ - adson = true; - } - adson = false; - - //Let's assume the kong API is clever enough to do its own checking - //var kongregate:KongregateAPI = new KongregateAPI(); - //this.addChild ( kongregate ); - - //stage.showDefaultContextMenu = false; - - - //show c64 intro (30, 0), set to (-10, 100) to not show - //transition = -10; fakepercent = 100; - transition = 30; fakepercent = 0; - - - var rc_menu:ContextMenu = new ContextMenu(); - rc_menu.hideBuiltInItems(); - this.contextMenu = rc_menu; - ct = new ColorTransform(0, 0, 0, 1, 255, 255, 255, 1); //Set to white - - darkcol = 0x000000; lightcol = 0x000000; curcol = 0; - offset = 0; coltimer = 0; - - frontrect = new Rectangle(30, 20, 260, 200); - temprect = new Rectangle(0, 0, 320, 24); - - tl = new Point(0, 0); - tpoint = new Point(0, 0); - bfont_rect=new Rectangle(0,0,8,8); - var tempbmp:Bitmap; - tempbmp = new im_bfont(); buffer = tempbmp.bitmapData; - makebfont(); - - backbuffer=new BitmapData(320, 240,false,0x000000); - screenbuffer = new BitmapData(320, 240, false, 0x000000); - screen = new Bitmap(screenbuffer); - screen.width = 640;//320;//;640; - screen.height = 480;// 240;//480; - - addChild(screen); - - addEventListener(Event.ENTER_FRAME, checkFrame); - - if (!adson) { - /* - loading = new im_loading(); - loading.x = 320 - (loading.width / 2); - loading.y = 316; - addChild(loading); - */ - } - - showctp = false; - startgame = false; - } - - public function visit_distractionware(e:Event):void{ - var distractionware_link:URLRequest = new URLRequest( "http://www.distractionware.com" ); - navigateToURL( distractionware_link, "_blank" ); - } - - public function visit_sponsor(e:Event):void{ - var sponsor_link:URLRequest = new URLRequest( "http://www.kongregate.com/?gamereferral=dontlookback" ); - navigateToURL( sponsor_link, "_blank" ); - } - - public function print(x:int, y:int, t:String, r:int, g:int, b:int, cen:Boolean = false):void { - if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0; - if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; - ct.color = RGB(r, g, b); - if (cen) x = x - (len(t)); - bfontpos = 0; - for (var i:int = 0; i < t.length; i++) { - cur = t.charCodeAt(i); - tpoint.x = x + bfontpos; tpoint.y = y; - bfont[cur].colorTransform(bfont_rect, ct); - backbuffer.copyPixels(bfont[cur], bfont_rect, tpoint); - bfontpos+=bfontlen[cur]; - } - } - - public function checkFrame(e:Event):void { - var p:Number = this.loaderInfo.bytesLoaded / this.loaderInfo.bytesTotal; - - //if (stage.root.loaderInfo.url.search(/.swf$/) >= 0) p = 0; //Not for the demo! - transition = -10; fakepercent = 100; - - if (transition < 30) transition--; - if(transition>=30){ - if (int(p * 100) >= fakepercent) fakepercent++; - if (fakepercent >= 100) { - fakepercent = 100; - startgame = true; - } - - offset = (offset + 4 + int(Math.random()*5))%32; - coltimer--; - if (coltimer <= 0) { - curcol = (curcol + int(Math.random() * 5)) % 6; - coltimer = 8; - } - switch(curcol) { - case 0: - lightcol = 0xBF596F; - darkcol = 0x883E53; - break; - case 1: - lightcol = 0x6CBC5C; - darkcol = 0x508640; - break; - case 2: - lightcol = 0x5D57AA; - darkcol = 0x2F2F6C; - break; - case 3: - lightcol = 0xB7BA5E; - darkcol = 0x848342; - break; - case 4: - lightcol = 0x5790AA; - darkcol = 0x2F5B6C; - break; - case 5: - lightcol = 0x9061B1; - darkcol = 0x583D71; - break; - } - - for (var i:int = 0; i < 18; i++) { - temprect.y = (i * 16) -offset; - if (i % 2 == 0) { - backbuffer.fillRect(temprect, lightcol); - }else{ - backbuffer.fillRect(temprect, darkcol); - } - } - - backbuffer.fillRect(frontrect, 0x3E31A2); - - - tempstring = "LOADING... " + String(int(fakepercent))+"%"; - print(282, 204, tempstring, 124, 112, 218, true); - - //Render - screenbuffer.lock(); - screenbuffer.copyPixels(backbuffer, backbuffer.rect, tl, null, null, false); - screenbuffer.unlock(); - - backbuffer.lock(); - backbuffer.fillRect(backbuffer.rect, 0x000000); - backbuffer.unlock(); - if (currentFrame >= totalFrames){ - if (startgame) { - transition = 29; - } - } - }else if (transition <= -10) { - if (currentFrame >= totalFrames){ - startup(); - } - }else if (transition < 5) { - backbuffer.fillRect(backbuffer.rect, 0x000000); - //Render - screenbuffer.lock(); - screenbuffer.copyPixels(backbuffer, backbuffer.rect, tl, null, null, false); - screenbuffer.unlock(); - - backbuffer.lock(); - backbuffer.fillRect(backbuffer.rect, 0x000000); - backbuffer.unlock(); - }else if (transition < 20) { - temprect.y = 0; - temprect.height = 240; - backbuffer.fillRect(temprect, 0x000000); - backbuffer.fillRect(frontrect, 0x3E31A2); - - tempstring = "LOADING... 100%"; - print(282, 204, tempstring, 124, 112, 218, true); - //Render - screenbuffer.lock(); - screenbuffer.copyPixels(backbuffer, backbuffer.rect, tl, null, null, false); - screenbuffer.unlock(); - - backbuffer.lock(); - backbuffer.fillRect(backbuffer.rect, 0x000000); - backbuffer.unlock(); - } - } - - private function startup():void { - // hide loader - //stop(); - removeChild(screen); - removeEventListener(Event.ENTER_FRAME, checkFrame); - //loaderInfo.removeEventListener(ProgressEvent.PROGRESS, progress); - var mainClass:Class = getDefinitionByName("Main") as Class; - addChild(new mainClass() as DisplayObject); - //stage.removeChild(this); - } - - public function checksite():Boolean { - //Returns true if on a site that doesn't use mochiads - var currUrl:String = stage.loaderInfo.url.toLowerCase(); - //chat.kongregate.com - if ((currUrl.indexOf("distractionware.com") <= 0) && - (currUrl.indexOf("flashgamelicense.com") <= 0) && - (currUrl.indexOf("kongregate.com") <= 0) && - (currUrl.indexOf("chat.kongregate.com") <= 0)){ - //return true; - return false; - }else{ - return true; - } - } - - public function len(t:String):int { - bfontpos = 0; - for (var i:int = 0; i < t.length; i++) { - cur = t.charCodeAt(i); - bfontpos+=bfontlen[cur]; - } - return bfontpos; - } - - public function RGB(red:Number,green:Number,blue:Number):Number{ - return (blue | (green << 8) | (red << 16)) - } - - public function makebfont():void { - for (var j:Number = 0; j < 16; j++) { - for (var i:Number = 0; i < 16; i++) { - var t:BitmapData = new BitmapData(8, 8, true, 0x000000); - var t2emprect:Rectangle = new Rectangle(i * 8, j * 8, 8, 8); - t.copyPixels(buffer, t2emprect, tl); - bfont.push(t); - } - } - - //Ok, now we work out the lengths (this data string cortesy of a program I wrote!) - for (i = 0; i < 256; i++) bfontlen.push(6); - var maprow:Array; - var tstring:String="4,3,5,7,6,7,6,3,4,4,7,7,3,5,2,5,6,5,6,6,6,6,6,6,6,6,2,3,5,5,5,6,7,6,6,6,6,5,5,6,6,3,6,6,5,7,7,6,6,6,6,6,5,6,7,7,7,7,5,4,5,4,5,6,4,6,6,6,6,5,5,6,6,3,6,6,5,7,7,6,6,6,6,6,5,6,7,7,7,7,5,5,3,5,6,4"; - - maprow = new Array(); - maprow = tstring.split(","); - for(var k:int = 0; k < 96; k++) { - bfontlen[k + 32] = 8;// int(maprow[k]); - } - } - - public var darkcol:int, lightcol:int, curcol:int, coltimer:int; - public var offset:int; - - public var buffer:BitmapData; - public var backbuffer:BitmapData; - public var screenbuffer:BitmapData; - public var screen:Bitmap; - - public var frontrect:Rectangle; - public var temprect:Rectangle; - - public var showctp:Boolean; - public var startgame:Boolean; - public var adson:Boolean; - - [Embed(source = '../data/graphics/font.png')] private var im_bfont:Class; - public var bfontlen:Array = new Array(); - public var bfont:Array = new Array(); - public var bfont_rect:Rectangle; - public var tl:Point, tpoint:Point; - public var bfontpos:int; - public var cur:int; - public var ct:ColorTransform; - - public var tempstring:String; - public var fakepercent:int; - - public var transition:int; - - public var statcookie:SharedObject; - } -} \ No newline at end of file diff --git a/mobile_version/src/blockclass.as b/mobile_version/src/blockclass.as index 23801faf..864ca8d9 100644 --- a/mobile_version/src/blockclass.as +++ b/mobile_version/src/blockclass.as @@ -1,10 +1,7 @@ -package { - import flash.display.*; +package { import flash.geom.*; - import flash.events.*; - import flash.net.*; - public class blockclass extends Sprite { + public class blockclass { public function blockclass():void { rect = new Rectangle(); clear(); diff --git a/mobile_version/src/com/adobe/utils/AGALMiniAssembler.as b/mobile_version/src/com/adobe/utils/AGALMiniAssembler.as new file mode 100644 index 00000000..ca511f44 --- /dev/null +++ b/mobile_version/src/com/adobe/utils/AGALMiniAssembler.as @@ -0,0 +1,805 @@ +/* +Copyright (c) 2015, Adobe Systems Incorporated +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +* 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. + +* Neither the name of Adobe Systems Incorporated nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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. +*/ +package com.adobe.utils +{ + // =========================================================================== + // Imports + // --------------------------------------------------------------------------- + import flash.display3D.*; + import flash.utils.*; + + // =========================================================================== + // Class + // --------------------------------------------------------------------------- + public class AGALMiniAssembler + { // ====================================================================== + // Constants + // ---------------------------------------------------------------------- + protected static const REGEXP_OUTER_SPACES:RegExp = /^\s+|\s+$/g; + + // ====================================================================== + // Properties + // ---------------------------------------------------------------------- + // AGAL bytes and error buffer + private var _agalcode:ByteArray = null; + private var _error:String = ""; + + private var debugEnabled:Boolean = false; + + private static var initialized:Boolean = false; + public var verbose:Boolean = false; + + // ====================================================================== + // Getters + // ---------------------------------------------------------------------- + public function get error():String { return _error; } + public function get agalcode():ByteArray { return _agalcode; } + + // ====================================================================== + // Constructor + // ---------------------------------------------------------------------- + public function AGALMiniAssembler( debugging:Boolean = false ):void + { + debugEnabled = debugging; + if ( !initialized ) + init(); + } + // ====================================================================== + // Methods + // ---------------------------------------------------------------------- + + public function assemble2( ctx3d : Context3D, version:uint, vertexsrc:String, fragmentsrc:String ) : Program3D + { + var agalvertex : ByteArray = assemble ( VERTEX, vertexsrc, version ); + var agalfragment : ByteArray = assemble ( FRAGMENT, fragmentsrc, version ); + var prog : Program3D = ctx3d.createProgram(); + prog.upload(agalvertex,agalfragment); + return prog; + } + + public function assemble( mode:String, source:String, version:uint=1, ignorelimits:Boolean=false ):ByteArray + { + var start:uint = getTimer(); + + _agalcode = new ByteArray(); + _error = ""; + + var isFrag:Boolean = false; + + if ( mode == FRAGMENT ) + isFrag = true; + else if ( mode != VERTEX ) + _error = 'ERROR: mode needs to be "' + FRAGMENT + '" or "' + VERTEX + '" but is "' + mode + '".'; + + agalcode.endian = Endian.LITTLE_ENDIAN; + agalcode.writeByte( 0xa0 ); // tag version + agalcode.writeUnsignedInt( version ); // AGAL version, big endian, bit pattern will be 0x01000000 + agalcode.writeByte( 0xa1 ); // tag program id + agalcode.writeByte( isFrag ? 1 : 0 ); // vertex or fragment + + initregmap(version, ignorelimits); + + var lines:Array = source.replace( /[\f\n\r\v]+/g, "\n" ).split( "\n" ); + var nest:int = 0; + var nops:int = 0; + var i:int; + var lng:int = lines.length; + + for ( i = 0; i < lng && _error == ""; i++ ) + { + var line:String = new String( lines[i] ); + line = line.replace( REGEXP_OUTER_SPACES, "" ); + + // remove comments + var startcomment:int = line.search( "//" ); + if ( startcomment != -1 ) + line = line.slice( 0, startcomment ); + + // grab options + var optsi:int = line.search( /<.*>/g ); + var opts:Array; + if ( optsi != -1 ) + { + opts = line.slice( optsi ).match( /([\w\.\-\+]+)/gi ); + line = line.slice( 0, optsi ); + } + + // find opcode + var opCode:Array = line.match( /^\w{3}/ig ); + if ( !opCode ) + { + if ( line.length >= 3 ) + trace( "warning: bad line "+i+": "+lines[i] ); + continue; + } + var opFound:OpCode = OPMAP[ opCode[0] ]; + + // if debug is enabled, output the opcodes + if ( debugEnabled ) + trace( opFound ); + + if ( opFound == null ) + { + if ( line.length >= 3 ) + trace( "warning: bad line "+i+": "+lines[i] ); + continue; + } + + line = line.slice( line.search( opFound.name ) + opFound.name.length ); + + if ( ( opFound.flags & OP_VERSION2 ) && version<2 ) + { + _error = "error: opcode requires version 2."; + break; + } + + if ( ( opFound.flags & OP_VERT_ONLY ) && isFrag ) + { + _error = "error: opcode is only allowed in vertex programs."; + break; + } + + if ( ( opFound.flags & OP_FRAG_ONLY ) && !isFrag ) + { + _error = "error: opcode is only allowed in fragment programs."; + break; + } + if ( verbose ) + trace( "emit opcode=" + opFound ); + + agalcode.writeUnsignedInt( opFound.emitCode ); + nops++; + + if ( nops > MAX_OPCODES ) + { + _error = "error: too many opcodes. maximum is "+MAX_OPCODES+"."; + break; + } + + // get operands, use regexp + var regs:Array; + + // will match both syntax + regs = line.match( /vc\[([vof][acostdip]?)(\d*)?(\.[xyzw](\+\d{1,3})?)?\](\.[xyzw]{1,4})?|([vof][acostdip]?)(\d*)?(\.[xyzw]{1,4})?/gi ); + + if ( !regs || regs.length != opFound.numRegister ) + { + _error = "error: wrong number of operands. found "+regs.length+" but expected "+opFound.numRegister+"."; + break; + } + + var badreg:Boolean = false; + var pad:uint = 64 + 64 + 32; + var regLength:uint = regs.length; + + for ( var j:int = 0; j < regLength; j++ ) + { + var isRelative:Boolean = false; + var relreg:Array = regs[ j ].match( /\[.*\]/ig ); + if ( relreg && relreg.length > 0 ) + { + regs[ j ] = regs[ j ].replace( relreg[ 0 ], "0" ); + + if ( verbose ) + trace( "IS REL" ); + isRelative = true; + } + + var res:Array = regs[j].match( /^\b[A-Za-z]{1,2}/ig ); + if ( !res ) + { + _error = "error: could not parse operand "+j+" ("+regs[j]+")."; + badreg = true; + break; + } + var regFound:Register = REGMAP[ res[ 0 ] ]; + + // if debug is enabled, output the registers + if ( debugEnabled ) + trace( regFound ); + + if ( regFound == null ) + { + _error = "error: could not find register name for operand "+j+" ("+regs[j]+")."; + badreg = true; + break; + } + + if ( isFrag ) + { + if ( !( regFound.flags & REG_FRAG ) ) + { + _error = "error: register operand "+j+" ("+regs[j]+") only allowed in vertex programs."; + badreg = true; + break; + } + if ( isRelative ) + { + _error = "error: register operand "+j+" ("+regs[j]+") relative adressing not allowed in fragment programs."; + badreg = true; + break; + } + } + else + { + if ( !( regFound.flags & REG_VERT ) ) + { + _error = "error: register operand "+j+" ("+regs[j]+") only allowed in fragment programs."; + badreg = true; + break; + } + } + + regs[j] = regs[j].slice( regs[j].search( regFound.name ) + regFound.name.length ); + //trace( "REGNUM: " +regs[j] ); + var idxmatch:Array = isRelative ? relreg[0].match( /\d+/ ) : regs[j].match( /\d+/ ); + var regidx:uint = 0; + + if ( idxmatch ) + regidx = uint( idxmatch[0] ); + + if ( regFound.range < regidx ) + { + _error = "error: register operand "+j+" ("+regs[j]+") index exceeds limit of "+(regFound.range+1)+"."; + badreg = true; + break; + } + + var regmask:uint = 0; + var maskmatch:Array = regs[j].match( /(\.[xyzw]{1,4})/ ); + var isDest:Boolean = ( j == 0 && !( opFound.flags & OP_NO_DEST ) ); + var isSampler:Boolean = ( j == 2 && ( opFound.flags & OP_SPECIAL_TEX ) ); + var reltype:uint = 0; + var relsel:uint = 0; + var reloffset:int = 0; + + if ( isDest && isRelative ) + { + _error = "error: relative can not be destination"; + badreg = true; + break; + } + + if ( maskmatch ) + { + regmask = 0; + var cv:uint; + var maskLength:uint = maskmatch[0].length; + for ( var k:int = 1; k < maskLength; k++ ) + { + cv = maskmatch[0].charCodeAt(k) - "x".charCodeAt(0); + if ( cv > 2 ) + cv = 3; + if ( isDest ) + regmask |= 1 << cv; + else + regmask |= cv << ( ( k - 1 ) << 1 ); + } + if ( !isDest ) + for ( ; k <= 4; k++ ) + regmask |= cv << ( ( k - 1 ) << 1 ); // repeat last + } + else + { + regmask = isDest ? 0xf : 0xe4; // id swizzle or mask + } + + if ( isRelative ) + { + var relname:Array = relreg[0].match( /[A-Za-z]{1,2}/ig ); + var regFoundRel:Register = REGMAP[ relname[0]]; + if ( regFoundRel == null ) + { + _error = "error: bad index register"; + badreg = true; + break; + } + reltype = regFoundRel.emitCode; + var selmatch:Array = relreg[0].match( /(\.[xyzw]{1,1})/ ); + if ( selmatch.length==0 ) + { + _error = "error: bad index register select"; + badreg = true; + break; + } + relsel = selmatch[0].charCodeAt(1) - "x".charCodeAt(0); + if ( relsel > 2 ) + relsel = 3; + var relofs:Array = relreg[0].match( /\+\d{1,3}/ig ); + if ( relofs.length > 0 ) + reloffset = relofs[0]; + if ( reloffset < 0 || reloffset > 255 ) + { + _error = "error: index offset "+reloffset+" out of bounds. [0..255]"; + badreg = true; + break; + } + if ( verbose ) + trace( "RELATIVE: type="+reltype+"=="+relname[0]+" sel="+relsel+"=="+selmatch[0]+" idx="+regidx+" offset="+reloffset ); + } + + if ( verbose ) + trace( " emit argcode="+regFound+"["+regidx+"]["+regmask+"]" ); + if ( isDest ) + { + agalcode.writeShort( regidx ); + agalcode.writeByte( regmask ); + agalcode.writeByte( regFound.emitCode ); + pad -= 32; + } else + { + if ( isSampler ) + { + if ( verbose ) + trace( " emit sampler" ); + var samplerbits:uint = 5; // type 5 + var optsLength:uint = opts == null ? 0 : opts.length; + var bias:Number = 0; + for ( k = 0; k 50){ bvx = 9 - (Math.random() * 19); if (bvx > -6 && bvx < 6) bvx = 6; - bvx = bvx * 1.5; + bvx = bvx * 1.5; bb = new Rectangle(Math.random() * 320, Math.random() * 240, 32, 12); }else { bvy = 9 - (Math.random() * 19); if (bvy > -6 && bvy < 6) bvy = 6; - bvy = bvy * 1.5; + bvy = bvy * 1.5; bb = new Rectangle(Math.random() * 320, Math.random() * 240, 12, 32); } var bint:Number = 0.5 + ((Math.random() * 100) / 200); @@ -77,7 +144,7 @@ crewframe = 0; crewframedelay = 4; menuoffset = 0; resumegamemode = false; - //Textboxes! + //Textboxes! for (i = 0; i < 30; i++) { var t:textboxclass = new textboxclass; textbox.push(t); @@ -91,67 +158,27 @@ fadeamount = 0; fademode = 0; - bigbuffer = new BitmapData(320, 32, true, 0x000000); - bigbufferscreen = new Bitmap(bigbuffer); - bigbufferscreen.width = 640; - bigbufferscreen.height = 20; - towerbuffer = new BitmapData(320, 240, true, 0x000000); - frontbuffer = new BitmapData(320, 240, true, 0x000000); + //var devicexres:int = int(Capabilities.screenResolutionX); + //var deviceyres:int = int(Capabilities.screenResolutionY); + //updatescreen(starstage.stageWidth, starstage.stageHeight); - menubuffer = new BitmapData(320, 240, false, 0x000000); //yeah yeah I know this is lazy - - backbuffer=new BitmapData(320, 240,false,0x000000); - screenbuffer = new BitmapData(320,240,false,0x000000); - - temptile = new BitmapData(8, 8, false, 0x000000); - tempsprite = new BitmapData(32, 32, false, 0x000000); - screen = new Bitmap(screenbuffer); - - //screen.width = 640;//320;//;640; - //screen.height = 480;// 240;//480; - screen.smoothing = true; - - addChild(screen); - - buttonscreen.push(new Bitmap(buttonimg[0])); - buttonscreen.push(new Bitmap(buttonimg[1])); - buttonscreen.push(new Bitmap(buttonimg[2])); - buttonscreen.push(new Bitmap(buttonimg[3])); - - /* - if (device.deviceresolution == device.IPAD) { - buttonsize = 90; - buttonyspacing = buttonsize / 3; - buttonxspacing = 40; - buttonscreen[0].width = 92*3; buttonscreen[0].height = buttonsize; - buttonscreen[1].width = 92*3; buttonscreen[1].height = buttonsize; - buttonscreen[2].width = 40*3; buttonscreen[2].height = 40*3; - buttonscreen[3].width = 40 * 3; buttonscreen[3].height = 40 * 3; - buttonydiff = buttonscreen[2].height / 2; - }else if (device.deviceresolution == device.ANDROID) { - */ - - /* - }else { - buttonsize = 30; - buttonyspacing = buttonsize / 3; - buttonxspacing = 20; - buttonscreen[0].width = 92; buttonscreen[0].height = buttonsize; - buttonscreen[1].width = 92; buttonscreen[1].height = buttonsize; - buttonscreen[2].width = 40; buttonscreen[2].height = 40; - buttonscreen[3].width = 40; buttonscreen[3].height = 40; - buttonydiff = buttonscreen[2].height / 2; - } - */ - - /*buttonscreen.push(new Bitmap(buttonimg[1])); - buttonscreen[1].width = buttonsize; buttonscreen[1].height = buttonsize; - buttonscreen.push(new Bitmap(buttonimg[2])); - buttonscreen[2].width = buttonsize; buttonscreen[2].height = buttonsize; - buttonscreen.push(new Bitmap(buttonimg[3])); - buttonscreen[3].width = buttonsize; buttonscreen[3].height = buttonsize; - */ + //Starling.current.showStats = true; + } + + public function initbuttonstuff():void { + button_image_width.push(0); button_image_height.push(0); + button_image_width.push(0); button_image_height.push(0); + button_image_width.push(0); button_image_height.push(0); + button_image_width.push(0); button_image_height.push(0); + button_image.push(new Image(button_texture[0])); + button_image.push(new Image(button_texture[1])); + button_image.push(new Image(button_texture[2])); + button_image.push(new Image(button_texture[3])); + button_image[0].touchable = false; button_image[0].textureSmoothing = TextureSmoothing.NONE; + button_image[1].touchable = false; button_image[1].textureSmoothing = TextureSmoothing.NONE; + button_image[2].touchable = false; button_image[2].textureSmoothing = TextureSmoothing.NONE; + button_image[3].touchable = false; button_image[3].textureSmoothing = TextureSmoothing.NONE; for (i = 0; i < 10; i++) { buttonlerp.push(int(0)); @@ -172,72 +199,81 @@ initbuttonpositions(); - /* - buttonscreen[0].x = buttonxspacing; - buttonscreen[0].y = device.yres - buttonsize-buttonyspacing-buttonydiff; - buttonpos.push(new Point(buttonscreen[0].x, buttonscreen[0].y)); - - buttonscreen[1].x = buttonxspacing + buttonsize + buttonxspacing; - buttonscreen[1].y = device.yres - buttonsize-buttonyspacing; - buttonpos.push(new Point(buttonscreen[1].x, buttonscreen[1].y)); - - buttonscreen[2].x = device.xres - buttonxspacing - buttonsize; - buttonscreen[2].y = buttonsize / 4; - buttonpos.push(new Point(buttonscreen[2].x, buttonscreen[2].y)); - - buttonscreen[3].x = device.xres - buttonxspacing - buttonsize - buttonxspacing - buttonsize; - buttonscreen[3].y = device.yres - buttonsize-buttonyspacing; - buttonpos.push(new Point(buttonscreen[3].x, buttonscreen[3].y)); - */ drawonscreenbutton(0, -1); drawonscreenbutton(1, -1); drawonscreenbutton(2, -1); drawonscreenbutton(3, -1); - addChild(buttonscreen[0]); - addChild(buttonscreen[1]); - addChild(buttonscreen[2]); - addChild(buttonscreen[3]); - /* - drawonscreenbutton(0, 0); - drawonscreenbutton(1, 0); - drawonscreenbutton(2, 0); - drawonscreenbutton(3, 0); - showarrows(); - */ - } + starstage.addChild(button_image[0]); + starstage.addChild(button_image[1]); + starstage.addChild(button_image[2]); + starstage.addChild(button_image[3]); + + buttonsready = true; + } + + public function updatescreen(w:int, h:int):void { + starstage.stageWidth = w; + starstage.stageHeight = h; + + Starling.current.viewPort = new Rectangle(0, 0, w, h); + + // set rectangle dimensions for viewPort: + var stretchscalex:Number = w / screenwidth; + var stretchscaley:Number = h / screenheight; + screensizemultiplier = Math.min(stretchscalex, stretchscaley); + + //Never mess with starling's viewport for VVVVVV - instead, mess with the + //screen image + screen.width = screenwidth * screensizemultiplier; + screen.height = screenheight * screensizemultiplier; + + screen.x = (w / 2) - (screenwidth * screensizemultiplier / 2); + } public function initbuttonpositions():void { - devicex = device.xres; + devicex = device.xres; devicey = device.yres; - buttonsize = device.yres / (32 / 3); + buttonsize = devicey / (32 / 3); buttonyspacing = buttonsize / 3; - buttonxspacing = (buttonsize * 3) / 3; - buttonscreen[0].width = (buttonsize * 46) / 15; buttonscreen[0].height = buttonsize; - buttonscreen[1].width = (buttonsize * 46) / 15; buttonscreen[1].height = buttonsize; - buttonscreen[2].width = (buttonsize * 5) / 3; buttonscreen[2].height = (buttonsize * 5) / 3; - buttonscreen[3].width = (buttonsize * 5) / 3; buttonscreen[3].height = (buttonsize * 5) / 3; - buttonydiff = buttonscreen[2].height / 2; + buttonxspacing = (buttonsize * 3) / 3; - buttonscreen[0].x = device.xres + 1; - buttonscreen[0].y = 0; - buttonpos[0].setTo(buttonscreen[0].x, buttonscreen[0].y); + button_image[0].width = (buttonsize * 46) / 15; button_image[0].height = buttonsize; + button_image[1].width = (buttonsize * 46) / 15; button_image[1].height = buttonsize; + button_image[2].width = (buttonsize * 5) / 3; button_image[2].height = (buttonsize * 5) / 3; + button_image[3].width = (buttonsize * 5) / 3; button_image[3].height = (buttonsize * 5) / 3; - buttonscreen[1].x = 0; - buttonscreen[1].y = 0; - buttonpos[1].setTo(buttonscreen[1].x, buttonscreen[1].y); + button_image_width[0] = int(button_image[0].width); + button_image_width[1] = int(button_image[1].width); + button_image_width[2] = int(button_image[2].width); + button_image_width[3] = int(button_image[3].width); + button_image_height[0] = int(button_image[0].height); + button_image_height[1] = int(button_image[1].height); + button_image_height[2] = int(button_image[2].height); + button_image_height[3] = int(button_image[3].height); - buttonscreen[2].x = (buttonxspacing / 2); - buttonscreen[2].y = device.yres - buttonsize-buttonyspacing - buttonydiff; - buttonpos[2].setTo(buttonscreen[2].x, buttonscreen[2].y); - buttonscreen[3].x = (buttonxspacing/2) + buttonsize + buttonxspacing; - buttonscreen[3].y = device.yres - buttonsize-buttonyspacing; - buttonpos[3].setTo(buttonscreen[3].x, buttonscreen[3].y); + buttonydiff = button_image_height[2] / 2; + + button_image[0].x = devicex + 1; + button_image[0].y = 0; + buttonpos[0].setTo(button_image[0].x, button_image[0].y); + + button_image[1].x = 0; + button_image[1].y = 0; + buttonpos[1].setTo(button_image[1].x, button_image[1].y); + + button_image[2].x = (buttonxspacing / 2); + button_image[2].y = devicey - buttonsize-buttonyspacing - buttonydiff; + buttonpos[2].setTo(button_image[2].x, button_image[2].y); + + button_image[3].x = (buttonxspacing/2) + buttonsize + buttonxspacing ; + button_image[3].y = devicey - buttonsize-buttonyspacing; + buttonpos[3].setTo(button_image[3].x, button_image[3].y); } public function mobile_changebutton(t:int):void { - if (newbuttontype[0] != t) { + if (newbuttontype[0] != t) { if (currentbuttontype[0] != t) { newbuttontype[0] = t; buttonstate[0] = 1; @@ -246,7 +282,7 @@ } public function mobile_changeleftbutton(t:int):void { - if (newbuttontype[1] != t) { + if (newbuttontype[1] != t) { if (currentbuttontype[1] != t) { newbuttontype[1] = t; buttonstate[1] = 1; @@ -275,7 +311,7 @@ } public function drawbutton(game:gameclass, help:helpclass):void { - //Called every frame, this function controls what buttons appear and when, + //Called every frame, this function controls what buttons appear and when, //and lerps them in and out in a nice way. //buttonstate[0]: // 0 - Normal, slide in if not in @@ -319,13 +355,13 @@ changebuttonframe(3, 12); if (game.press_left) { - changebuttonpos(2, buttonxspacing/2, devicey - buttonsize-buttonyspacing - buttonydiff+10); + changebuttonpos(2, (buttonxspacing/2), devicey - buttonsize-buttonyspacing - buttonydiff+10); }else{ - changebuttonpos(2, buttonxspacing/2, devicey - buttonsize-buttonyspacing - buttonydiff); + changebuttonpos(2, (buttonxspacing/2), devicey - buttonsize-buttonyspacing - buttonydiff); } if (game.press_right) { - changebuttonpos(3, (buttonxspacing/2)+ buttonsize + buttonxspacing, devicey - buttonsize-buttonyspacing - buttonydiff+10); + changebuttonpos(3, (buttonxspacing/2) + buttonsize + buttonxspacing, devicey - buttonsize-buttonyspacing - buttonydiff+10); }else{ changebuttonpos(3, (buttonxspacing/2) + buttonsize + buttonxspacing, devicey - buttonsize-buttonyspacing - buttonydiff); } @@ -336,7 +372,7 @@ case BUTTON_BLANK: //Blank changebuttonframe(1, -1); - changebuttonpos(1, 0 - ((buttonscreen[1].width * (buttonlerp[1])) / 100), 0); + changebuttonpos(1, 0 - ((button_image_width[1] * (buttonlerp[1])) / 100), 0); break; case BUTTON_CONTROLS: //Controls button @@ -346,15 +382,15 @@ changebuttonframe(1, 8); } if (flipmode) { - changebuttonpos(1, 0 - ((buttonscreen[1].width * (buttonlerp[1])) / 100), devicey-buttonyspacing-buttonscreen[1].height); + changebuttonpos(1, 0 - ((button_image_width[1] * (buttonlerp[1])) / 100), devicey - buttonyspacing - button_image_height[1]); }else { - changebuttonpos(1, 0 - ((buttonscreen[1].width * (buttonlerp[1])) / 100), buttonyspacing); + changebuttonpos(1, 0 - ((button_image_width[1] * (buttonlerp[1])) / 100), buttonyspacing); } break; case BUTTON_GAMECENTER: changebuttonframe(1, 10); - changebuttonpos(1, 0 - ((buttonscreen[1].width * (buttonlerp[1])) / 100), 5); + changebuttonpos(1, 0 - ((button_image_width[1] * (buttonlerp[1])) / 100), 5); break; } @@ -362,20 +398,20 @@ case BUTTON_BLANK: //Blank changebuttonframe(0, -1); - changebuttonpos(0, devicex - ((buttonscreen[0].width * (100-buttonlerp[0])) / 100), 0); + changebuttonpos(0, devicex - ((button_image_width[0] * (100-buttonlerp[0])) / 100), 0); break; case BUTTON_MENU: //Menu button changebuttonframe(0, 0); - changebuttonpos(0, devicex - ((buttonscreen[0].width * (100-buttonlerp[0])) / 100), 0); + changebuttonpos(0, devicex - ((button_image_width[0] * (100-buttonlerp[0])) / 100), 0); break; case BUTTON_BACK: //Back button changebuttonframe(0, 1); if (flipmode) { - changebuttonpos(0, devicex - ((buttonscreen[0].width * (100 - buttonlerp[0])) / 100), devicey - buttonyspacing - buttonscreen[0].height); + changebuttonpos(0, devicex - ((button_image_width[0] * (100 - buttonlerp[0])) / 100), devicey - buttonyspacing - button_image_height[0]); }else{ - changebuttonpos(0, devicex - ((buttonscreen[0].width * (100 - buttonlerp[0])) / 100), buttonyspacing); + changebuttonpos(0, devicex - ((button_image_width[0] * (100 - buttonlerp[0])) / 100), buttonyspacing); } break; case BUTTON_USE: @@ -385,7 +421,7 @@ }else { changebuttonframe(0, 5); } - changebuttonpos(0, devicex - ((buttonscreen[0].width * (100-buttonlerp[0])) / 100), buttonyspacing); + changebuttonpos(0, devicex - ((button_image_width[0] * (100-buttonlerp[0])) / 100), buttonyspacing); break; case BUTTON_TALK: //Talk button @@ -394,7 +430,7 @@ }else { changebuttonframe(0, 3); } - changebuttonpos(0, devicex - ((buttonscreen[0].width * (100-buttonlerp[0])) / 100), buttonyspacing); + changebuttonpos(0, devicex - ((button_image_width[0] * (100-buttonlerp[0])) / 100), buttonyspacing); break; case BUTTON_TELEPORT: //Teleport button @@ -403,48 +439,34 @@ }else { changebuttonframe(0, 7); } - changebuttonpos(0, devicex - ((buttonscreen[0].width * (100-buttonlerp[0])) / 100), buttonyspacing); + changebuttonpos(0, devicex - ((button_image_width[0] * (100-buttonlerp[0])) / 100), buttonyspacing); break; } - - /* - if (game.press_left) { drawonscreenbutton(0, 1); - }else{ drawonscreenbutton(0, 0);} - - if (game.press_right) { drawonscreenbutton(1, 1); - }else { drawonscreenbutton(1, 0); } - - if (game.press_map) { drawonscreenbutton(2, 1); - }else { drawonscreenbutton(2, 0); } - */ - //Draw circle pad! - //if (key.controlstick != -1) { - /* - backbuffer.copyPixels(dwgfx.buttonimg[13], dwgfx.buttonimg[13].rect, new Point(key.controlstick_x - 18+18, key.controlstick_y - 18+18)); - for (i = 0; i < key.touchPoints; i++) { - if (key.touchid[i] == key.controlstick) { - dwgfx.backbuffer.copyPixels(dwgfx.buttonimg[14], dwgfx.buttonimg[14].rect, new Point(key.touchx[i] - 18, key.touchy[i] - 18)); - } - } - */ - //} } - public function addbutton():void { - var t:BitmapData = new BitmapData(buffer.width, buffer.height, true, 0x00000000); - t.copyPixels(buffer, new Rectangle(0,0,buffer.width, buffer.height), new Point(0,0)); - buttonimg.push(t); + public function addbutton(imgname:String):void { + var sourcetexture:Texture = starlingassets.getTexture(imgname); + var tmpimage:Image = new Image(sourcetexture); + + var newtexture:RenderTexture = new RenderTexture(int(tmpimage.texture.width), int(tmpimage.texture.height)); + newtexture.draw(tmpimage); + + sourcetexture.dispose(); + tmpimage.dispose(); + + button_texture.push(newtexture); } public function drawonscreenbutton(t:int, t2:int):void { //Draw button t with frame t2 if (t2 == -1) { - buttonimg[t].fillRect(buttonimg[t].rect, 0x00000000); + button_texture[t].clear(); } if (buttonframe[t] != t2) { buttonframe[t] = t2; - buttonimg[t].fillRect(buttonimg[t].rect, 0x00000000); - buttonimg[t].copyPixels(buttonimg[4 + t2], buttonimg[t].rect, tl); + button_texture[t].clear(); + var tempimg:Image = new Image(button_texture[4 + t2]); + button_texture[t].draw(tempimg); } } @@ -453,167 +475,169 @@ if (buttonframe[t] != t2) { buttonframe[t] = t2; if (t2 == -1) { - buttonimg[t].fillRect(buttonimg[t].rect, 0x00000000); + button_texture[t].clear(); }else{ - buttonimg[t].fillRect(buttonimg[t].rect, 0x00000000); - buttonimg[t].copyPixels(buttonimg[4 + t2], buttonimg[t].rect, tl); + button_texture[t].clear(); + var tempimg:Image = new Image(button_texture[4 + t2]); + button_texture[t].draw(tempimg); } } } public function changebuttonpos(t:int, xp:int, yp:int):void { - buttonscreen[t].x = xp; - buttonscreen[t].y = yp; + button_image[t].x = xp; + button_image[t].y = yp; } public function changebuttonxpos(t:int, xp:int):void { - buttonscreen[t].x = xp; + button_image[t].x = xp; } public function showarrows():void { if (buttonactive[0] == false) { buttonhighlight[0] = 120; buttonactive[0] = true; - addChild(buttonscreen[0]); - addChild(buttonscreen[1]); - addChild(buttonscreen[2]); + starstage.addChild(button_image[0]); + starstage.addChild(button_image[1]); + starstage.addChild(button_image[2]); } } public function hidearrows():void { if (buttonactive[0] == true) { buttonactive[0] = false; - removeChild(buttonscreen[0]); - removeChild(buttonscreen[1]); - removeChild(buttonscreen[2]); + starstage.removeChild(button_image[0]); + starstage.removeChild(button_image[1]); + starstage.removeChild(button_image[2]); } } - public function drawspritesetcol(x:int, y:int, t:int, c:int, help:helpclass):void { - tpoint.x = x; tpoint.y = y; - setcol(c, help); - sprites[t].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(sprites[t], sprites_rect, tpoint); - } - - - public function makebfont():void { - for (var j:Number = 0; j < 16; j++) { - for (var i:Number = 0; i < 16; i++) { - var t:BitmapData = new BitmapData(8, 8, true, 0x000000); - var t2:BitmapData = new BitmapData(8, 8, true, 0x000000); - var temprect:Rectangle = new Rectangle(i * 8, j * 8, 8, 8); - t.copyPixels(buffer, temprect, tl); - t2.draw(t, flipfontmatrix); - bfont.push(t); - flipbfont.push(t2); - } - } - - //Ok, now we work out the lengths (this data string cortesy of a program I wrote!) - for (i = 0; i < 256; i++) bfontlen.push(6); - var maprow:Array; - var tstring:String="4,3,5,7,6,7,6,3,4,4,7,7,3,5,2,5,6,5,6,6,6,6,6,6,6,6,2,3,5,5,5,6,7,6,6,6,6,5,5,6,6,3,6,6,5,7,7,6,6,6,6,6,5,6,7,7,7,7,5,4,5,4,5,6,4,6,6,6,6,5,5,6,6,3,6,6,5,7,7,6,6,6,6,6,5,6,7,7,7,7,5,5,3,5,6,4"; - - maprow = new Array(); - maprow = tstring.split(","); - for(var k:int = 0; k < 96; k++) { - bfontlen[k + 32] = 8;// int(maprow[k]); - } - } - - public function makebfontmask():void { - for (var j:Number = 0; j < 16; j++) { - for (var i:Number = 0; i < 16; i++) { - var t:BitmapData = new BitmapData(9, 9, true, 0x000000); - var t2:BitmapData = new BitmapData(9, 9, true, 0x000000); - var temprect:Rectangle = new Rectangle(i * 9, j * 9, 9, 9); - t.copyPixels(buffer, temprect, tl); - t2.draw(t, flipfontmatrix2); - bfontmask.push(t); - flipbfontmask.push(t2); - } - } + setcol(c, help); + sprites[t].color = ct.color; + tposition.identity(); + tposition.translate(x, y); + backbuffer.draw(sprites[t], tposition); } public function makeentcolourarray():void { - for (var j:Number = 0; j < 60; j++) { - for (var i:Number = 0; i < 12; i++) { - var t:BitmapData = new BitmapData(8, 8, true, 0x000000); - var temprect:Rectangle = new Rectangle(i * 8, j * 8, 8, 8); - t.copyPixels(buffer, temprect, tl); + var sourcetexture:Texture = starlingassets.getTexture("entcolours"); + for (var j:int = 0; j < 60; j++) { + for (var i:int = 0; i < 12; i++) { + var tiletex:Texture = Texture.fromTexture(sourcetexture, new Rectangle(i * 8, j * 8, 8, 8)); + var t:Image = new Image(tiletex); + t.textureSmoothing = TextureSmoothing.NONE; + t.touchable = false; entcolours.push(t); } } } public function maketilearray():void { - for (var j:Number = 0; j < 30; j++) { - for (var i:Number = 0; i < 40; i++) { - var t:BitmapData = new BitmapData(8, 8, true, 0x000000); - var temprect:Rectangle = new Rectangle(i * 8, j * 8, 8, 8); - t.copyPixels(buffer, temprect, tl); - tiles.push(t); + var sourcetexture:Texture = starlingassets.getTexture("tiles"); + for (var j:int = 0; j < 30; j++) { + for (var i:int = 0; i < 40; i++) { + var tiletex:Texture = Texture.fromTexture(sourcetexture, new Rectangle(i * 8, j * 8, 8, 8)); + var tile:Image = new Image(tiletex); + tile.textureSmoothing = TextureSmoothing.NONE; + tile.touchable = false; + tiles.push(tile); } } } public function maketile2array():void { - for (var j:Number = 0; j < 30; j++) { - for (var i:Number = 0; i < 40; i++) { - var t:BitmapData = new BitmapData(8, 8, true, 0x000000); - var temprect:Rectangle = new Rectangle(i * 8, j * 8, 8, 8); - t.copyPixels(buffer, temprect, tl); - tiles2.push(t); + var sourcetexture:Texture = starlingassets.getTexture("tiles2"); + for (var j:int = 0; j < 30; j++) { + for (var i:int = 0; i < 40; i++) { + var tiletex:Texture = Texture.fromTexture(sourcetexture, new Rectangle(i * 8, j * 8, 8, 8)); + var tile:Image = new Image(tiletex); + tile.textureSmoothing = TextureSmoothing.NONE; + tile.touchable = false; + tiles2.push(tile); } } } public function maketile3array():void { - for (var j:Number = 0; j < 30; j++) { - for (var i:Number = 0; i < 30; i++) { - var t:BitmapData = new BitmapData(8, 8, true, 0x000000); - var temprect:Rectangle = new Rectangle(i * 8, j * 8, 8, 8); - t.copyPixels(buffer, temprect, tl); - tiles3.push(t); + var sourcetexture:Texture = starlingassets.getTexture("tiles3"); + for (var j:int = 0; j < 30; j++) { + for (var i:int = 0; i < 30; i++) { + var tiletex:Texture = Texture.fromTexture(sourcetexture, new Rectangle(i * 8, j * 8, 8, 8)); + var tile:Image = new Image(tiletex); + tile.textureSmoothing = TextureSmoothing.NONE; + tile.touchable = false; + tiles3.push(tile); } } } public function makespritearray():void { - for (var j:Number = 0; j < 16; j++) { - for (var i:Number = 0; i < 12; i++) { - var t:BitmapData = new BitmapData(32, 32, true, 0x000000); - var temprect:Rectangle = new Rectangle(i * 32, j * 32, 32, 32); - t.copyPixels(buffer, temprect, tl); + var sourcetexture:Texture = starlingassets.getTexture("sprites"); + for (var j:int = 0; j < 16; j++) { + for (var i:int = 0; i < 12; i++) { + var tiletex:Texture = Texture.fromTexture(sourcetexture, new Rectangle(i * 32, j * 32, 32, 32)); + var t:Image = new Image(tiletex); + t.textureSmoothing = TextureSmoothing.NONE; + t.touchable = false; sprites.push(t); } } + + //Sprites also need to load flash bitmapdatas, which are used for pixel perfect hittests + var b:Bitmap = new img_bitmapsprites(); + var buffer:BitmapData = b.bitmapData; + + for (j = 0; j < 16; j++) { + for (i = 0; i < 12; i++) { + var tb:BitmapData = new BitmapData(32, 32, true, 0x000000); + var temprect:Rectangle = new Rectangle(i * 32, j * 32, 32, 32); + tb.copyPixels(buffer, temprect, tl); + sprites_bitmap.push(tb); + } + } } public function makeflipspritearray():void { - for (var j:Number = 0; j < 16; j++) { - for (var i:Number = 0; i < 12; i++) { - var t:BitmapData = new BitmapData(32, 32, true, 0x000000); - var temprect:Rectangle = new Rectangle(i * 32, j * 32, 32, 32); - t.copyPixels(buffer, temprect, tl); + var sourcetexture:Texture = starlingassets.getTexture("flipsprites"); + for (var j:int = 0; j < 16; j++) { + for (var i:int = 0; i < 12; i++) { + var tiletex:Texture = Texture.fromTexture(sourcetexture, new Rectangle(i * 32, j * 32, 32, 32)); + var t:Image = new Image(tiletex); + t.textureSmoothing = TextureSmoothing.NONE; + t.touchable = false; flipsprites.push(t); } } + + + //Sprites also need to load flash bitmapdatas, which are used for pixel perfect hittests + var b:Bitmap = new img_bitmapflipsprites(); + var buffer:BitmapData = b.bitmapData; + + for (j = 0; j < 16; j++) { + for (i = 0; i < 12; i++) { + var tb:BitmapData = new BitmapData(32, 32, true, 0x000000); + var temprect:Rectangle = new Rectangle(i * 32, j * 32, 32, 32); + tb.copyPixels(buffer, temprect, tl); + flipsprites_bitmap.push(tb); + } + } } public function maketelearray():void { - for (var i:Number = 0; i < 10; i++) { - var t:BitmapData = new BitmapData(96, 96, true, 0x000000); - var temprect:Rectangle = new Rectangle(i * 96, 0, 96, 96); - t.copyPixels(buffer, temprect, tl); + var sourcetexture:Texture = starlingassets.getTexture("teleporter"); + for (var i:int = 0; i < 10; i++) { + var tiletex:Texture = Texture.fromTexture(sourcetexture, new Rectangle(i * 96, j * 96, 96, 96)); + var t:Image = new Image(tiletex); + t.textureSmoothing = TextureSmoothing.NONE; + t.touchable = false; tele.push(t); } } public function drawmobilebutton(game:gameclass, xp:int, yp:int, wp:int, hp:int, t:String, cr:int, cg:int, cb:int, xoff:int = -8, yoff:int = -10):void { - xp = xp + xoff; + xp = xp + xoff; yp = yp + yoff; drawfillrect(xp + 4, yp + 4, wp, hp, cr * 0.25, cg * 0.25, cb * 0.25); drawfillrect(xp, yp, wp, hp, cr, cg, cb); @@ -827,51 +851,7 @@ } public function drawlevelmenu(game:gameclass, cr:int, cg:int, cb:int, division:int = 30):void { - for (var i:int = 0; i < game.nummenuoptions; i++){ - if (i == game.currentmenuoption){ - if(game.nummenuoptions-i<=2){ - //Draw it highlighted - if (game.menuoptionsactive[i]) { - tempstring = game.menuoptions[i]; tempstring = "[ " + tempstring.toUpperCase() + " ]"; - print(110 + (i * division) - 16 +game.menuxoff, 140 + 8 + (i * 12) +game.menuyoff, tempstring, cr, cg, cb); - }else{ - tempstring = game.menuoptions[i]; - tempstring = "[ " + tempstring + " ]"; - //Draw it in gray - print(110 + (i * division) - 16 +game.menuxoff, 140 + 8 + (i * 12) + game.menuyoff, tempstring, 128, 128, 128); - } - }else{ - //Draw it highlighted - if (game.menuoptionsactive[i]){ - tempstring = game.menuoptions[i]; tempstring = "[ " + tempstring.toUpperCase() + " ]"; - print(110 + (i * division) - 16 +game.menuxoff, 140 + (i * 12) +game.menuyoff, tempstring, cr, cg, cb); - }else{ - tempstring = game.menuoptions[i]; - tempstring = "[ " + tempstring + " ]"; - //Draw it in gray - print(110 + (i * division) - 16 +game.menuxoff, 140 + (i * 12)+game.menuyoff, tempstring, 128, 128, 128); - } - } - }else{ - if(game.nummenuoptions-i<=2){ - //Draw it normally - if (game.menuoptionsactive[i]){ - print(110 + (i * division) +game.menuxoff, 140+8 + (i * 12)+game.menuyoff, game.menuoptions[i], cr, cg, cb); - }else{ - //Draw it in gray - print(110 + (i * division) +game.menuxoff, 140+8 + (i * 12)+game.menuyoff, game.menuoptions[i], 128, 128, 128); - } - }else{ - //Draw it normally - if (game.menuoptionsactive[i]){ - print(110 + (i * division) +game.menuxoff, 140 + (i * 12)+game.menuyoff, game.menuoptions[i], cr, cg, cb); - }else{ - //Draw it in gray - print(110 + (i * division) +game.menuxoff, 140 + (i * 12)+game.menuyoff, game.menuoptions[i], 128, 128, 128); - } - } - } - } + trace("dwgfx.drawlevelmenu() is not implemented yet"); } //Fade functions @@ -907,16 +887,14 @@ public function drawfade():void { if (fademode == 1) { - backbuffer.fillRect(backbuffer.rect, 0x000000); + cls(0x000000); }else if(fademode==3){ for (i = 0; i < 15; i++) { - setmadrect(fadebars[i], i * 16, fadeamount, 16); - backbuffer.fillRect(madrect, 0x000000); + drawfillrect(fadebars[i], i * 16, fadeamount, 16, 0, 0, 0); } }else if(fademode==5){ for (i = 0; i < 15; i++) { - setmadrect(fadebars[i]-fadeamount, i * 16, 500, 16); - backbuffer.fillRect(madrect, 0x000000); + drawfillrect(fadebars[i]-fadeamount, i * 16, 500, 16, 0, 0, 0); } } } @@ -941,7 +919,7 @@ m = ntextbox; ntextbox++; } - if(m<20){ + if (m < 20) { textbox[m].clear(); textbox[m].line[0] = t; textbox[m].xp = xp; @@ -1019,8 +997,7 @@ public function drawtextbox(x:int, y:int, w:int, h:int, r:int, g:int, b:int):void { //given these parameters, draw a textbox - madrect.x = x; madrect.y = y; madrect.width = w*8; madrect.height = h*8; - backbuffer.fillRect(madrect, RGB(r / 6, g / 6, b / 6)); + drawfillrect(x, y, w * 8, h * 8, r / 6, g / 6, b / 6); drawcoloredtile(x, y, 40, r, g, b); drawcoloredtile(x + (w*8) - 8, y, 42, r, g, b); drawcoloredtile(x, y + (h*8) - 8, 45, r, g, b); @@ -1037,10 +1014,9 @@ } } - public function drawpixeltextbox(x:int, y:int, w:int, h:int, w2:int, h2:int, r:int, g:int, b:int, xo:int=0, yo:int=0):void { + public function drawpixeltextbox(x:int, y:int, w:int, h:int, w2:int, h2:int, r:int, g:int, b:int, xo:int = 0, yo:int = 0):void { //given these parameters, draw a textbox with a pixel width - madrect.x = x; madrect.y = y; madrect.width = w; madrect.height = h; - backbuffer.fillRect(madrect, RGB(r / 6, g / 6, b / 6)); + drawfillrect(x, y, w, h, r / 6, g / 6, b / 6); for (k = 0; k < w2-2; k++) { drawcoloredtile(x + 8-xo + (k * 8), y, 41, r, g, b); @@ -1058,13 +1034,9 @@ drawcoloredtile(x + (w) - 8, y + (h) - 8, 47, r, g, b); } - public function drawcustompixeltextbox(x:int, y:int, w:int, h:int, w2:int, h2:int, r:int, g:int, b:int, xo:int=0, yo:int=0):void { + public function drawcustompixeltextbox(x:int, y:int, w:int, h:int, w2:int, h2:int, r:int, g:int, b:int, xo:int = 0, yo:int = 0):void { //given these parameters, draw a textbox with a pixel width - - //madrect.x = x; madrect.y = y; madrect.w = w; madrect.h = h; - //backbuffer.fillRect(madrect, RGB(r / 6, g / 6, b / 6)); - madrect.x = x; madrect.y = y; madrect.width = w; madrect.height = h; - backbuffer.fillRect(madrect, RGB(r / 6, g / 6, b / 6)); + drawfillrect(x, y, w, h, r / 6, g / 6, b / 6); for (k = 0; k < w2-2; k++){ drawcoloredtile(x + 8-xo + (k * 8), y, 41, r, g, b); @@ -1092,34 +1064,67 @@ drawcoloredtile(x + (w) - 8, y + (h) - 8, 47, r, g, b); } - public function drawpartimage(t:int, xp:int, yp:int, wp:int, hp:int):void { - tpoint.x = xp; tpoint.y = yp; - madrect.x = 0; madrect.y = 0; madrect.width = wp; madrect.height = hp; + public var subtex:Texture; + public var subimage:Image; + public function drawpartimage(t:Texture, xp:int, yp:int, wp:int, hp:int):void { + // Acquire SubTexture and build an Image from it. + trect.x = 0; + trect.y = 0; + trect.width = wp; + trect.height = hp; - backbuffer.copyPixels(images[t], madrect, tpoint); + if (subtex == null) { + subtex = Texture.fromTexture(t, trect); + subimage = new Image(subtex); // alloc. avoidable with pooling? + subimage.touchable = false; + subimage.textureSmoothing = TextureSmoothing.NONE; + + tposition.identity(); + tposition.translate(xp, yp); + backbuffer.draw(subimage, tposition); + return; + }else { + //dispose of the old one + //No memory leaks, but this is throwing away a texture every call, which might + //get expensive? Ideally you'd reuse an image here if it hasn't changed. + //Or, maybe easier, make a function that grabs the chunk we need and keeps it somewhere + //else, hardcoding around the problem. Let's see if this is a problem in practice, though. + subtex.dispose(); + subimage.dispose(); + + subtex = Texture.fromTexture(t, trect); + subimage = new Image(subtex); + subimage.touchable = false; + subimage.textureSmoothing = TextureSmoothing.NONE; + + tposition.identity(); + tposition.translate(xp, yp); + backbuffer.draw(subimage, tposition); + return; + } } public function cutscenebars():void { if (showcutscenebars) { cutscenebarspos += 25; if (cutscenebarspos >= 360) cutscenebarspos = 360; - setmadrect(0, 0, cutscenebarspos, 16); - backbuffer.fillRect(madrect, 0x000000); - setmadrect(360-cutscenebarspos, 224, cutscenebarspos, 16); - backbuffer.fillRect(madrect, 0x000000); + drawfillrect(0, 0, cutscenebarspos, 16, 0, 0, 0); + drawfillrect(360 - cutscenebarspos, 224, cutscenebarspos, 16, 0, 0, 0); }else { //disappearing if (cutscenebarspos > 0) { cutscenebarspos -= 25; - //draw - setmadrect(0, 0, cutscenebarspos, 16); - backbuffer.fillRect(madrect, 0x000000); - setmadrect(360-cutscenebarspos, 224, cutscenebarspos, 16); - backbuffer.fillRect(madrect, 0x000000); + //draw + drawfillrect(0, 0, cutscenebarspos, 16, 0, 0, 0); + drawfillrect(360 - cutscenebarspos, 224, cutscenebarspos, 16, 0, 0, 0); } } } + //For android: this function draws textboxes to a buffer first, where they're later copied to the screen + //buffer is a 320x500 image. X coordinates aren't changed, but y coordinates are + public var textbox_ybuffer:int; + public var textbox_ybufferheight:int = 0; public function drawgui(help:helpclass):void { textboxcleanup(); //Draw all the textboxes to the screen @@ -1137,8 +1142,8 @@ print(textbox[i].xp + 8, textbox[i].yp + 8 + (j * 8), textbox[i].line[j], 196, 196, 255 - help.glow); } } - }else{ - backbuffer.fillRect(textbox[i].textrect, RGB(textbox[i].r/6, textbox[i].g/6, textbox[i].b / 6)); + }else { + drawfillrect(textbox[i].textrect.x, textbox[i].textrect.y, textbox[i].textrect.width, textbox[i].textrect.height, textbox[i].r/6, textbox[i].g/6, textbox[i].b / 6); drawcoloredtile(textbox[i].xp, textbox[i].yp, 40, textbox[i].r, textbox[i].g, textbox[i].b); drawcoloredtile(textbox[i].xp+textbox[i].w-8, textbox[i].yp, 42, textbox[i].r, textbox[i].g, textbox[i].b); @@ -1223,111 +1228,258 @@ return (blue | (green << 8) | (red << 16)) } - public function addmobileimage():void { - var t:BitmapData = new BitmapData(buffer.width, buffer.height, true, 0x000000); - setmadrect(0, 0, buffer.width, buffer.height); - t.copyPixels(buffer, madrect, tl); + public function addmobileimage(imgname:String):void { + var sourcetexture:Texture = starlingassets.getTexture(imgname); + var t:Image = new Image(sourcetexture); + t.touchable = false; + t.textureSmoothing = TextureSmoothing.NONE; mobileimages.push(t); } - public function addimage():void { - var t:BitmapData = new BitmapData(buffer.width, buffer.height, true, 0x000000); - setmadrect(0, 0, buffer.width, buffer.height); - t.copyPixels(buffer, madrect, tl); + public function addimage(imgname:String):void { + var sourcetexture:Texture = starlingassets.getTexture(imgname); + var t:Image = new Image(sourcetexture); + t.touchable = false; + t.textureSmoothing = TextureSmoothing.NONE; images.push(t); } - public function addplayerlevelimage():void { - var t:BitmapData = new BitmapData(buffer.width, buffer.height, true, 0x000000); - setmadrect(0, 0, buffer.width, buffer.height); - t.copyPixels(buffer, madrect, tl); + public function addimage_rendertexture(imgname:String):void { + customminimap = starlingassets.getTexture(imgname) as RenderTexture; + var t:Image = new Image(customminimap); + t.touchable = false; + t.textureSmoothing = TextureSmoothing.NONE; + images.push(t); + } + + public function addplayerlevelimage(imgname:String):void { + var sourcetexture:Texture = starlingassets.getTexture(imgname); + var t:Image = new Image(sourcetexture); + t.touchable = false; + t.textureSmoothing = TextureSmoothing.NONE; playerlevelimages.push(t); } - public function addbackground():void { - var t:BitmapData = new BitmapData(160, 144, true, 0x000000); - t.copyPixels(buffer, bg_rect, tl); + public function addbackground(imgname:String):void { + //Don't think this is actually used in VVVVVV + var sourcetexture:Texture = starlingassets.getTexture(imgname); + var t:Image = new Image(sourcetexture); + t.touchable = false; + t.textureSmoothing = TextureSmoothing.NONE; backgrounds.push(t); } + public var towerx_pix:int; + public var towery_pix:int; + public var tower_bgcol:Vector. = new Vector.; + public var tower_bgdarkcol:Vector. = new Vector.; + public var lasttowercolstate:int = -1; + public var lasttowercolstate_front:int = -1; + public var maptdrawfront:Boolean = true; + public var forcetowerupdate:int = 10; + public var forcefronttowerupdate:int = 10; + public var towerfront_lastypos:int = 0; + public var tower_lastypos:int = 0; + + public var forcewarpzonehorizontalupdate:int = 2; + public var forcewarpzoneverticalupdate:int = 1; + public var forcetowerstaticupdate:int = 1; + + public function forcescreenupdates():void { + if (forcetowerupdate < 3) forcetowerupdate = 3; + if (forcefronttowerupdate < 3) forcefronttowerupdate = 3; + if (forcetowerstaticupdate < 3) forcetowerstaticupdate = 3; + if (forcewarpzoneverticalupdate < 1) forcewarpzoneverticalupdate = 1; + if (forcewarpzonehorizontalupdate < 1) forcewarpzonehorizontalupdate = 1; + } + public function drawtowerbackground(map:mapclass):void { - if (map.bypos < 0) map.bypos += 120 * 8; + if (map.bypos < 0) { + map.bypos += 120 * 8; + forcetowerupdate += 5; + } - if (map.scrolldir == 1) map.tdrawback = true; - - if (map.tdrawback) { - //Draw the whole thing; needed for every colour cycle! - for (j = 0; j < 30; j++) { - for (i = 0; i < 40; i++) { - temp = map.tower.backat(i, j, map.bypos); - drawtowertile3(i * 8, (j * 8) - (map.bypos % 8), temp, map.colstate); + if (map.scrolldir == 1) { + if (map.tdrawback || lasttowercolstate != map.colstate || forcetowerupdate > 0) { + //Draw the whole thing; needed for every colour cycle! + towerbufferbackground_meshbatch.y = 0; + tower_lastypos = map.bypos; + + towerbufferbackground_meshbatch.clear(); + //Draw the whole thing; needed for every colour cycle! + for (j = 0; j < 40; j++) { + for (i = 0; i < 40; i++) { + temp = map.tower.backat(i, j, map.bypos); + drawtowertile3(i * 8, (j * 8) - (map.bypos % 8), temp, map.colstate); + } + } + + backbuffer.draw(towerbufferbackground_meshbatch); + + map.tdrawback = false; + lasttowercolstate = map.colstate; + if (forcetowerupdate > 0) forcetowerupdate--; + }else { + //just scroll down a bit + towerbufferbackground_meshbatch.y = -int(map.bypos - tower_lastypos); + backbuffer.draw(towerbufferbackground_meshbatch); + + if (towerbufferbackground_meshbatch.y < -80) { + towerbufferbackground_meshbatch.y = 0; + map.tdrawback = true; } } - - backbuffer.copyPixels(towerbuffer, towerbuffer.rect, tl, null, null, false); - - map.tdrawback = false; - }else { - //just update the bottom - towerbuffer.scroll(0, -map.bscroll); - for (i = 0; i < 40; i++) { - temp = map.tower.backat(i, 0, map.bypos); - drawtowertile3(i * 8, -(map.bypos % 8), temp, map.colstate); + }else{ + if (map.tdrawback || lasttowercolstate != map.colstate || forcetowerupdate > 0) { + //Draw the whole thing; needed for every colour cycle! + towerbufferbackground_meshbatch.y = 0; + tower_lastypos = map.bypos; + + towerbufferbackground_meshbatch.clear(); + //Draw the whole thing; needed for every colour cycle! + for (j = -10; j < 30; j++) { + for (i = 0; i < 40; i++) { + temp = map.tower.backat(i, j, map.bypos); + drawtowertile3(i * 8, (j * 8) - (map.bypos % 8), temp, map.colstate); + } + } + + backbuffer.draw(towerbufferbackground_meshbatch); + + map.tdrawback = false; + lasttowercolstate = map.colstate; + if (forcetowerupdate > 0) forcetowerupdate--; + }else { + //just scroll down a bit + towerbufferbackground_meshbatch.y = -int(map.bypos - tower_lastypos); + backbuffer.draw(towerbufferbackground_meshbatch); + + if (towerbufferbackground_meshbatch.y > 80) { + towerbufferbackground_meshbatch.y = 0; + map.tdrawback = true; + } } - - backbuffer.copyPixels(towerbuffer, towerbuffer.rect, tl, null, null, false); } } public function drawtowerbackgroundsolo(map:mapclass):void { - if (map.bypos < 0) map.bypos += 120 * 8; + if (map.bypos < 0) { + map.bypos += 120 * 8; + forcetowerupdate += 5; + } - if (map.tdrawback) { + if (map.scrolldir == 1) map.tdrawback = true; + + if (map.tdrawback || lasttowercolstate != map.colstate || forcetowerupdate > 0) { //Draw the whole thing; needed for every colour cycle! - for (j = 0; j < 31; j++) { + towerbufferbackground_meshbatch.y = 0; + tower_lastypos = map.bypos; + + towerbufferbackground_meshbatch.clear(); + //Draw the whole thing; needed for every colour cycle! + for (j = -10; j < 31; j++) { for (i = 0; i < 40; i++) { temp = map.tower.backat(i, j, map.bypos); drawtowertile3(i * 8, (j * 8) - (map.bypos % 8), temp, map.colstate); } } - backbuffer.copyPixels(towerbuffer, towerbuffer.rect, tl, null, null, false); + backbuffer.draw(towerbufferbackground_meshbatch); map.tdrawback = false; + lasttowercolstate = map.colstate; + if (forcetowerupdate > 0) forcetowerupdate--; }else { - //just update the bottom - towerbuffer.scroll(0, -map.bscroll); - for (i = 0; i < 40; i++) { - temp = map.tower.backat(i, 0, map.bypos); - drawtowertile3(i * 8, -(map.bypos % 8), temp, map.colstate); - } + //just scroll down a bit + towerbufferbackground_meshbatch.y = -int(map.bypos - tower_lastypos); + backbuffer.draw(towerbufferbackground_meshbatch); - backbuffer.copyPixels(towerbuffer, towerbuffer.rect, tl, null, null, false); + if (towerbufferbackground_meshbatch.y > 80) { + towerbufferbackground_meshbatch.y = 0; + map.tdrawback = true; + } } } - public function drawtowermap(map:mapclass):void { - for (j = 0; j < 30; j++) { - for (i = 0; i < 40; i++) { - temp = map.tower.at(i, j, map.ypos); - if (temp > 0) drawtile3(i * 8, (j * 8) - (map.ypos % 8), temp, map.colstate); + public function drawtowermap(game:gameclass, map:mapclass):void { + if (game.deathseq > 0) { forcefronttowerupdate = 20; } + + if (map.scrolldir == 1) { + if (maptdrawfront || lasttowercolstate_front != map.colstate || forcefronttowerupdate > 0) { + towerbufferforeground_meshbatch.y = 0; + towerfront_lastypos = map.ypos; + towerbufferforeground_meshbatch.clear(); + + for (j = 0; j < 40; j++) { + for (i = 0; i < 40; i++) { + temp = map.tower.at(i, j, map.ypos); + if (temp > 0) drawtile3_batch(i * 8, (j * 8) - (map.ypos % 8), temp, map.colstate); + } + } + + backbuffer.draw(towerbufferforeground_meshbatch); + + maptdrawfront = false; + lasttowercolstate_front = map.colstate; + if (forcefronttowerupdate > 0) forcefronttowerupdate--; + }else { + //just scroll down a bit + towerbufferforeground_meshbatch.y = -int(map.ypos - towerfront_lastypos); + backbuffer.draw(towerbufferforeground_meshbatch); + + if (towerbufferforeground_meshbatch.y < -80) { + towerbufferforeground_meshbatch.y = 0; + maptdrawfront = true; + } + } + }else{ + if (maptdrawfront || lasttowercolstate_front != map.colstate || forcefronttowerupdate > 0) { + towerbufferforeground_meshbatch.y = 0; + towerfront_lastypos = map.ypos; + towerbufferforeground_meshbatch.clear(); + + for (j = -10; j < 30; j++) { + for (i = 0; i < 40; i++) { + temp = map.tower.at(i, j, map.ypos); + if (temp > 0) drawtile3_batch(i * 8, (j * 8) - (map.ypos % 8), temp, map.colstate); + } + } + + backbuffer.draw(towerbufferforeground_meshbatch); + + maptdrawfront = false; + lasttowercolstate_front = map.colstate; + if (forcefronttowerupdate > 0) forcefronttowerupdate--; + }else { + //just scroll down a bit + towerbufferforeground_meshbatch.y = -int(map.ypos - towerfront_lastypos); + backbuffer.draw(towerbufferforeground_meshbatch); + + if (towerbufferforeground_meshbatch.y > 80) { + towerbufferforeground_meshbatch.y = 0; + maptdrawfront = true; + } } } } public function drawtowermap_nobackground(map:mapclass):void { + towerbufferforeground_meshbatch.clear(); + for (j = 0; j < 30; j++) { for (i = 0; i < 40; i++) { temp = map.tower.at(i, j, map.ypos); - if (temp > 0 && temp<28) drawtile3(i * 8, (j * 8) - (map.ypos % 8), temp, map.colstate); + if (temp > 0 && temp<28) drawtile3_batch(i * 8, (j * 8) - (map.ypos % 8), temp, map.colstate); } } + + backbuffer.draw(towerbufferforeground_meshbatch); } public function drawtowerspikes(map:mapclass):void { for (i = 0; i < 40; i++) { - drawtile3(i * 8, -8+map.spikeleveltop, 9, map.colstate); - drawtile3(i * 8, 230-map.spikelevelbottom, 8, map.colstate); + drawtile3(i * 8, -8 + map.spikeleveltop, 9, map.colstate); + drawtile3(i * 8, 230 - map.spikelevelbottom, 8, map.colstate); } } @@ -1345,39 +1497,53 @@ for (var i:int = 0; i < obj.nentity; i++) { if (!obj.entities[i].invis && obj.entities[i].active) { if (obj.entities[i].size == 0) { // Sprites - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp-map.ypos; + tposition.identity(); + tposition.translate(obj.entities[i].xp, obj.entities[i].yp - map.ypos); + setcol(obj.entities[i].colour, help); - sprites[obj.entities[i].drawframe].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(sprites[obj.entities[i].drawframe], sprites_rect, tpoint); + sprites[obj.entities[i].drawframe].color = ct.color; + backbuffer.draw(sprites[obj.entities[i].drawframe], tposition); //screenwrapping! if (!map.minitowermode) { if ( map.ypos >= 500 && map.ypos <= 5000) { //The "wrapping" area of the tower if (tpoint.x < 0) { - tpoint.x += 320; - backbuffer.copyPixels(sprites[obj.entities[i].drawframe], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(obj.entities[i].xp + 320, obj.entities[i].yp - map.ypos); + backbuffer.draw(sprites[obj.entities[i].drawframe], tposition); } if (tpoint.x > 300) { - tpoint.x -= 320; - backbuffer.copyPixels(sprites[obj.entities[i].drawframe], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(obj.entities[i].xp - 320, obj.entities[i].yp - map.ypos); + backbuffer.draw(sprites[obj.entities[i].drawframe], tposition); } } } - }else if (obj.entities[i].size == 1) { // Tiles - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp-map.ypos; - backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); + }else if (obj.entities[i].size == 1) { // Tiles + tposition.identity(); + tposition.translate(obj.entities[i].xp, obj.entities[i].yp - map.ypos); + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition); }else if (obj.entities[i].size == 2) { // Special: Moving platform, 4 tiles - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp-map.ypos; - backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); + tposition.identity(); + tposition.translate(obj.entities[i].xp, obj.entities[i].yp - map.ypos); + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition); + + tposition.identity(); + tposition.translate(obj.entities[i].xp + 8, obj.entities[i].yp - map.ypos); + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition); + + tposition.identity(); + tposition.translate(obj.entities[i].xp + 16, obj.entities[i].yp - map.ypos); + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition); + + tposition.identity(); + tposition.translate(obj.entities[i].xp + 24, obj.entities[i].yp - map.ypos); }else if (obj.entities[i].size == 3) { // Big chunky pixels! prect.x = obj.entities[i].xp; prect.y = obj.entities[i].yp-map.ypos; //A seperate index of colours, for simplicity - if(obj.entities[i].colour==1){ - backbuffer.fillRect(prect, RGB(196 - (Math.random() * 64), 10, 10)); + if (obj.entities[i].colour == 1) { + drawfillrect(prect.x, prect.y, prect.width, prect.height, 196 - (Math.random() * 64), 10, 10); }else if (obj.entities[i].colour == 2) { - backbuffer.fillRect(prect, RGB(160- help.glow/2 - (Math.random()*20), 200- help.glow/2, 220 - help.glow)); + drawfillrect(prect.x, prect.y, prect.width, prect.height, 160- help.glow/2 - (Math.random()*20), 200- help.glow/2, 220 - help.glow); } }else if (obj.entities[i].size == 4) { // Small pickups drawhuetile(obj.entities[i].xp, obj.entities[i].yp-map.ypos, obj.entities[i].tile, obj.entities[i].colour); @@ -1397,24 +1563,25 @@ public function drawbackground(t:int, map:mapclass):void { switch(t) { case 1: - //Starfield - backbuffer.fillRect(backbuffer.rect, 0x000000); + //Starfield + drawfillrect(0, 0, 320, 240, 0, 0, 0); for (i = 0; i < 50; i++) { if (starsspeed[i] <= 6) { - backbuffer.fillRect(stars[i], 0x222222); + drawfillrect(stars[i].x, stars[i].y, stars[i].width, stars[i].height, 34, 34, 34); }else { - backbuffer.fillRect(stars[i], 0x555555); + drawfillrect(stars[i].x, stars[i].y, stars[i].width, stars[i].height, 85, 85, 85); } stars[i].x -= starsspeed[i]; if (stars[i].x < -10) { stars[i].x += 340; stars[i].y = Math.random() * 240; - starsspeed[i] = 4+(Math.random()*4); + starsspeed[i] = 4 + (Math.random()*4); } } break; case 2: - //Lab + //Lab + i = 0; switch(rcol) { //Akward ordering to match tileset case 0: bcol2 = RGB(0, 16*backboxint[i], 16*backboxint[i]); break; //Cyan @@ -1432,7 +1599,7 @@ if (spcol >= 12) spcol = 0; } switch(spcol) { - case 0: bcol2 = RGB(0, 16*backboxint[i], 16*backboxint[i]); break; //Cyan + case 0: bcol2 = RGB(0, 16 * backboxint[i], 16 * backboxint[i]); break; //Cyan case 1: bcol2 = RGB(0, (spcoldel+1)*backboxint[i], 16*backboxint[i]); break; //Cyan case 2: bcol2 = RGB(0, 0, 16*backboxint[i]); break; //Blue case 3: bcol2 = RGB((16-spcoldel)*backboxint[i], 0, 16*backboxint[i]); break; //Blue @@ -1447,7 +1614,7 @@ } break; } - backbuffer.fillRect(backbuffer.rect, bcol2); + cls(bcol2); for (i = 0; i < 18; i++) { switch(rcol) { //Akward ordering to match tileset @@ -1475,11 +1642,11 @@ } break; } - backbuffer.fillRect(backboxes[i], bcol); + drawfillrect(backboxes[i].x, backboxes[i].y, backboxes[i].width, backboxes[i].height, bcol); backboxrect.x = backboxes[i].x + 1; backboxrect.y = backboxes[i].y + 1; backboxrect.width = backboxes[i].width - 2; backboxrect.height = backboxes[i].height - 2; - backbuffer.fillRect(backboxrect, bcol2); + drawfillrect(backboxrect.x, backboxrect.y, backboxrect.width, backboxrect.height, bcol2); backboxes[i].x += backboxvx[i]; backboxes[i].y += backboxvy[i]; @@ -1490,67 +1657,65 @@ } break; case 3: //Warp zone (horizontal) - backoffset+=3; if (backoffset >= 16) backoffset -= 16; + backoffset += 3; if (backoffset >= 16) backoffset -= 16; - if (backgrounddrawn) { - towerbuffer.scroll( -3, 0); + if (forcewarpzonehorizontalupdate > 0) { + warpzonehorizontal_meshbatch.x = 0; + warpzonehorizontal_meshbatch.clear(); for (j = 0; j < 15; j++) { - temp = 680 + (rcol * 3); - drawtowertile(317 - backoffset, (j * 16), temp+40); //20*16 = 320 - drawtowertile(317 - backoffset + 8, (j * 16), temp + 41); - drawtowertile(317 - backoffset, (j * 16) + 8, temp + 80); - drawtowertile(317 - backoffset + 8, (j * 16) + 8, temp + 81); - } - }else { - //draw the whole thing for the first time! - backoffset = 0; - towerbuffer.fillRect(towerbuffer.rect, 0x000000); - for (j = 0; j < 15; j++) { - for (i = 0; i < 21; i++) { + for (i = -1; i < 21; i++) { temp = 680 + (rcol * 3); - drawtowertile((i * 16) - backoffset, (j * 16), temp+40); - drawtowertile((i * 16) - backoffset + 8, (j * 16), temp + 41); - drawtowertile((i * 16) - backoffset, (j * 16) + 8, temp + 80); - drawtowertile((i * 16) - backoffset + 8, (j * 16) + 8, temp + 81); + tposition.identity(); tposition.translate(int((i * 16) - backoffset), int(j * 16)); + warpzonehorizontal_meshbatch.addMesh(tiles2[temp+40], tposition); + tposition.identity(); tposition.translate(int((i * 16) - backoffset + 8), int((j * 16))); + warpzonehorizontal_meshbatch.addMesh(tiles2[temp + 41], tposition); + tposition.identity(); tposition.translate(int((i * 16) - backoffset), int((j * 16) + 8)); + warpzonehorizontal_meshbatch.addMesh(tiles2[temp + 80], tposition); + tposition.identity(); tposition.translate(int((i * 16) - backoffset + 8), int((j * 16) + 8)); + warpzonehorizontal_meshbatch.addMesh(tiles2[temp + 81], tposition); } } - backgrounddrawn = true; + + backbuffer.draw(warpzonehorizontal_meshbatch); + forcewarpzonehorizontalupdate--; + }else { + warpzonehorizontal_meshbatch.x = backoffset; + backbuffer.draw(warpzonehorizontal_meshbatch); } - backbuffer.copyPixels(towerbuffer, towerbuffer.rect, tl); break; case 4: //Warp zone (vertical) - backoffset+=3; if (backoffset >= 16) backoffset -= 16; + backoffset += 3; + if (backoffset >= 16) backoffset -= 16; - if (backgrounddrawn) { - towerbuffer.scroll(0, -3); - for (i = 0; i < 21; i++) { - temp = 760 + (rcol * 3); - drawtowertile((i * 16), 237 - backoffset, temp + 40); //14*17=240 - 3 - drawtowertile((i * 16) + 8, 237 - backoffset, temp + 41); - drawtowertile((i * 16), 237 - backoffset + 8, temp + 80); - drawtowertile((i * 16) + 8, 237 - backoffset + 8, temp + 81); - } - }else { - //draw the whole thing for the first time! - backoffset = 0; - towerbuffer.fillRect(towerbuffer.rect, 0x000000); - for (j = 0; j < 15; j++) { + if (forcewarpzoneverticalupdate > 0) { + warpzonevertical_meshbatch.y = 0; + warpzonevertical_meshbatch.clear(); + for (j = -1; j < 16; j++) { for (i = 0; i < 21; i++) { temp = 760 + (rcol * 3); - drawtowertile((i * 16), (j * 16)- backoffset, temp+40); - drawtowertile((i * 16)+ 8, (j * 16)- backoffset, temp + 41); - drawtowertile((i * 16), (j * 16)- backoffset + 8, temp + 80); - drawtowertile((i * 16)+ 8, (j * 16)- backoffset + 8, temp + 81); + tposition.identity(); tposition.translate(int((i * 16)), int((j * 16) - backoffset)); + warpzonevertical_meshbatch.addMesh(tiles2[temp+40], tposition); + tposition.identity(); tposition.translate(int((i * 16) + 8), int((j * 16) - backoffset)); + warpzonevertical_meshbatch.addMesh(tiles2[temp + 41], tposition); + tposition.identity(); tposition.translate(int((i * 16)), int((j * 16) - backoffset + 8)); + warpzonevertical_meshbatch.addMesh(tiles2[temp + 80], tposition); + tposition.identity(); tposition.translate(int((i * 16) + 8), int((j * 16) - backoffset + 8)); + warpzonevertical_meshbatch.addMesh(tiles2[temp + 81], tposition); } } - backgrounddrawn = true; + + backbuffer.draw(warpzonevertical_meshbatch); + forcewarpzoneverticalupdate--; + }else { + warpzonevertical_meshbatch.y = backoffset; + backbuffer.draw(warpzonevertical_meshbatch); } - backbuffer.copyPixels(towerbuffer, towerbuffer.rect, tl); + //backbuffer.copyPixels(towerbuffer, towerbuffer.rect, tl); break; case 5: - //Warp zone, central + //Warp zone, central switch(rcol) { //Akward ordering to match tileset case 0: warpbcol = 0x0A100E; warpfcol = 0x102221; break; //Cyan @@ -1568,24 +1733,24 @@ warpskip = (warpskip + 1) % 2; } - for (i = 10 ; i >= 0; i--) { + for (i = 10; i >= 0; i--) { temp = (i << 4) + backoffset; setwarprect(160 - temp, 120 - temp, temp * 2, temp * 2); if (i % 2 == warpskip) { - backbuffer.fillRect(warprect, warpbcol); + drawfillrect(warprect.x, warprect.y, warprect.width, warprect.height, warpbcol); }else { - backbuffer.fillRect(warprect, warpfcol); + drawfillrect(warprect.x, warprect.y, warprect.width, warprect.height, warpfcol); } } break; case 6: //Final Starfield - backbuffer.fillRect(backbuffer.rect, 0x000000); + cls(0x000000); for (i = 0; i < 50; i++) { if (starsspeed[i] <= 8) { - backbuffer.fillRect(stars[i], 0x222222); + drawfillrect(stars[i].x, stars[i].y, stars[i].width, stars[i].height, 0x222222); }else { - backbuffer.fillRect(stars[i], 0x555555); + drawfillrect(stars[i].x, stars[i].y, stars[i].width, stars[i].height, 0x555555); } stars[i].y -= starsspeed[i]; if (stars[i].y < -10) { @@ -1597,71 +1762,118 @@ break; case 7: //Static, unscrolling section of the tower - for (j = 0; j < 30; j++) { for (i = 0; i < 40; i++) { drawtile3(i * 8, j * 8, map.tower.backat(i, j, 200), 15); } } + if (forcetowerstaticupdate > 0) { + towerbufferstatic_meshbatch.clear(); + + for (j = 0; j < 30; j++) { + for (i = 0; i < 40; i++) { + tposition.identity(); + tposition.translate(int(i * 8), int(j * 8)); + towerbufferstatic_meshbatch.addMesh(tiles3[map.tower.backat(i, j, 200) + (15 * 30)], tposition); + } + } + + backbuffer.draw(towerbufferstatic_meshbatch); + forcetowerstaticupdate--; + }else { + backbuffer.draw(towerbufferstatic_meshbatch); + } break; case 8: //Static, unscrolling section of the tower - for (j = 0; j < 30; j++) { for (i = 0; i < 40; i++) { drawtile3(i * 8, j * 8, map.tower.backat(i, j, 200), 10); } } + if (forcetowerstaticupdate > 0) { + towerbufferstatic_meshbatch.clear(); + + for (j = 0; j < 30; j++) { + for (i = 0; i < 40; i++) { + tposition.identity(); + tposition.translate(int(i * 8), int(j * 8)); + towerbufferstatic_meshbatch.addMesh(tiles3[map.tower.backat(i, j, 200) + (15 * 10)], tposition); + } + } + + backbuffer.draw(towerbufferstatic_meshbatch); + forcetowerstaticupdate--; + }else { + backbuffer.draw(towerbufferstatic_meshbatch); + } break; case 9: //Static, unscrolling section of the tower - for (j = 0; j < 30; j++) { for (i = 0; i < 40; i++) { drawtile3(i * 8, j * 8, map.tower.backat(i, j, 600), 0); } } + if (forcetowerstaticupdate > 0) { + towerbufferstatic_meshbatch.clear(); + + for (j = 0; j < 30; j++) { + for (i = 0; i < 40; i++) { + tposition.identity(); + tposition.translate(int(i * 8), int(j * 8)); + towerbufferstatic_meshbatch.addMesh(tiles3[map.tower.backat(i, j, 600)], tposition); + } + } + + backbuffer.draw(towerbufferstatic_meshbatch); + forcetowerstaticupdate--; + }else { + backbuffer.draw(towerbufferstatic_meshbatch); + } break; default: - backbuffer.fillRect(backbuffer.rect, 0x000000); - //backbuffer.copyPixels(backgrounds[t], bg_rect, tl); + drawfillrect(0, 0, 320, 240, 0, 0, 0); break; } } + public function textbox_drawimage(t:int, xp:int, yp:int, cent:Boolean=false):void { + trace("dwgfx.textbox_drawimage() is not implemented yet"); + } + public function drawimage(t:int, xp:int, yp:int, cent:Boolean=false):void { if (cent) { - tpoint.x = 160 - int(images[t].width / 2); tpoint.y = yp; - trect.x = 0; trect.y = 0; trect.width = images[t].width; trect.height = images[t].height; - backbuffer.copyPixels(images[t], trect, tpoint); + tposition.identity(); + tposition.translate(160 - int(images[t].width / 2), yp); + backbuffer.draw(images[t], tposition); }else { - tpoint.x = xp; tpoint.y = yp; - trect.x = 0; trect.y = 0; trect.width = images[t].width; trect.height = images[t].height; - backbuffer.copyPixels(images[t], trect, tpoint); + tposition.identity(); + tposition.translate(xp, yp); + backbuffer.draw(images[t], tposition); } } public function drawmobileimage(t:int, xp:int, yp:int, cent:Boolean=false):void { if (cent) { - tpoint.x = 160 - int(mobileimages[t].width / 2); tpoint.y = yp; - trect.x = 0; trect.y = 0; trect.width = mobileimages[t].width; trect.height = mobileimages[t].height; - backbuffer.copyPixels(mobileimages[t], trect, tpoint); + tposition.identity(); + tposition.translate(160 - int(mobileimages[t].width / 2), yp); + backbuffer.draw(mobileimages[t], tposition); }else { - tpoint.x = xp; tpoint.y = yp; - trect.x = 0; trect.y = 0; trect.width = mobileimages[t].width; trect.height = mobileimages[t].height; - backbuffer.copyPixels(mobileimages[t], trect, tpoint); + tposition.identity(); + tposition.translate(xp, yp); + backbuffer.draw(mobileimages[t], tposition); } } public function drawmobilehands(t:int, xp:int, yp:int):void { - tpoint.x = xp; tpoint.y = yp; - trect.x = 0; trect.y = 0; trect.width = mobileimages[t].width; trect.height = 70; - backbuffer.copyPixels(mobileimages[t], trect, tpoint); + tposition.identity(); + tposition.translate(xp, yp); + backbuffer.draw(mobileimages[t], tposition); } public function drawplayerlevelimage(t:int, xp:int, yp:int):void { - tpoint.x = xp; tpoint.y = yp; - trect.x = 0; trect.y = 0; trect.width = playerlevelimages[t].width; trect.height = playerlevelimages[t].height; - backbuffer.copyPixels(playerlevelimages[t], trect, tpoint); + tposition.identity(); + tposition.translate(xp, yp); + backbuffer.draw(playerlevelimages[t], tposition); } public function drawimagecol(t:int, xp:int, yp:int, r:int, g:int, b:int, cent:Boolean = false):void { - setcolreal(RGB(r,g,b)); if (cent) { - tpoint.x = 160 - int(images[t].width / 2); tpoint.y = yp; - trect.x = 0; trect.y = 0; trect.width = images[t].width; trect.height = images[t].height; - images[t].colorTransform(trect, ct); - backbuffer.copyPixels(images[t], trect, tpoint); + tposition.identity(); + tposition.translate(160 - int(images[t].width / 2), yp); + images[t].color = RGB(r, g, b); + backbuffer.draw(images[t], tposition); }else { - tpoint.x = xp; tpoint.y = yp; - trect.x = 0; trect.y = 0; trect.width = images[t].width; trect.height = images[t].height; - images[t].colorTransform(trect, ct); - backbuffer.copyPixels(images[t], trect, tpoint); + tposition.identity(); + tposition.translate(xp, yp); + images[t].color = RGB(r, g, b); + backbuffer.draw(images[t], tposition); } } @@ -1677,7 +1889,7 @@ } public function setcol(t:int, help:helpclass):void { - //Setup predefinied colours as per our zany palette + //Setup predefinied colours as per our zany palette switch(t) { //Player Normal case 0: ct.color = RGB(160- help.glow/2 - (Math.random()*20), 200- help.glow/2, 220 - help.glow); break; @@ -1824,25 +2036,30 @@ public function setcolreal(t:int):void { ct.color = t; } - + + public function textbox_drawcoloredtile(x:int, y:int, t:int, r:int, g:int, b:int):void { + trace("dwgfx.textbox_drawcoloredtile() is not implemented yet"); + } + public function drawcoloredtile(x:int, y:int, t:int, r:int, g:int, b:int):void { - tpoint.x = x; tpoint.y = y; - setcolreal(RGB(r,g,b)); - tiles[t].colorTransform(tiles_rect, ct); - backbuffer.copyPixels(tiles[t], tiles_rect, tpoint); + tposition.identity(); + tposition.translate(x, y); + + tiles[t].color = RGB(r, g, b); + backbuffer.draw(tiles[t], tposition); } public function drawhuetile(x:int, y:int, t:int, c:int):void { - tpoint.x = x; tpoint.y = y; + tposition.identity(); + tposition.translate(x, y); switch(c) { - case 0:setcolreal(RGB(250-(Math.random()*32), 250-(Math.random()*32), 10)); break; - case 1:setcolreal(RGB(250-(Math.random()*32), 250-(Math.random()*32), 10)); break; + case 0: tiles[t].color = RGB(250-(Math.random()*32), 250-(Math.random()*32), 10); break; + case 1: tiles[t].color = RGB(250-(Math.random()*32), 250-(Math.random()*32), 10); break; - default:setcolreal(RGB(250-(Math.random()*32), 250-(Math.random()*32), 10)); break; + default: tiles[t].color = RGB(250-(Math.random()*32), 250-(Math.random()*32), 10); break; } - tiles[t].colorTransform(tiles_rect, ct); - backbuffer.copyPixels(tiles[t], tiles_rect, tpoint); + backbuffer.draw(tiles[t], tposition); } public function drawcrewman(x:int, y:int, t:int, act:Boolean, help:helpclass, noshift:Boolean=false):void { @@ -1945,42 +2162,32 @@ } public function drawsprite(x:int, y:int, t:int, r:int, g:int, b:int):void { - tpoint.x = x; tpoint.y = y; - setcolreal(RGB(r,g,b)); - sprites[t].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(sprites[t], sprites_rect, tpoint); + sprites[t].color = RGB(r, g, b); + tposition.identity(); + tposition.translate(x, y); + backbuffer.draw(sprites[t], tposition); + } + + public function textbox_drawsprite(x:int, y:int, t:int, r:int, g:int, b:int):void { + trace("dwgfx.textbox_drawsprite() is not implemented yet"); } public function drawgravityline(t:int, obj:entityclass):void { if (obj.entities[t].life == 0) { - /* switch(linestate) { - case 0: backbuffer.fillRect(line_rect, RGB(0, 200, 0)); break; - case 1: backbuffer.fillRect(line_rect, RGB(16, 245, 0)); break; - case 2: backbuffer.fillRect(line_rect, RGB(0, 245, 16)); break; - case 3: backbuffer.fillRect(line_rect, RGB(16, 200, 0)); break; - case 4: backbuffer.fillRect(line_rect, RGB(24, 255, 16)); break; - case 5: backbuffer.fillRect(line_rect, RGB(16, 235, 0)); break; - case 6: backbuffer.fillRect(line_rect, RGB(0, 164, 16)); break; - case 7: backbuffer.fillRect(line_rect, RGB(16, 245, 24)); break; - case 8: backbuffer.fillRect(line_rect, RGB(0, 255, 16)); break; - case 9: backbuffer.fillRect(line_rect, RGB(96, 245, 96)); break; - } - */ - switch(linestate) { - case 0: backbuffer.fillRect(line_rect, RGB(200 - 20, 200 - 20, 200 - 20)); break; - case 1: backbuffer.fillRect(line_rect, RGB(225 - 30, 245 - 30, 245 - 30)); break; - case 2: backbuffer.fillRect(line_rect, RGB(245 - 30, 245 - 30, 225 - 30)); break; - case 3: backbuffer.fillRect(line_rect, RGB(164 - 10, 200 - 20, 200 - 20)); break; - case 4: backbuffer.fillRect(line_rect, RGB(224 - 20, 255 - 30, 196 - 20)); break; - case 5: backbuffer.fillRect(line_rect, RGB(205 - 20, 235 - 30, 196 - 20)); break; - case 6: backbuffer.fillRect(line_rect, RGB(164 - 10, 164 - 10, 164 - 10)); break; - case 7: backbuffer.fillRect(line_rect, RGB(225 - 30, 245 - 30, 205 - 20)); break; - case 8: backbuffer.fillRect(line_rect, RGB(205 - 20, 255 - 30, 225 - 30)); break; - case 9: backbuffer.fillRect(line_rect, RGB(245 - 30, 245 - 30, 245 - 30)); break; + case 0: drawfillrect(line_rect.x, line_rect.y, line_rect.width, line_rect.height, 200 - 20, 200 - 20, 200 - 20); break; + case 1: drawfillrect(line_rect.x, line_rect.y, line_rect.width, line_rect.height, 225 - 30, 245 - 30, 245 - 30); break; + case 2: drawfillrect(line_rect.x, line_rect.y, line_rect.width, line_rect.height, 245 - 30, 245 - 30, 225 - 30); break; + case 3: drawfillrect(line_rect.x, line_rect.y, line_rect.width, line_rect.height, 164 - 10, 200 - 20, 200 - 20); break; + case 4: drawfillrect(line_rect.x, line_rect.y, line_rect.width, line_rect.height, 224 - 20, 255 - 30, 196 - 20); break; + case 5: drawfillrect(line_rect.x, line_rect.y, line_rect.width, line_rect.height, 205 - 20, 235 - 30, 196 - 20); break; + case 6: drawfillrect(line_rect.x, line_rect.y, line_rect.width, line_rect.height, 164 - 10, 164 - 10, 164 - 10); break; + case 7: drawfillrect(line_rect.x, line_rect.y, line_rect.width, line_rect.height, 225 - 30, 245 - 30, 205 - 20); break; + case 8: drawfillrect(line_rect.x, line_rect.y, line_rect.width, line_rect.height, 205 - 20, 255 - 30, 225 - 30); break; + case 9: drawfillrect(line_rect.x, line_rect.y, line_rect.width, line_rect.height, 245 - 30, 245 - 30, 245 - 30); break; } }else{ - backbuffer.fillRect(line_rect, RGB(96, 96, 96)); + drawfillrect(line_rect.x, line_rect.y, line_rect.width, line_rect.height, 96, 96, 96); } } @@ -1999,184 +2206,229 @@ if (!obj.entities[i].invis && obj.entities[i].active) { if (obj.entities[i].size == 0) { // Sprites if (flipmode) { - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp; + tposition.identity(); + tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp)); setcol(obj.entities[i].colour, help); - flipsprites[obj.entities[i].drawframe].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(flipsprites[obj.entities[i].drawframe], sprites_rect, tpoint); + flipsprites[obj.entities[i].drawframe].color = ct.color; + backbuffer.draw(flipsprites[obj.entities[i].drawframe], tposition); if (map.warpx) { //screenwrapping! if (tpoint.x < 0) { - tpoint.x += 320; - backbuffer.copyPixels(flipsprites[obj.entities[i].drawframe], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp) + 320, int(obj.entities[i].yp)); + backbuffer.draw(flipsprites[obj.entities[i].drawframe], tposition); } if (tpoint.x > 300) { - tpoint.x -= 320; - backbuffer.copyPixels(flipsprites[obj.entities[i].drawframe], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp) - 320, int(obj.entities[i].yp)); + backbuffer.draw(flipsprites[obj.entities[i].drawframe], tposition); } }else if (map.warpy) { if (tpoint.y < 0) { - tpoint.y += 230; - backbuffer.copyPixels(flipsprites[obj.entities[i].drawframe], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp) + 230); + backbuffer.draw(flipsprites[obj.entities[i].drawframe], tposition); } if (tpoint.y > 210) { - tpoint.y -= 230; - backbuffer.copyPixels(flipsprites[obj.entities[i].drawframe], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp) - 230); + backbuffer.draw(flipsprites[obj.entities[i].drawframe], tposition); } } - }else{ - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp; + }else { + tposition.identity(); + tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp)); setcol(obj.entities[i].colour, help); - sprites[obj.entities[i].drawframe].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(sprites[obj.entities[i].drawframe], sprites_rect, tpoint); + sprites[obj.entities[i].drawframe].color = ct.color; + backbuffer.draw(sprites[obj.entities[i].drawframe], tposition); if (map.warpx) { //screenwrapping! if (tpoint.x < 0) { - tpoint.x += 320; - backbuffer.copyPixels(sprites[obj.entities[i].drawframe], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp) + 320, int(obj.entities[i].yp)); + backbuffer.draw(sprites[obj.entities[i].drawframe], tposition); } if (tpoint.x > 300) { - tpoint.x -= 320; - backbuffer.copyPixels(sprites[obj.entities[i].drawframe], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp) - 320, int(obj.entities[i].yp)); + backbuffer.draw(sprites[obj.entities[i].drawframe], tposition); } }else if (map.warpy) { if (tpoint.y < 0) { - tpoint.y += 230; - backbuffer.copyPixels(sprites[obj.entities[i].drawframe], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp) + 230); + backbuffer.draw(sprites[obj.entities[i].drawframe], tposition); } if (tpoint.y > 210) { - tpoint.y -= 230; - backbuffer.copyPixels(sprites[obj.entities[i].drawframe], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp) - 230); + backbuffer.draw(sprites[obj.entities[i].drawframe], tposition); } } } }else if (obj.entities[i].size == 1) { // Tiles - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp; - backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp)); + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition); }else if (obj.entities[i].size == 2) { // Special: Moving platform, 4 tiles - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp; - if(map.custommode){ - backbuffer.copyPixels(entcolours[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(entcolours[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(entcolours[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(entcolours[obj.entities[i].drawframe], tiles_rect, tpoint); - }else{ - backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); + tposition.identity(); tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp)); + if (map.custommode) { + backbuffer.draw(entcolours[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp) + 8, int(obj.entities[i].yp)); + backbuffer.draw(entcolours[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp) + 16, int(obj.entities[i].yp)); + backbuffer.draw(entcolours[obj.entities[i].drawframe], tposition) + tposition.identity(); tposition.translate(int(obj.entities[i].xp) + 24, int(obj.entities[i].yp)); + backbuffer.draw(entcolours[obj.entities[i].drawframe], tposition) + }else { + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp) + 8, int(obj.entities[i].yp)); + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp) + 16, int(obj.entities[i].yp)); + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition) + tposition.identity(); tposition.translate(int(obj.entities[i].xp) + 24, int(obj.entities[i].yp)); + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition) } }else if (obj.entities[i].size == 3) { // Big chunky pixels! - prect.x = obj.entities[i].xp; prect.y = obj.entities[i].yp; + prect.x = int(obj.entities[i].xp); prect.y = int(obj.entities[i].yp); //A seperate index of colours, for simplicity - if(obj.entities[i].colour==1){ - backbuffer.fillRect(prect, RGB(196 - (Math.random() * 64), 10, 10)); + if (obj.entities[i].colour == 1) { + drawfillrect(prect.x, prect.y, prect.width, prect.height, 196 - (Math.random() * 64), 10, 10); }else if (obj.entities[i].colour == 2) { - backbuffer.fillRect(prect, RGB(160- help.glow/2 - (Math.random()*20), 200- help.glow/2, 220 - help.glow)); + drawfillrect(prect.x, prect.y, prect.width, prect.height, 160 - help.glow / 2 - (Math.random() * 20), 200 - help.glow / 2, 220 - help.glow); } }else if (obj.entities[i].size == 4) { // Small pickups - drawhuetile(obj.entities[i].xp, obj.entities[i].yp, obj.entities[i].tile, obj.entities[i].colour); + drawhuetile(int(obj.entities[i].xp), int(obj.entities[i].yp), obj.entities[i].tile, obj.entities[i].colour); }else if (obj.entities[i].size == 5) { //Horizontal Line - line_rect.x = obj.entities[i].xp; line_rect.y = obj.entities[i].yp; + line_rect.x = int(obj.entities[i].xp); line_rect.y = int(obj.entities[i].yp); line_rect.width = obj.entities[i].w; line_rect.height = 1; drawgravityline(i, obj); }else if (obj.entities[i].size == 6) { //Vertical Line - line_rect.x = obj.entities[i].xp; line_rect.y = obj.entities[i].yp; + line_rect.x = int(obj.entities[i].xp); line_rect.y = int(obj.entities[i].yp); line_rect.width = 1; line_rect.height = obj.entities[i].h; drawgravityline(i, obj); }else if (obj.entities[i].size == 7) { //Teleporter - drawtele(obj.entities[i].xp, obj.entities[i].yp, obj.entities[i].drawframe, obj.entities[i].colour, help); + drawtele(int(obj.entities[i].xp), int(obj.entities[i].yp), obj.entities[i].drawframe, obj.entities[i].colour, help); }else if (obj.entities[i].size == 8) { // Special: Moving platform, 8 tiles - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp; + tposition.identity(); tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp)); if (map.custommode) { - backbuffer.copyPixels(entcolours[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(entcolours[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(entcolours[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(entcolours[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(entcolours[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(entcolours[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(entcolours[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(entcolours[obj.entities[i].drawframe], tiles_rect, tpoint); + backbuffer.draw(entcolours[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp + 8), int(obj.entities[i].yp)); + backbuffer.draw(entcolours[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp + 16), int(obj.entities[i].yp)); + backbuffer.draw(entcolours[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp + 24), int(obj.entities[i].yp)); + backbuffer.draw(entcolours[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp + 32), int(obj.entities[i].yp)); + backbuffer.draw(entcolours[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp + 40), int(obj.entities[i].yp)); + backbuffer.draw(entcolours[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp + 48), int(obj.entities[i].yp)); + backbuffer.draw(entcolours[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp + 56), int(obj.entities[i].yp)); + backbuffer.draw(entcolours[obj.entities[i].drawframe], tposition); }else{ - backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); - tpoint.x += 8; backbuffer.copyPixels(tiles[obj.entities[i].drawframe], tiles_rect, tpoint); + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp + 8), int(obj.entities[i].yp)); + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp + 16), int(obj.entities[i].yp)); + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp + 24), int(obj.entities[i].yp)); + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp + 32), int(obj.entities[i].yp)); + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp + 40), int(obj.entities[i].yp)); + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp + 48), int(obj.entities[i].yp)); + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition); + tposition.identity(); tposition.translate(int(obj.entities[i].xp + 56), int(obj.entities[i].yp)); + backbuffer.draw(tiles[obj.entities[i].drawframe], tposition); } }else if (obj.entities[i].size == 9) { // Really Big Sprite! (2x2) if (flipmode) { - setcol(obj.entities[i].colour, help); + setcol(obj.entities[i].colour, help); - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp; - flipsprites[obj.entities[i].drawframe].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(flipsprites[obj.entities[i].drawframe], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp)); + flipsprites[obj.entities[i].drawframe].color = ct.color; + backbuffer.draw(flipsprites[obj.entities[i].drawframe], tposition); - tpoint.x = obj.entities[i].xp+32; tpoint.y = obj.entities[i].yp; - flipsprites[obj.entities[i].drawframe+1].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(flipsprites[obj.entities[i].drawframe+1], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp) + 32, int(obj.entities[i].yp)); + flipsprites[obj.entities[i].drawframe + 1].color = ct.color; + backbuffer.draw(flipsprites[obj.entities[i].drawframe + 1], tposition); - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp+32; - flipsprites[obj.entities[i].drawframe+12].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(flipsprites[obj.entities[i].drawframe+12], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp) + 32); + flipsprites[obj.entities[i].drawframe + 12].color = ct.color; + backbuffer.draw(flipsprites[obj.entities[i].drawframe + 12], tposition); - tpoint.x = obj.entities[i].xp+32; tpoint.y = obj.entities[i].yp+32; - flipsprites[obj.entities[i].drawframe+13].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(flipsprites[obj.entities[i].drawframe + 13], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp) + 32, int(obj.entities[i].yp) + 32); + flipsprites[obj.entities[i].drawframe + 13].color = ct.color; + backbuffer.draw(flipsprites[obj.entities[i].drawframe + 13], tposition); }else{ - setcol(obj.entities[i].colour, help); + setcol(obj.entities[i].colour, help); - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp; - sprites[obj.entities[i].drawframe].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(sprites[obj.entities[i].drawframe], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp)); + sprites[obj.entities[i].drawframe].color = ct.color; + backbuffer.draw(sprites[obj.entities[i].drawframe], tposition); - tpoint.x = obj.entities[i].xp+32; tpoint.y = obj.entities[i].yp; - sprites[obj.entities[i].drawframe+1].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(sprites[obj.entities[i].drawframe+1], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp) + 32, int(obj.entities[i].yp)); + sprites[obj.entities[i].drawframe + 1].color = ct.color; + backbuffer.draw(sprites[obj.entities[i].drawframe + 1], tposition); - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp+32; - sprites[obj.entities[i].drawframe+12].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(sprites[obj.entities[i].drawframe+12], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp) + 32); + sprites[obj.entities[i].drawframe + 12].color = ct.color; + backbuffer.draw(sprites[obj.entities[i].drawframe + 12], tposition); - tpoint.x = obj.entities[i].xp+32; tpoint.y = obj.entities[i].yp+32; - sprites[obj.entities[i].drawframe+13].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(sprites[obj.entities[i].drawframe + 13], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp) + 32, int(obj.entities[i].yp) + 32); + sprites[obj.entities[i].drawframe + 13].color = ct.color; + backbuffer.draw(sprites[obj.entities[i].drawframe + 13], tposition); } }else if (obj.entities[i].size == 10) { // 2x1 Sprite if (flipmode) { - setcol(obj.entities[i].colour, help); + setcol(obj.entities[i].colour, help); - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp; - flipsprites[obj.entities[i].drawframe].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(flipsprites[obj.entities[i].drawframe], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp)); + flipsprites[obj.entities[i].drawframe].color = ct.color; + backbuffer.draw(flipsprites[obj.entities[i].drawframe], tposition); - tpoint.x = obj.entities[i].xp+32; tpoint.y = obj.entities[i].yp; - flipsprites[obj.entities[i].drawframe+1].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(flipsprites[obj.entities[i].drawframe + 1], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp) + 32, int(obj.entities[i].yp)); + flipsprites[obj.entities[i].drawframe + 1].color = ct.color; + backbuffer.draw(flipsprites[obj.entities[i].drawframe + 1], tposition); }else{ - setcol(obj.entities[i].colour, help); + setcol(obj.entities[i].colour, help); - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp; - sprites[obj.entities[i].drawframe].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(sprites[obj.entities[i].drawframe], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp)); + sprites[obj.entities[i].drawframe].color = ct.color; + backbuffer.draw(sprites[obj.entities[i].drawframe], tposition); - tpoint.x = obj.entities[i].xp+32; tpoint.y = obj.entities[i].yp; - sprites[obj.entities[i].drawframe+1].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(sprites[obj.entities[i].drawframe + 1], sprites_rect, tpoint); + tposition.identity(); + tposition.translate(int(obj.entities[i].xp) + 32, int(obj.entities[i].yp)); + sprites[obj.entities[i].drawframe + 1].color = ct.color; + backbuffer.draw(sprites[obj.entities[i].drawframe + 1], tposition); } }else if (obj.entities[i].size == 11) { //The fucking elephant - setcol(obj.entities[i].colour, help); - images[3].colorTransform(images[3].rect, ct); - drawimage(3, obj.entities[i].xp, obj.entities[i].yp); + setcol(obj.entities[i].colour, help); + images[3].color = ct.color; + drawimage(3, int(obj.entities[i].xp), int(obj.entities[i].yp)); }else if (obj.entities[i].size == 12) { // Regular sprites that don't wrap if (flipmode) { //forget this for a minute; - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp; + tpoint.x = int(obj.entities[i].xp); tpoint.y = int(obj.entities[i].yp); setcol(obj.entities[i].colour, help); - flipsprites[obj.entities[i].drawframe].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(flipsprites[obj.entities[i].drawframe], sprites_rect, tpoint); + flipsprites[obj.entities[i].drawframe].color = ct.color; + tposition.identity(); + tposition.translate(tpoint.x, tpoint.y); + backbuffer.draw(flipsprites[obj.entities[i].drawframe], tposition); //if we're outside the screen, we need to draw indicators if (obj.entities[i].xp < -20 && obj.entities[i].vx > 0) { @@ -2188,8 +2440,11 @@ tpoint.y = tpoint.y+4; setcol(23, help); - tiles[1167].colorTransform(tiles_rect, ct); - backbuffer.copyPixels(tiles[1167], tiles_rect, tpoint); + tiles[1167].color = ct.color; + tposition.identity(); + tposition.translate(tpoint.x, tpoint.y); + + backbuffer.draw(tiles[1167], tposition); }else if (obj.entities[i].xp > 340 && obj.entities[i].vx < 0) { if (obj.entities[i].xp > 420) { tpoint.x = 320 - (int(( obj.entities[i].xp-320) / 10)); @@ -2199,14 +2454,20 @@ tpoint.y = tpoint.y+4; setcol(23, help); - tiles[1166].colorTransform(tiles_rect, ct); - backbuffer.copyPixels(tiles[1166], tiles_rect, tpoint); + tiles[1166].color = ct.color; + + tposition.identity(); + tposition.translate(tpoint.x, tpoint.y); + + backbuffer.draw(tiles[1166], tposition); } }else{ - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp; + tpoint.x = int(obj.entities[i].xp); tpoint.y = int(obj.entities[i].yp); setcol(obj.entities[i].colour, help); - sprites[obj.entities[i].drawframe].colorTransform(sprites_rect, ct); - backbuffer.copyPixels(sprites[obj.entities[i].drawframe], sprites_rect, tpoint); + sprites[obj.entities[i].drawframe].color = ct.color; + tposition.identity(); + tposition.translate(tpoint.x, tpoint.y); + backbuffer.draw(sprites[obj.entities[i].drawframe], tposition); //if we're outside the screen, we need to draw indicators @@ -2219,8 +2480,11 @@ tpoint.y = tpoint.y+4; setcol(23, help); - tiles[1167].colorTransform(tiles_rect, ct); - backbuffer.copyPixels(tiles[1167], tiles_rect, tpoint); + tiles[1167].color = ct.color; + tposition.identity(); + tposition.translate(tpoint.x, tpoint.y); + + backbuffer.draw(tiles[1167], tposition); }else if (obj.entities[i].xp > 340 && obj.entities[i].vx < 0) { if (obj.entities[i].xp > 420) { tpoint.x = 320 - (int(( obj.entities[i].xp-320) / 10)); @@ -2230,37 +2494,31 @@ tpoint.y = tpoint.y+4; setcol(23, help); - tiles[1166].colorTransform(tiles_rect, ct); - backbuffer.copyPixels(tiles[1166], tiles_rect, tpoint); + tiles[1166].color = ct.color; + + tposition.identity(); + tposition.translate(tpoint.x, tpoint.y); + + backbuffer.draw(tiles[1166], tposition); } } }else if (obj.entities[i].size == 13) { // Special for epilogue: huge hero! if (flipmode) { - scaleMatrix = new Matrix(); - scaleMatrix.scale(6, 6); - bigbuffer.fillRect(bigbuffer.rect, 0x000000); + tposition.identity(); + tposition.scale(6, 6); + tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp)); - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp; setcol(obj.entities[i].colour, help); - flipsprites[obj.entities[i].drawframe].colorTransform(sprites_rect, ct); - bigbuffer.copyPixels(flipsprites[obj.entities[i].drawframe], sprites_rect, new Point(0, 0)); - - scaleMatrix.translate(obj.entities[i].xp, obj.entities[i].yp); - backbuffer.draw(bigbufferscreen, scaleMatrix); - scaleMatrix.translate(-obj.entities[i].xp, -obj.entities[i].yp); + flipsprites[obj.entities[i].drawframe].color = ct.color; + backbuffer.draw(flipsprites[obj.entities[i].drawframe], tposition); }else { - scaleMatrix = new Matrix(); - scaleMatrix.scale(6, 6); - bigbuffer.fillRect(bigbuffer.rect, 0x000000); + tposition.identity(); + tposition.scale(6, 6); + tposition.translate(int(obj.entities[i].xp), int(obj.entities[i].yp)); - tpoint.x = obj.entities[i].xp; tpoint.y = obj.entities[i].yp; setcol(obj.entities[i].colour, help); - sprites[obj.entities[i].drawframe].colorTransform(sprites_rect, ct); - bigbuffer.copyPixels(sprites[obj.entities[i].drawframe], sprites_rect, new Point(0, 0)); - - scaleMatrix.translate(obj.entities[i].xp, obj.entities[i].yp); - backbuffer.draw(bigbufferscreen, scaleMatrix); - scaleMatrix.translate(-obj.entities[i].xp, -obj.entities[i].yp); + sprites[obj.entities[i].drawframe].color = ct.color; + backbuffer.draw(sprites[obj.entities[i].drawframe], tposition); } } } @@ -2268,67 +2526,81 @@ } public function drawbuffertile(x:int, y:int, t:int):void { - tpoint.x = x; tpoint.y = y; - buffer.copyPixels(tiles[t], tiles_rect, tpoint); + trace("dwgfx.drawbuffertile() is not implemented yet"); } public function drawforetile(x:int, y:int, t:int):void { - tpoint.x = x; tpoint.y = y; - frontbuffer.copyPixels(tiles[t], tiles_rect, tpoint); + tposition.identity(); + tposition.translate(x, y); + frontbuffer_meshbatch.addMesh(tiles[t], tposition); } public function drawforetile2(x:int, y:int, t:int):void { - tpoint.x = x; tpoint.y = y; - frontbuffer.copyPixels(tiles2[t], tiles_rect, tpoint); + tposition.identity(); + tposition.translate(x, y); + frontbuffer_meshbatch.addMesh(tiles2[t], tposition); } public function drawforetile3(x:int, y:int, t:int, off:int):void { - tpoint.x = x; tpoint.y = y; - frontbuffer.copyPixels(tiles3[t+(off*30)], tiles_rect, tpoint); + tposition.identity(); + tposition.translate(x, y); + frontbuffer_meshbatch.addMesh(tiles3[t+(off*30)], tposition); } - public function drawtele(x:int, y:int, t:int, c:int, help:helpclass):void { - tpoint.x = x; tpoint.y = y; + tele[0].color = RGB(16, 16, 16); + tposition.identity(); + tposition.translate(x, y); + backbuffer.draw(tele[0], tposition); - setcolreal(RGB(16,16,16)); - tele[0].colorTransform(tele_rect, ct); - backbuffer.copyPixels(tele[0], tele_rect, tpoint); - - setcol(c, help); + setcol(c, help); if (t > 9) t = 8; if (t < 0) t = 0; - tele[t].colorTransform(tele_rect, ct); - backbuffer.copyPixels(tele[t], tele_rect, tpoint); + tele[t].color = ct.color; + + tposition.identity(); + tposition.translate(x, y); + backbuffer.draw(tele[t], tposition); } public function drawtile(x:int, y:int, t:int):void { - tpoint.x = x; tpoint.y = y; - backbuffer.copyPixels(tiles[t], tiles_rect, tpoint); + tposition.identity(); + tposition.translate(x, y); + backbuffer.draw(tiles[t], tposition); } public function drawtile2(x:int, y:int, t:int):void { - tpoint.x = x; tpoint.y = y; - backbuffer.copyPixels(tiles2[t], tiles_rect, tpoint); + tposition.identity(); + tposition.translate(x, y); + backbuffer.draw(tiles2[t], tposition); } public function drawtile3(x:int, y:int, t:int, off:int):void { - tpoint.x = x; tpoint.y = y; - backbuffer.copyPixels(tiles3[t+(off*30)], tiles_rect, tpoint); + tposition.identity(); + tposition.translate(x, y); + backbuffer.draw(tiles3[t + (off * 30)], tposition); + } + + public function drawtile3_batch(x:int, y:int, t:int, off:int):void { + tposition.identity(); + tposition.translate(x, y); + towerbufferforeground_meshbatch.addMesh(tiles3[t + (off * 30)], tposition); } public function drawtowertile3(x:int, y:int, t:int, off:int):void { - tpoint.x = x; tpoint.y = y; - towerbuffer.copyPixels(tiles3[t+(off*30)], tiles_rect, tpoint); + tposition.identity(); + tposition.translate(x, y); + towerbufferbackground_meshbatch.addMesh(tiles3[t+(off*30)], tposition); } public function drawtowertile(x:int, y:int, t:int):void { - tpoint.x = x; tpoint.y = y; - towerbuffer.copyPixels(tiles2[t], tiles_rect, tpoint, null, null, true); + tposition.identity(); + tposition.translate(x, y); + backbuffer.draw(tiles2[t], tposition); } public function drawmap(map:mapclass):void { if (!foregrounddrawn) { - frontbuffer.fillRect(frontbuffer.rect, 0x000000); + frontbuffer_meshbatch.clear(); if(map.tileset==0){ for (j = 0; j < 29+map.extrarow; j++) { for (i = 0; i < 40; i++) { @@ -2348,17 +2620,17 @@ } } } - foregrounddrawn=true; + foregrounddrawn = true; } - backbuffer.copyPixels(frontbuffer, frontbuffer.rect, tl); + backbuffer.draw(frontbuffer_meshbatch); } public function drawfinalmap(map:mapclass):void { //Update colour cycling for final level if (map.final_colormode) { map.final_aniframedelay--; - if(map.final_aniframedelay==0) foregrounddrawn=false; + if (map.final_aniframedelay == 0) foregrounddrawn = false; if (map.final_aniframedelay <= 0) { map.final_aniframedelay = 2; map.final_aniframe++; @@ -2367,9 +2639,9 @@ } if (!foregrounddrawn) { - frontbuffer.fillRect(frontbuffer.rect, 0x000000); + frontbuffer_meshbatch.clear(); if(map.tileset==0){ - for (j = 0; j < 29+map.extrarow; j++) { + for (j = 0; j < 29 + map.extrarow; j++) { for (i = 0; i < 40; i++) { if(map.contents[i + map.vmult[j]]>0) drawforetile(i * 8, j * 8, map.finalat(i,j)); } @@ -2384,54 +2656,69 @@ foregrounddrawn=true; } - backbuffer.copyPixels(frontbuffer, frontbuffer.rect, tl); + backbuffer.draw(frontbuffer_meshbatch); } public function drawmaptileat(xp:int, yp:int, mapx:int, mapy:int, map:mapclass):void { - if (mapx < 100) mapx = 119; if (mapy < 100) mapy = 119; - if (mapx > 119) mapx = 100; if (mapy > 119) mapy = 100; - if (map.explored[(mapx - 100) + ((mapy - 100) * 20)] == 1) { - madrect.x = (mapx - 100)*12; madrect.y = (mapy - 100)*9; madrect.width = 12; madrect.height = 9; - madpoint.x = xp; madpoint.y = yp; - backbuffer.copyPixels(images[1], madrect, madpoint); - }else { - drawimage(2, xp, yp); - } + //Not used anymore, phew + trace("dwgfx.drawmaptileat() is not implemented yet"); } public function drawminimap(game:gameclass, map:mapclass):void { - drawfillrect(272, 8, 40, 31, 196, 196, 196); - drawfillrect(273, 9, 38, 29, 164,164,164); - for (j = 0; j < 3; j++){ - for (i = 0; i < 3; i++) { - drawmaptileat(274 + (i * 12), 10 + (j * 9), game.roomx - 1 + i, game.roomy - 1 + j, map); - } - } - } - - public function drawrect(x:int, y:int, w:int, h:int, r:int, g:int, b:int):void { - //Draw the retangle indicated by that object - madrect.x = x; madrect.y = y; - madrect.width = w; madrect.height = 1; - backbuffer.fillRect(madrect, RGB(r,g,b)); - - madrect.width = 1; madrect.height = h; - backbuffer.fillRect(madrect, RGB(r,g,b)); - - madrect.x = x + w - 1; - madrect.width = 1; madrect.height = h; - backbuffer.fillRect(madrect, RGB(r,g,b)); - - madrect.x = x; madrect.y = y + h - 1; - madrect.width = w; madrect.height = 1; - backbuffer.fillRect(madrect, RGB(r,g,b)); + //Not used anymore, phew + trace("dwgfx.drawminimap() is not implemented yet"); } - public function drawfillrect(x:int, y:int, w:int, h:int, r:int, g:int, b:int):void { + public function drawrect(x:int, y:int, w:int, h:int, r:int, g:int, b:int):void { //Draw the retangle indicated by that object - madrect.x = x; madrect.y = y; - madrect.width = w; madrect.height = h; - backbuffer.fillRect(madrect, RGB(r,g,b)); + tquad.x = x; tquad.y = y; + tquad.width = w; tquad.height = 1; + tquad.color = RGB(r, g, b); + backbuffer.draw(tquad); + + tquad.width = 1; tquad.height = h; + backbuffer.draw(tquad); + + tquad.x = x + w - 1; + backbuffer.draw(tquad); + + tquad.x = x; tquad.y = y + h - 1; + tquad.width = w; tquad.height = 1; + backbuffer.draw(tquad); + } + + public function cls(c:int):void { + drawfillrect(0, 0, screenwidth, screenheight, c); + } + + public function drawfillrect(x:int, y:int, w:int, h:int, r:int, g:int = -1, b:int = -1):void { + //Draw the retangle indicated by that object + tquad.x = x; + tquad.y = y; + tquad.width = w; + tquad.height = h; + if (b == -1) { + tquad.color = r; + }else{ + tquad.color = RGB(r, g, b); + } + + backbuffer.draw(tquad); + } + + public function drawfillrect_onimage(img:RenderTexture, x:int, y:int, w:int, h:int, r:int, g:int = -1, b:int = -1):void { + //Draw the retangle indicated by that object + tquad.x = x; + tquad.y = y; + tquad.width = w; + tquad.height = h; + if (b == -1) { + tquad.color = r; + }else{ + tquad.color = RGB(r, g, b); + } + + img.draw(tquad); } public function printcrewname(x:int, y:int, t:int):void { @@ -2470,82 +2757,85 @@ } } - public function printmask(x:int, y:int, t:String, cen:Boolean = false):void { - if (cen) x = 160 - (len(t) / 2); - bfontpos = 0; - for (var i:int = 0; i < t.length; i++) { - cur = t.charCodeAt(i); - tpoint.x = x + bfontpos-1; tpoint.y = y; - if (flipmode) { - backbuffer.copyPixels(flipbfontmask[cur], bfont_rect, tpoint); - }else{ - backbuffer.copyPixels(bfontmask[cur], bfont_rect, tpoint); - } - bfontpos+=bfontlen[cur]; - } + public function printmask(x:int, y:int, t:String, cen:Boolean = false):void { + trace("dwgfx.printmask() is not implemented yet"); + } + + public function textbox_print(x:int, y:int, t:String, r:int, g:int, b:int, cen:Boolean = false):void { + trace("dwgfx.textbox_print() is not implemented yet"); } public function print(x:int, y:int, t:String, r:int, g:int, b:int, cen:Boolean = false):void { if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0; if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; - ct.color = RGB(r, g, b); - if (cen) x = 160 - (len(t) / 2); - bfontpos = 0; - for (var i:int = 0; i < t.length; i++) { - cur = t.charCodeAt(i); - tpoint.x = x + bfontpos; tpoint.y = y; - if (flipmode) { - flipbfont[cur].colorTransform(bfont_rect, ct); - backbuffer.copyPixels(flipbfont[cur], bfont_rect, tpoint); - }else { - bfont[cur].colorTransform(bfont_rect, ct); - backbuffer.copyPixels(bfont[cur], bfont_rect, tpoint); - } - bfontpos+=bfontlen[cur]; + if (cen) { + x = 0; + ttf.format.horizontalAlign = "center"; + }else { + ttf.format.horizontalAlign = "left"; + } + + ttf.format.color = RGB(r, g, b); + ttf.text = t; + + if (flipmode) { + tposition.identity(); + tposition.scale(1, -1); + tposition.translate(x, y + 8); + backbuffer.draw(ttf, tposition); + }else{ + tposition.identity(); + tposition.translate(x, y); + backbuffer.draw(ttf, tposition); } } public function rprint(x:int, y:int, t:String, r:int, g:int, b:int, cen:Boolean = false):void { if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0; if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; - ct.color = RGB(r, g, b); - if (cen) x = 308 - (len(t)); - bfontpos = 0; - for (var i:int = 0; i < t.length; i++) { - cur = t.charCodeAt(i); - tpoint.x = x + bfontpos; tpoint.y = y; - if (flipmode) { - flipbfont[cur].colorTransform(bfont_rect, ct); - backbuffer.copyPixels(flipbfont[cur], bfont_rect, tpoint); - }else{ - bfont[cur].colorTransform(bfont_rect, ct); - backbuffer.copyPixels(bfont[cur], bfont_rect, tpoint); - } - bfontpos+=bfontlen[cur]; + ttf.format.horizontalAlign = "right"; + x = x - 320; + + ttf.format.color = RGB(r, g, b); + ttf.text = t; + + if (flipmode) { + tposition.identity(); + tposition.scale(1, -1); + tposition.translate(x, y + 8); + backbuffer.draw(ttf, tposition); + }else { + tposition.identity(); + tposition.translate(x, y); + backbuffer.draw(ttf, tposition); } } public function printoff(x:int, y:int, t:String, r:int, g:int, b:int, cen:Boolean = false):void { - if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0; + if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0; if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; - ct.color = RGB(r, g, b); - if (cen) x = (160 - (len(t) / 2))+x; - bfontpos = 0; - for (var i:int = 0; i < t.length; i++) { - cur = t.charCodeAt(i); - tpoint.x = x + bfontpos; tpoint.y = y; - if (flipmode) { - flipbfont[cur].colorTransform(bfont_rect, ct); - backbuffer.copyPixels(flipbfont[cur], bfont_rect, tpoint); - }else{ - bfont[cur].colorTransform(bfont_rect, ct); - backbuffer.copyPixels(bfont[cur], bfont_rect, tpoint); - } - bfontpos+=bfontlen[cur]; + if (cen) { + ttf.format.horizontalAlign = "center"; + }else { + ttf.format.horizontalAlign = "left"; + } + + ttf.format.color = RGB(r, g, b); + ttf.text = t; + + if (flipmode) { + tposition.identity(); + tposition.scale(1, -1); + tposition.translate(x, y + 8); + backbuffer.draw(ttf, tposition); + }else { + tposition.identity(); + tposition.translate(x, y); + backbuffer.draw(ttf, tposition); } } - public function bprint(x:int, y:int, t:String, r:int, g:int, b:int, cen:Boolean=false):void { + public function bprint(x:int, y:int, t:String, r:int, g:int, b:int, cen:Boolean = false):void { //printmask(x, y, t, cen); print(x, y - 1, t, 0, 0, 0, cen); if (cen) { @@ -2561,38 +2851,11 @@ } public function printmasktemptile(x:int, y:int, t:String, cen:Boolean = false):void { - if (cen) x = 160- (len(t) / 2); - bfontpos = 0; - for (var i:int = 0; i < t.length; i++) { - cur = t.charCodeAt(i); - tpoint.x = x + bfontpos - 1; tpoint.y = y; - if (flipmode) { - temptile.copyPixels(flipbfontmask[cur], bfont_rect, tpoint); - }else{ - temptile.copyPixels(bfontmask[cur], bfont_rect, tpoint); - } - bfontpos+=bfontlen[cur]; - } + trace("dwgfx.printmasktemptile() is not implemented yet"); } public function printtemptile(x:int, y:int, t:String, r:int, g:int, b:int, cen:Boolean = false):void { - if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0; - if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; - ct.color = RGB(r, g, b); - if (cen) x = 160 - (len(t) / 2); - bfontpos = 0; - for (var i:int = 0; i < t.length; i++) { - cur = t.charCodeAt(i); - tpoint.x = x + bfontpos; tpoint.y = y; - if (flipmode) { - flipbfont[cur].colorTransform(bfont_rect, ct); - temptile.copyPixels(flipbfont[cur], bfont_rect, tpoint); - }else{ - bfont[cur].colorTransform(bfont_rect, ct); - temptile.copyPixels(bfont[cur], bfont_rect, tpoint); - } - bfontpos+=bfontlen[cur]; - } + trace("dwgfx.printtemptile() is not implemented yet"); } public function onscreen(t:int):Boolean { @@ -2603,175 +2866,154 @@ public function bigrprint(x:int, y:int, t:String, r:int, g:int, b:int, cen:Boolean = false, sc:Number = 2):void { if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0; if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; - ct.color = RGB(r, g, b); + ttf.format.horizontalAlign = "right"; + x = x - 320; - scaleMatrix = new Matrix(); - scaleMatrix.scale(sc, sc); + ttf.format.color = RGB(r, g, b); + ttf.format.size = BitmapFont.NATIVE_SIZE * sc; + ttf.text = t; - bigbuffer.fillRect(bigbuffer.rect, 0x000000); - - x = x / sc; - - x -= (len(t)); - - //if (r < -1) r = -1; if (g < 0) g = 0; if (b < 0) b = 0;? - //if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; - ct.color = RGB(r, g, b); - if(sc==2){ - if (cen) x = 80 - (len(t)/2); - }else if (sc == 3) { - if (cen) x = 160/3 - (len(t) / 2); - }else if (sc == 4) { - if (cen) x = 40 - (len(t) / 2); + if (flipmode) { + tposition.identity(); + tposition.scale(1, -1); + tposition.translate(x, y + (8 * sc)); + backbuffer.draw(ttf, tposition); + }else { + tposition.identity(); + tposition.translate(x, y); + backbuffer.draw(ttf, tposition); } - bfontpos = 0; - for (i = 0; i < t.length; i++) { - cur = t.charCodeAt(i); - if (flipmode) { - flipbfont[cur].colorTransform(bfont_rect, ct); - bigbuffer.copyPixels(flipbfont[cur], bfont_rect, new Point(x + bfontpos, 0)); - }else { - bfont[cur].colorTransform(bfont_rect, ct); - bigbuffer.copyPixels(bfont[cur], bfont_rect, new Point(x + bfontpos, 0)); - } - bfontpos+=bfontlen[cur]; - } - - scaleMatrix.translate(0, y); - backbuffer.draw(bigbufferscreen, scaleMatrix); - scaleMatrix.translate(0, -y); + ttf.format.size = BitmapFont.NATIVE_SIZE; } public function bigprint(x:int, y:int, t:String, r:int, g:int, b:int, cen:Boolean = false, sc:Number = 2):void { - if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0; + if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0; if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; - ct.color = RGB(r, g, b); - - scaleMatrix = new Matrix(); - scaleMatrix.scale(sc, sc); - - bigbuffer.fillRect(bigbuffer.rect, 0x000000); - - x = x / sc; - - //if (r < -1) r = -1; if (g < 0) g = 0; if (b < 0) b = 0;? - //if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; - ct.color = RGB(r, g, b); - if(sc==2){ - if (cen) x = 80 - (len(t)/2); - }else if (sc == 3) { - if (cen) x = 160/3 - (len(t) / 2); - }else if (sc == 4) { - if (cen) x = 40 - (len(t) / 2); - } - bfontpos = 0; - for (var ti:int = 0; ti < t.length; ti++) { - cur = t.charCodeAt(ti); - if (flipmode) { - flipbfont[cur].colorTransform(bfont_rect, ct); - bigbuffer.copyPixels(flipbfont[cur], bfont_rect, new Point(x + bfontpos, 0)); - }else { - bfont[cur].colorTransform(bfont_rect, ct); - bigbuffer.copyPixels(bfont[cur], bfont_rect, new Point(x + bfontpos, 0)); - } - bfontpos+=bfontlen[cur]; + if (cen) { + x = 0; + ttf.format.horizontalAlign = "center"; + }else { + ttf.format.horizontalAlign = "left"; } - scaleMatrix.translate(0, y); - backbuffer.draw(bigbufferscreen, scaleMatrix); - scaleMatrix.translate(0, -y); + ttf.format.color = RGB(r, g, b); + ttf.format.size = BitmapFont.NATIVE_SIZE * sc; + ttf.text = t; + + if (flipmode) { + tposition.identity(); + tposition.scale(1, -1); + tposition.translate(x, y + (8 * sc)); + backbuffer.draw(ttf, tposition); + }else { + tposition.identity(); + tposition.translate(x, y); + backbuffer.draw(ttf, tposition); + } + + ttf.format.size = BitmapFont.NATIVE_SIZE; } - public function bprinttemptile(x:int, y:int, t:String, r:int, g:int, b:int, cen:Boolean=false):void { - printmasktemptile(x, y, t, cen); - printtemptile(x, y, t, r, g, b, cen); - } public function len(t:String):int { - bfontpos = 0; - for (var i:int = 0; i < t.length; i++) { - cur = t.charCodeAt(i); - bfontpos+=bfontlen[cur]; - } - return bfontpos; + //For VVVVVV, we're using a fixed width font, so... + return t.length * 8; } public function flashlight():void { - backbuffer.fillRect(backbuffer.rect, 0xBBBBBB); + drawfillrect(0, 0, 320, 240, 187, 187, 187); } public function screenshake():void { - screenbuffer.lock(); - if(flipmode){ - tpoint.x = int((Math.random() * 7) - 4); tpoint.y = int((Math.random() * 7) - 4); - flipmatrix.translate(tpoint.x, tpoint.y); - screenbuffer.draw(backbuffer, flipmatrix); - flipmatrix.translate(-tpoint.x, -tpoint.y); - }else{ - screenbuffer.copyPixels(backbuffer, backbuffer.rect, tl, null, null, false); - tpoint.x = (Math.random() * 7) - 4; tpoint.y = (Math.random() * 7) - 4; - screenbuffer.copyPixels(backbuffer, backbuffer.rect, tpoint, null, null, false); - } - screenbuffer.unlock(); + screenmenubuffer.draw(smallscreen); - backbuffer.lock(); - backbuffer.fillRect(backbuffer.rect, 0x000000); - backbuffer.unlock(); + if (flipmode) { + tposition.identity(); + tposition.scale(1, -1); + tposition.translate(int((Math.random() * 7) - 4), screenheight + int((Math.random() * 7) - 4)); + drawfillrect(0, 0, screenwidth, screenheight, 0, 0, 0); + backbuffer.draw(screenmenubuffer_image, tposition); + }else{ + tposition.identity(); + tposition.translate(int((Math.random() * 7) - 4), int((Math.random() * 7) - 4)); + + backbuffer.draw(screenmenubuffer_image, tposition); + } } public function render():void { - screenbuffer.lock(); - if(flipmode){ - screenbuffer.draw(backbuffer, flipmatrix); - }else{ - screenbuffer.copyPixels(backbuffer, backbuffer.rect, tl, null, null, false); + if (flipmode) { + tposition.identity(); + tposition.scale(1, -1); + tposition.translate(0, screenheight); + + screenmenubuffer.drawBundled(function():void { + screenmenubuffer.draw(smallscreen); + }); + + backbuffer.draw(screenmenubuffer_image, tposition); } - screenbuffer.unlock(); + } + + public function clear_menubuffer():void { + tquad.x = 0; tquad.y = 0; + tquad.width = screenwidth; + tquad.height = screenheight; + tquad.color = RGB(0, 0, 0); - backbuffer.lock(); - backbuffer.fillRect(backbuffer.rect, 0x000000); - backbuffer.unlock(); + menubuffer.drawBundled(function():void { + menubuffer.draw(tquad); + }); + screenmenubuffer.drawBundled(function():void { + screenmenubuffer.draw(tquad); + }); + } + + public function setup_menubuffer():void { + menubuffer.draw(smallscreen); } public function menuoffrender():void { - screenbuffer.lock(); - screenbuffer.copyPixels(menubuffer, menubuffer.rect, tl, null, null, false); + //The backbuffer currently contains the menu, so we save this... + screenmenubuffer.draw(smallscreen); - if(flipmode){ - flipmatrix.translate(0, menuoffset); - screenbuffer.draw(backbuffer, flipmatrix); - flipmatrix.translate(0, -menuoffset); + //Then we draw the old in-game screen instead + backbuffer.draw(menubuffer_image); + + //Finally, we draw the menu back on top of this, offset. + if (flipmode) { + tposition.identity(); + tposition.scale(1, -1); + tposition.translate(0, screenheight + menuoffset); + backbuffer.draw(screenmenubuffer_image, tposition); }else { - tpoint.x = 0; - tpoint.y = menuoffset; - screenbuffer.copyPixels(backbuffer, backbuffer.rect, tpoint, null, null, false); + tposition.identity(); + tposition.translate(0, menuoffset); + backbuffer.draw(screenmenubuffer_image, tposition); } - - screenbuffer.unlock(); - - backbuffer.lock(); - backbuffer.fillRect(backbuffer.rect, 0x000000); - backbuffer.unlock(); } - public var backgrounds:Vector. = new Vector.; - public var images:Vector. = new Vector.; - public var playerlevelimages:Vector. = new Vector.; - public var mobileimages:Vector. = new Vector.; - public var tele:Vector. = new Vector.; - public var tele_rect:Rectangle; - public var tiles:Vector. = new Vector.; - public var tiles2:Vector. = new Vector.; - public var tiles3:Vector. = new Vector.; - public var sprites:Vector. = new Vector.; - public var flipsprites:Vector. = new Vector.; - public var entcolours:Vector. = new Vector.; - public var bfont:Vector. = new Vector.; - public var bfontmask:Vector. = new Vector.; - public var flipbfont:Vector. = new Vector.; - public var flipbfontmask:Vector. = new Vector.; - public var bfontlen:Vector. = new Vector.; - public var bfontpos:int; + public var tquad:Quad; + public var ttf:TextField; + public var tposition:Matrix; + public var starlingassets:AssetManager; + public var starstage:Stage + + public var backgrounds:Vector. = new Vector.; + public var images:Vector. = new Vector.; + public var playerlevelimages:Vector. = new Vector.; + public var mobileimages:Vector. = new Vector.; + public var tele:Vector. = new Vector.; + public var tiles:Vector. = new Vector.; + public var tiles2:Vector. = new Vector.; + public var tiles3:Vector. = new Vector.; + public var sprites:Vector. = new Vector.; + public var flipsprites:Vector. = new Vector.; + public var sprites_bitmap:Vector. = new Vector.; + public var flipsprites_bitmap:Vector. = new Vector.; + public var entcolours:Vector. = new Vector.; + public var customminimap:RenderTexture; + public var cur:int; public var ct:ColorTransform; public var tiles_rect:Rectangle; @@ -2789,22 +3031,30 @@ public var tempstring:String; public var alphamult:uint; public var stemp:String; - public var buffer:BitmapData; public var i:int, j:int, k:int, m:int; public var tpoint:Point, trect:Rectangle; public var madrect:Rectangle, madpoint:Point; - public var temptile:BitmapData; - public var tempsprite:BitmapData; + public var temptile:RenderTexture; + public var tempsprite:RenderTexture; public var footerrect:Rectangle; + //Actual backgrounds - public var backbuffer:BitmapData; - public var frontbuffer:BitmapData; - public var towerbuffer:BitmapData; - public var screenbuffer:BitmapData; - public var menubuffer:BitmapData; - public var screen:Bitmap; + public var backbuffer:RenderTexture; + public var frontbuffer_meshbatch:MeshBatch; + public var menubuffer:RenderTexture; + public var menubuffer_image:Image; + public var screenmenubuffer:RenderTexture; + public var screenmenubuffer_image:Image; + public var towerbufferforeground_meshbatch:MeshBatch; + public var towerbufferbackground_meshbatch:MeshBatch; + public var towerbufferstatic_meshbatch:MeshBatch; + public var warpzonehorizontal_meshbatch:MeshBatch; + public var warpzonevertical_meshbatch:MeshBatch; + public var screen:Image; + public var smallscreen:Image; public var updatebackground:Boolean; + //Textbox Stuff public var ntextbox:int; public var textbox:Vector. = new Vector.; @@ -2846,17 +3096,16 @@ public var flipfontmatrix:Matrix = new Matrix(); public var flipfontmatrix2:Matrix = new Matrix(); - public var scaleMatrix:Matrix = new Matrix(); - public var bigbuffer:BitmapData; - public var bigbufferscreen:Bitmap; - //Mobile stuff public var screensizemultiplier:Number; public var screenoffx:int, screenoffy:int; - public var buttonimg:Vector. = new Vector.; - public var buttonscreen:Vector. = new Vector.; + public var buttonsready:Boolean = false; + public var button_texture:Vector. = new Vector.; + public var button_image:Vector. = new Vector.; + public var button_image_width:Vector. = new Vector.; + public var button_image_height:Vector. = new Vector.; public var buttonactive:Vector. = new Vector.; public var buttonhighlight:Vector. = new Vector.; @@ -2870,5 +3119,11 @@ public var b_gap:int = 40, b_size:int = 26; public var devicex:int, devicey:int; + + [Embed(source = "../data/bitmapsprites.png")] + public static const img_bitmapsprites:Class; + + [Embed(source = "../data/bitmapflipsprites.png")] + public static const img_bitmapflipsprites:Class; } } \ No newline at end of file diff --git a/mobile_version/src/edentitiesclass.as b/mobile_version/src/edentitiesclass.as index 493eaf33..78d4ddd0 100644 --- a/mobile_version/src/edentitiesclass.as +++ b/mobile_version/src/edentitiesclass.as @@ -1,9 +1,4 @@ package { - import flash.display.*; - import flash.geom.*; - import flash.events.*; - import flash.net.*; - public class edentitiesclass { public function edentitiesclass():void { clear(); diff --git a/mobile_version/src/editor.as b/mobile_version/src/editor.as index 6dbe5828..8edb57ea 100644 --- a/mobile_version/src/editor.as +++ b/mobile_version/src/editor.as @@ -1,9 +1,9 @@ package { - import flash.display.*; - import flash.display3D.textures.RectangleTexture; import flash.geom.*; import flash.events.*; import flash.net.*; + import starling.display.Image; + import starling.textures.RenderTexture; public class editor { public static function init():void { @@ -1249,17 +1249,17 @@ package { } public static function fillbox(dwgfx:dwgraphicsclass, x:int, y:int, x2:int, y2:int, c:int):void { - dwgfx.backbuffer.fillRect(new Rectangle(x, y, x2 - x, 1), c); - dwgfx.backbuffer.fillRect(new Rectangle(x, y2 - 1, x2 - x, 1), c); - dwgfx.backbuffer.fillRect(new Rectangle(x, y, 1, y2 - y), c); - dwgfx.backbuffer.fillRect(new Rectangle(x2 - 1, y, 1, y2 - y), c); + dwgfx.drawfillrect(x, y, x2 - x, 1, c); + dwgfx.drawfillrect(x, y2 - 1, x2 - x, 1, c); + dwgfx.drawfillrect(x, y, 1, y2 - y, c); + dwgfx.drawfillrect(x2 - 1, y, 1, y2 - y, c); } public static function fillboxabs(dwgfx:dwgraphicsclass, x:int, y:int, x2:int, y2:int, c:int):void { - dwgfx.backbuffer.fillRect(new Rectangle(x, y, x2, 1), c); - dwgfx.backbuffer.fillRect(new Rectangle(x, y + y2 - 1, x2, 1), c); - dwgfx.backbuffer.fillRect(new Rectangle(x, y, 1, y2), c); - dwgfx.backbuffer.fillRect(new Rectangle(x + x2 - 1, y, 1, y2), c); + dwgfx.drawfillrect(x, y, x2, 1, c); + dwgfx.drawfillrect(x, y + y2 - 1, x2, 1, c); + dwgfx.drawfillrect(x, y, 1, y2, c); + dwgfx.drawfillrect(x + x2 - 1, y, 1, y2, c); } public static function generatecustomminimap(dwgfx:dwgraphicsclass, map:mapclass):void { @@ -1291,68 +1291,74 @@ package { map.custommmysize = 180 - (map.custommmyoff * 2); } - dwgfx.images[12].fillRect(dwgfx.images[12].rect, dwgfx.RGBA(0, 0, 0)); - - var tm:int = 0; - var temp:int = 0; - //Scan over the map size - if(mapheight<=5 && mapwidth<=5){ - //4x map - for (var j2:int = 0; j2 < mapheight; j2++){ - for (var i2:int = 0; i2 < mapwidth; i2++) { - //Ok, now scan over each square - tm = 196; - if (level[i2 + (j2 * maxwidth)].tileset == 1) tm = 96; - - for (var j:int = 0; j < 36; j++) { - for (var i:int = 0; i < 48; i++) { - temp = absfree(int(i * 0.83) + (i2 * 40), int(j * 0.83) + (j2 * 30)); - if(temp>=1){ - //Fill in this pixel - dwgfx.images[12].fillRect(new Rectangle((i2 * 48) + i, (j2 * 36) + j, 1, 1), dwgfx.RGBA(tm, tm, tm)); - } - } - } - } - } - }else if (mapheight <= 10 && mapwidth <= 10) { - //2x map - for (j2 = 0; j2 < mapheight; j2++) { - for (i2 = 0; i2 < mapwidth; i2++) { - //Ok, now scan over each square - tm = 196; - if (level[i2 + (j2 * maxwidth)].tileset == 1) tm = 96; - - for (j = 0; j < 18; j++) { - for (i = 0; i < 24; i++) { - temp = absfree(int(i * 1.6) + (i2 * 40), int(j * 1.6) + (j2 * 30)); - if (temp >= 1) { - //Fill in this pixel - dwgfx.images[12].fillRect(new Rectangle((i2 * 24) + i, (j2 * 18) + j, 1, 1), dwgfx.RGBA(tm, tm, tm)); - } - } - } - } - } - }else { - for (j2 = 0; j2 < mapheight; j2++) { - for (i2 = 0; i2 < mapwidth; i2++) { - //Ok, now scan over each square - tm=196; - if (level[i2 + (j2 * maxwidth)].tileset == 1) tm = 96; - - for (j = 0; j < 9; j++) { - for (i = 0; i < 12; i++) { - temp = absfree(3 + (i * 3) + (i2 * 40), (j * 3) + (j2 * 30)); - if(temp>=1){ - //Fill in this pixel - dwgfx.images[12].fillRect(new Rectangle((i2 * 12) + i, (j2 * 9) + j, 1, 1), dwgfx.RGBA(tm, tm, tm)); - } - } - } - } - } + if (dwgfx.customminimap == null) { + dwgfx.customminimap = new RenderTexture(240, 180); + dwgfx.images[12] = new Image(dwgfx.customminimap); } + dwgfx.customminimap.clear(dwgfx.RGB(0, 0, 0), 1.0); + + dwgfx.customminimap.drawBundled(function():void { + var tm:int = 0; + var temp:int = 0; + //Scan over the map size + if(mapheight<=5 && mapwidth<=5){ + //4x map + for (var j2:int = 0; j2 < mapheight; j2++){ + for (var i2:int = 0; i2 < mapwidth; i2++) { + //Ok, now scan over each square + tm = 196; + if (level[i2 + (j2 * maxwidth)].tileset == 1) tm = 96; + + for (var j:int = 0; j < 36; j++) { + for (var i:int = 0; i < 48; i++) { + temp = absfree(int(i * 0.83) + (i2 * 40), int(j * 0.83) + (j2 * 30)); + if(temp>=1){ + //Fill in this pixel + dwgfx.drawfillrect_onimage(dwgfx.customminimap, (i2 * 48) + i, (j2 * 36) + j, 1, 1, tm, tm, tm); + } + } + } + } + } + }else if (mapheight <= 10 && mapwidth <= 10) { + //2x map + for (j2 = 0; j2 < mapheight; j2++) { + for (i2 = 0; i2 < mapwidth; i2++) { + //Ok, now scan over each square + tm = 196; + if (level[i2 + (j2 * maxwidth)].tileset == 1) tm = 96; + + for (j = 0; j < 18; j++) { + for (i = 0; i < 24; i++) { + temp = absfree(int(i * 1.6) + (i2 * 40), int(j * 1.6) + (j2 * 30)); + if (temp >= 1) { + //Fill in this pixel + dwgfx.drawfillrect_onimage(dwgfx.customminimap, (i2 * 24) + i, (j2 * 18) + j, 1, 1, tm, tm, tm); + } + } + } + } + } + }else { + for (j2 = 0; j2 < mapheight; j2++) { + for (i2 = 0; i2 < mapwidth; i2++) { + //Ok, now scan over each square + tm=196; + if (level[i2 + (j2 * maxwidth)].tileset == 1) tm = 96; + + for (j = 0; j < 9; j++) { + for (i = 0; i < 12; i++) { + temp = absfree(3 + (i * 3) + (i2 * 40), (j * 3) + (j2 * 30)); + if(temp>=1){ + //Fill in this pixel + dwgfx.drawfillrect_onimage(dwgfx.customminimap, (i2 * 12) + i, (j2 * 9) + j, 1, 1, tm, tm, tm); + } + } + } + } + } + } + }); } public static function editorrender(dwgfx:dwgraphicsclass, game:gameclass, map:mapclass, obj:entityclass, help:helpclass):void { diff --git a/mobile_version/src/edlevelclass.as b/mobile_version/src/edlevelclass.as index 19f18a8f..56f96300 100644 --- a/mobile_version/src/edlevelclass.as +++ b/mobile_version/src/edlevelclass.as @@ -1,9 +1,4 @@ package { - import flash.display.*; - import flash.geom.*; - import flash.events.*; - import flash.net.*; - public class edlevelclass{ public function edlevelclass():void { clear(); diff --git a/mobile_version/src/entclass.as b/mobile_version/src/entclass.as index c95c3e6e..d8e65ddf 100644 --- a/mobile_version/src/entclass.as +++ b/mobile_version/src/entclass.as @@ -1,10 +1,7 @@ -package { - import flash.display.*; +package { import flash.geom.*; - import flash.events.*; - import flash.net.*; - public class entclass extends Sprite { + public class entclass{ public function entclass():void { clear(); } diff --git a/mobile_version/src/entityclass.as b/mobile_version/src/entityclass.as index d1deaed8..813f7154 100644 --- a/mobile_version/src/entityclass.as +++ b/mobile_version/src/entityclass.as @@ -1,11 +1,8 @@ -package { +package { import bigroom.input.KeyPoll; - import flash.display.*; import flash.geom.*; - import flash.events.*; - import flash.net.*; - - public class entityclass extends Sprite { + + public class entityclass { static public var BLOCK:Number = 0; static public var TRIGGER:Number = 1; static public var DAMAGE:Number = 2; @@ -587,8 +584,8 @@ break; case TRIGGER: //Trigger blocks[k].type = TRIGGER; - blocks[k].x = xp; - blocks[k].y = yp; + blocks[k].xp = xp; + blocks[k].yp = yp; blocks[k].wp = w; blocks[k].hp = h; blocks[k].rectset(xp, yp, w, h); @@ -598,8 +595,8 @@ break; case DAMAGE: //Damage blocks[k].type = DAMAGE; - blocks[k].x = xp; - blocks[k].y = yp; + blocks[k].xp = xp; + blocks[k].yp = yp; blocks[k].wp = w; blocks[k].hp = h; blocks[k].rectset(xp, yp, w, h); @@ -608,8 +605,8 @@ break; case DIRECTIONAL: //Directional blocks[k].type = DIRECTIONAL; - blocks[k].x = xp; - blocks[k].y = yp; + blocks[k].xp = xp; + blocks[k].yp = yp; blocks[k].wp = w; blocks[k].hp = h; blocks[k].rectset(xp, yp, w, h); @@ -629,8 +626,8 @@ break; case ACTIVITY: //Activity Zone blocks[k].type = ACTIVITY; - blocks[k].x = xp; - blocks[k].y = yp; + blocks[k].xp = xp; + blocks[k].yp = yp; blocks[k].wp = w; blocks[k].hp = h; blocks[k].rectset(xp, yp, w, h); @@ -816,19 +813,19 @@ trig=0; break; case 25: - blocks[k].prompt = "Passion for Exploring"; + blocks[k].prompt = "Passion for exploring"; blocks[k].script = "terminal_juke1"; setblockcolour(k, "orange"); trig=0; break; case 26: - blocks[k].prompt = "Pushing Onwards"; + blocks[k].prompt = "Pushing onwards"; blocks[k].script = "terminal_juke2"; setblockcolour(k, "orange"); trig=0; break; case 27: - blocks[k].prompt = "Positive Force"; + blocks[k].prompt = "Positive force"; blocks[k].script = "terminal_juke3"; setblockcolour(k, "orange"); trig=0; @@ -840,13 +837,13 @@ trig=0; break; case 29: - blocks[k].prompt = "Potential for Anything"; + blocks[k].prompt = "Potential for anything"; blocks[k].script = "terminal_juke5"; setblockcolour(k, "orange"); trig=0; break; case 30: - blocks[k].prompt = "Predestined Fate"; + blocks[k].prompt = "Predestined fate"; blocks[k].script = "terminal_juke6"; setblockcolour(k, "orange"); trig=0; @@ -870,7 +867,7 @@ trig=0; break; case 34: - blocks[k].prompt = "ecroF evitisoP"; + blocks[k].prompt = "ecrof evitisoP"; blocks[k].script = "terminal_juke10"; setblockcolour(k, "orange"); trig=0; @@ -1276,7 +1273,7 @@ entities[k].gravity = true; break; - case 1: //Simple enemy, bouncing off the walls + case 1: entities[k].rule = 1; entities[k].xp = xp; entities[k].yp = yp; entities[k].behave = vx; entities[k].para = vy; @@ -3790,14 +3787,14 @@ colpoint1.x = entities[i].xp; colpoint1.y = entities[i].yp; colpoint2.x = entities[j].xp; colpoint2.y = entities[j].yp; if (dwgfx.flipmode) { - if (dwgfx.flipsprites[entities[i].drawframe].hitTest( - colpoint1, 1, dwgfx.flipsprites[entities[j].drawframe], colpoint2, 1)) { + if (dwgfx.flipsprites_bitmap[entities[i].drawframe].hitTest( + colpoint1, 1, dwgfx.flipsprites_bitmap[entities[j].drawframe], colpoint2, 1)) { //Do the collision stuff game.deathseq = 30; } - }else{ - if (dwgfx.sprites[entities[i].drawframe].hitTest( - colpoint1, 1, dwgfx.sprites[entities[j].drawframe], colpoint2, 1)) { + }else { + if (dwgfx.sprites_bitmap[entities[i].drawframe].hitTest( + colpoint1, 1, dwgfx.sprites_bitmap[entities[j].drawframe], colpoint2, 1)) { //Do the collision stuff game.deathseq = 30; } @@ -3851,9 +3848,9 @@ if (entities[j].onentity > 0) { //ok; only check the actual collision if they're in a close proximity temp = entities[i].yp - entities[j].yp; - if (temp > -30 && temp < 30) { + if (temp < 30 || temp > -30) { temp = entities[i].xp - entities[j].xp; - if (temp > -30 && temp < 30) { + if (temp < 30 || temp > -30) { if (entitycollide(i, j)) entities[j].state = entities[j].onentity; } } @@ -3890,14 +3887,14 @@ colpoint1.x = entities[i].xp; colpoint1.y = entities[i].yp; colpoint2.x = entities[j].xp; colpoint2.y = entities[j].yp; if (dwgfx.flipmode) { - if (dwgfx.flipsprites[entities[i].drawframe].hitTest( - colpoint1, 1, dwgfx.flipsprites[entities[j].drawframe], colpoint2, 1)) { + if (dwgfx.flipsprites_bitmap[entities[i].drawframe].hitTest( + colpoint1, 1, dwgfx.flipsprites_bitmap[entities[j].drawframe], colpoint2, 1)) { //Do the collision stuff game.deathseq = 30; game.scmhurt = true; } - }else{ - if (dwgfx.sprites[entities[i].drawframe].hitTest( - colpoint1, 1, dwgfx.sprites[entities[j].drawframe], colpoint2, 1)) { + }else { + if (dwgfx.sprites_bitmap[entities[i].drawframe].hitTest( + colpoint1, 1, dwgfx.sprites_bitmap[entities[j].drawframe], colpoint2, 1)) { //Do the collision stuff game.deathseq = 30; game.scmhurt = true; } @@ -4019,4 +4016,4 @@ public var mobilemenus:Boolean = true; } -} +} \ No newline at end of file diff --git a/mobile_version/src/gameclass.as b/mobile_version/src/gameclass.as index 2f487b8f..e2e69729 100644 --- a/mobile_version/src/gameclass.as +++ b/mobile_version/src/gameclass.as @@ -325,7 +325,7 @@ //These coordinates now need to be translated to actual screen coordinates //Gamecenter if(menustart && !showloadingnotice) { - if (inbox(m_touchx, m_touchy, 0, 0, dwgfx.buttonscreen[0].width + (dwgfx.buttonxspacing * 2), dwgfx.buttonscreen[0].height + (dwgfx.buttonyspacing * 2))) { + if (inbox(m_touchx, m_touchy, 0, 0, dwgfx.button_image_width[0] + (dwgfx.buttonxspacing * 2), dwgfx.button_image_height[0] + (dwgfx.buttonyspacing * 2))) { //Bring up game center achievements scores.showAchievements(); } @@ -465,11 +465,11 @@ m_touchy = key.touchy[key.touchPoints - 1]; if (dwgfx.flipmode) m_touchy = dwgfx.devicey - m_touchy; - if (inbox(m_touchx, m_touchy, dwgfx.devicex - dwgfx.buttonscreen[0].width - dwgfx.buttonxspacing, 0, dwgfx.buttonscreen[0].width + (dwgfx.buttonxspacing*2), dwgfx.buttonscreen[0].height + (dwgfx.buttonyspacing*2) )) { + if (inbox(m_touchx, m_touchy, dwgfx.devicex - dwgfx.button_image_width[0] - dwgfx.buttonxspacing, 0, dwgfx.button_image_width[0] + (dwgfx.buttonxspacing*2), dwgfx.button_image_height[0] + (dwgfx.buttonyspacing*2) )) { press_map = true; } if(menupage == 0){ - if (inbox(m_touchx, m_touchy, 0, 0, dwgfx.buttonscreen[1].width + (dwgfx.buttonxspacing*2), dwgfx.buttonscreen[1].height + (dwgfx.buttonyspacing*2) )) { + if (inbox(m_touchx, m_touchy, 0, 0, dwgfx.button_image_width[1] + (dwgfx.buttonxspacing*2), dwgfx.button_image_height[1] + (dwgfx.buttonyspacing*2) )) { menupage = 30; music.playef(11, 10); } @@ -646,12 +646,12 @@ }else */ if (insecretlab) { - if (inbox(key.touchx[i], key.touchy[i], 0, 0, dwgfx.buttonscreen[0].width + (dwgfx.buttonxspacing * 2), dwgfx.buttonscreen[0].height + (dwgfx.buttonyspacing * 2) )) { + if (inbox(key.touchx[i], key.touchy[i], 0, 0, dwgfx.button_image_width[0] + (dwgfx.buttonxspacing * 2), dwgfx.button_image_height[0] + (dwgfx.buttonyspacing * 2) )) { scores.opengamecenter(); press_map = true; } } - if (inbox(key.touchx[i], key.touchy[i], dwgfx.devicex - dwgfx.buttonscreen[0].width - dwgfx.buttonxspacing, 0, dwgfx.buttonscreen[0].width + (dwgfx.buttonxspacing*2), dwgfx.buttonscreen[0].height + (dwgfx.buttonyspacing*2) )) { + if (inbox(key.touchx[i], key.touchy[i], dwgfx.devicex - dwgfx.button_image_width[0] - dwgfx.buttonxspacing, 0, dwgfx.button_image_width[0] + (dwgfx.buttonxspacing*2), dwgfx.button_image_height[0] + (dwgfx.buttonyspacing*2) )) { press_map = true; }else { if (key.touchid[i] != key.controlstick) { @@ -670,12 +670,12 @@ // press_action = true; //}else if (insecretlab) { - if (inbox(key.touchx[i], key.touchy[i], 0, 0, dwgfx.buttonscreen[0].width + (dwgfx.buttonxspacing * 2), dwgfx.buttonscreen[0].height + (dwgfx.buttonyspacing * 2) )) { + if (inbox(key.touchx[i], key.touchy[i], 0, 0, dwgfx.button_image_width[0] + (dwgfx.buttonxspacing * 2), dwgfx.button_image_height[0] + (dwgfx.buttonyspacing * 2) )) { scores.opengamecenter(); press_map = true; } } - if (inbox(key.touchx[i], key.touchy[i], dwgfx.devicex - dwgfx.buttonscreen[0].width - dwgfx.buttonxspacing, 0, dwgfx.buttonscreen[0].width + (dwgfx.buttonxspacing*2), dwgfx.buttonscreen[0].height + (dwgfx.buttonyspacing*2) )) { + if (inbox(key.touchx[i], key.touchy[i], dwgfx.devicex - dwgfx.button_image_width[0] - dwgfx.buttonxspacing, 0, dwgfx.button_image_width[0] + (dwgfx.buttonxspacing*2), dwgfx.button_image_height[0] + (dwgfx.buttonyspacing*2) )) { press_map = true; }else{ if (inbox(key.touchx[i], key.touchy[i], 0, 0, dwgfx.devicex / 2, dwgfx.devicey)) { @@ -729,12 +729,12 @@ //D-Pad controls for (i = 0; i < key.touchPoints; i++) { if (insecretlab) { - if (inbox(key.touchx[i], key.touchy[i], 0, 0, dwgfx.buttonscreen[0].width + (dwgfx.buttonxspacing * 2), dwgfx.buttonscreen[0].height + (dwgfx.buttonyspacing * 2) )) { + if (inbox(key.touchx[i], key.touchy[i], 0, 0, dwgfx.button_image_width[0] + (dwgfx.buttonxspacing * 2), dwgfx.button_image_height[0] + (dwgfx.buttonyspacing * 2) )) { scores.opengamecenter(); press_map = true; } } - if (inbox(key.touchx[i], key.touchy[i], dwgfx.devicex - dwgfx.buttonscreen[0].width - dwgfx.buttonxspacing, 0, dwgfx.buttonscreen[0].width + (dwgfx.buttonxspacing*2), dwgfx.buttonscreen[0].height + (dwgfx.buttonyspacing*2) )) { + if (inbox(key.touchx[i], key.touchy[i], dwgfx.devicex - dwgfx.button_image_width[0] - dwgfx.buttonxspacing, 0, dwgfx.button_image_width[0] + (dwgfx.buttonxspacing*2), dwgfx.button_image_height[0] + (dwgfx.buttonyspacing*2) )) { press_map = true; }else if (inbox(key.touchx[i], key.touchy[i], 0, dwgfx.buttonpos[2].y - (dwgfx.devicey*2/3), dwgfx.buttonsize + dwgfx.buttonxspacing+ (dwgfx.buttonxspacing/2), dwgfx.devicey)) { press_left = true; diff --git a/mobile_version/src/helpclass.as b/mobile_version/src/helpclass.as index 59f0b59b..5c2c723b 100644 --- a/mobile_version/src/helpclass.as +++ b/mobile_version/src/helpclass.as @@ -1,11 +1,11 @@ -package { +package { import flash.display.*; import flash.geom.*; import flash.events.*; import flash.net.*; import flash.system.System; - public class helpclass extends Sprite { + public class helpclass{ public function init():void { sine = new Array(); cosine = new Array(); diff --git a/mobile_version/src/includes/input.as b/mobile_version/src/includes/input.as index 5c792a83..91f170e2 100644 --- a/mobile_version/src/includes/input.as +++ b/mobile_version/src/includes/input.as @@ -239,8 +239,8 @@ public function titleinput(key:KeyPoll, dwgfx:dwgraphicsclass, map:mapclass, gam }else if (game.currentmenuoption == 4) { //More games (external link) music.playef(11, 10); - var distractionware_link:URLRequest = new URLRequest( "http://distractionware.com/games/ios/" ); - //var distractionware_link:URLRequest = new URLRequest( "http://distractionware.com/games/android/" ); + //var distractionware_link:URLRequest = new URLRequest( "http://distractionware.com/games/ios/" ); + var distractionware_link:URLRequest = new URLRequest( "http://distractionware.com/games/android/" ); navigateToURL( distractionware_link, "_blank" ); } }else{ @@ -649,7 +649,7 @@ public function titleinput(key:KeyPoll, dwgfx:dwgraphicsclass, map:mapclass, gam } }else if (game.currentmenuname == "unlockmenu") { if (game.currentmenuoption == 0) { - //unlock time trials separately... + //unlock time trials seperately... music.playef(11, 10); game.createmenu("unlockmenutrials"); map.nexttowercolour(); @@ -1337,7 +1337,7 @@ public function gameinput(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma game.gamestate = 5; dwgfx.menuoffset = 240; //actually this should count the roomname if (map.extrarow) dwgfx.menuoffset -= 10; - dwgfx.menubuffer.copyPixels(dwgfx.screenbuffer, dwgfx.screenbuffer.rect, dwgfx.tl, null, null, false); + dwgfx.setup_menubuffer(); dwgfx.resumegamemode = false; game.useteleporter = true; @@ -1371,7 +1371,7 @@ public function gameinput(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma game.gamestate = MAPMODE; game.gamesaved = false; dwgfx.resumegamemode = false; game.menupage = 20; // The Map Page - dwgfx.menubuffer.copyPixels(dwgfx.screenbuffer, dwgfx.screenbuffer.rect, dwgfx.tl, null, null, false); + dwgfx.setup_menubuffer(); dwgfx.menuoffset = 240; //actually this should count the roomname if (map.extrarow) dwgfx.menuoffset -= 10; }else if (game.intimetrial && dwgfx.fademode == 0) { @@ -1382,7 +1382,7 @@ public function gameinput(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma game.gamestate = MAPMODE; game.gamesaved = false; dwgfx.resumegamemode = false; game.menupage = 10; // The Map Page - dwgfx.menubuffer.copyPixels(dwgfx.screenbuffer, dwgfx.screenbuffer.rect, dwgfx.tl, null, null, false); + dwgfx.setup_menubuffer(); dwgfx.menuoffset = 240; //actually this should count the roomname if (map.extrarow) dwgfx.menuoffset -= 10; }else{ @@ -1399,7 +1399,7 @@ public function gameinput(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma map.cursordelay = 0; map.cursorstate = 0; game.gamesaved = false; dwgfx.resumegamemode = false; game.menupage = 0; // The Map Page - dwgfx.menubuffer.copyPixels(dwgfx.screenbuffer, dwgfx.screenbuffer.rect, dwgfx.tl, null, null, false); + dwgfx.setup_menubuffer(); dwgfx.menuoffset = 240; //actually this should count the roomname if (map.extrarow) dwgfx.menuoffset -= 10; } @@ -1411,7 +1411,7 @@ public function gameinput(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma game.gamestate = MAPMODE; game.gamesaved = false; dwgfx.resumegamemode = false; game.menupage = 10; // The Map Page - dwgfx.menubuffer.copyPixels(dwgfx.screenbuffer, dwgfx.screenbuffer.rect, dwgfx.tl, null, null, false); + dwgfx.setup_menubuffer(); dwgfx.menuoffset = 240; //actually this should count the roomname if (map.extrarow) dwgfx.menuoffset -= 10; } @@ -1553,7 +1553,7 @@ public function mapinput(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, map } if (dwgfx.fademode == 1) { - dwgfx.menubuffer.fillRect(dwgfx.menubuffer.rect, 0x000000); + dwgfx.clear_menubuffer(); dwgfx.resumegamemode = true; obj.removeallblocks(); game.menukludge = false; diff --git a/mobile_version/src/includes/logic.as b/mobile_version/src/includes/logic.as index 4993a378..46fd3af2 100644 --- a/mobile_version/src/includes/logic.as +++ b/mobile_version/src/includes/logic.as @@ -465,7 +465,7 @@ public function gamelogic(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma if (obj.entities[i].type == 2 && obj.entities[i].state == 3) { //Ok! super magical exception for the room with the intention death for the shiny trinket //fix this when the maps are finalised - if (game.roomx != 111 || game.roomy != 107 || map.custommode) { + if (game.roomx != 111 && game.roomy != 107) { obj.entities[i].state = 4; }else { obj.entities[i].state = 4; diff --git a/mobile_version/src/includes/render.as b/mobile_version/src/includes/render.as index 72dd7872..6d4d653f 100644 --- a/mobile_version/src/includes/render.as +++ b/mobile_version/src/includes/render.as @@ -1,11 +1,12 @@ import flash.geom.Point; +import starling.textures.Texture; public function titlerender(key:KeyPoll, dwgfx:dwgraphicsclass, map:mapclass, game:gameclass, obj:entityclass, help:helpclass):void { dwgfx.drawbutton(game, help); - dwgfx.backbuffer.lock(); - dwgfx.backbuffer.fillRect(dwgfx.backbuffer.rect, 0x000000); + //dwgfx.backbuffer.lock(); if (!game.menustart) { + dwgfx.cls(0x000000); tr = 164 - (help.glow / 2) - Math.random() * 4; tg = 164 - (help.glow / 2) - Math.random() * 4; tb = 164 - (help.glow / 2) - Math.random() * 4; @@ -26,9 +27,10 @@ public function titlerender(key:KeyPoll, dwgfx:dwgraphicsclass, map:mapclass, ga /*dwgfx.print(5, 5, "IGF WIP Build, 29th Oct '09", tr, tg, tb, true); dwgfx.print(5, 200, "Game by Terry Cavanagh", tr, tg, tb, true); - dwgfx.print(5, 210, "Music by Magnus P~lsson", tr, tg, tb, true); + dwgfx.print(5, 210, "Music by Magnus Palsson", tr, tg, tb, true); dwgfx.print(5, 220, "Roomnames by Bennett Foddy", tr, tg, tb, true);*/ }else { + dwgfx.cls(dwgfx.tower_bgdarkcol[map.colstate]); if(!game.colourblindmode) dwgfx.drawtowerbackgroundsolo(map); tr = map.r - (help.glow / 4) - Math.random() * 4; @@ -39,8 +41,7 @@ public function titlerender(key:KeyPoll, dwgfx:dwgraphicsclass, map:mapclass, ga if (tb < 0) tb = 0; if(tb>255) tb=255; temp = 30+20; - - if(game.currentmenuname=="mainmenu"){ + if (game.currentmenuname == "mainmenu") { dwgfx.drawsprite((160 - 96) + 0 * 32, temp, 23, tr, tg, tb); dwgfx.drawsprite((160 - 96) + 1 * 32, temp, 23, tr, tg, tb); dwgfx.drawsprite((160 - 96) + 2 * 32, temp, 23, tr, tg, tb); @@ -116,7 +117,7 @@ public function titlerender(key:KeyPoll, dwgfx:dwgraphicsclass, map:mapclass, ga //dwgfx.print( 40, 85, "http://www.distractionware.com", tr, tg, tb, true); dwgfx.print( -1, 120, "and features music by", tr, tg, tb, true); - dwgfx.bigprint( 40, 135, "Magnus P~lsson", tr, tg, tb, true, 2); + dwgfx.bigprint( 40, 135, "Magnus Palsson", tr, tg, tb, true, 2); dwgfx.drawimagecol(8, -1, 156, tr *0.75, tg *0.75, tb *0.75, true); //dwgfx.print( 40, 155, "http://souleye.madtracker.net", tr, tg, tb, true); }else if (game.currentmenuname == "credits2") { @@ -199,7 +200,7 @@ public function titlerender(key:KeyPoll, dwgfx:dwgraphicsclass, map:mapclass, ga dwgfx.print( 80, 80,"Joshua Buergel", tr, tg, tb); dwgfx.print( 80, 90,"Joshua Hochner", tr, tg, tb); dwgfx.print( 80, 100,"Kurt Ostfeld", tr, tg, tb); - dwgfx.print( 80, 110,"Magnus P~lsson", tr, tg, tb); + dwgfx.print( 80, 110,"Magnus Palsson", tr, tg, tb); dwgfx.print( 80, 120,"Mark Neschadimenko", tr, tg, tb); dwgfx.print( 80, 130,"Matt Antonellis", tr, tg, tb); dwgfx.print( 80, 140,"Matthew Reppert", tr, tg, tb); @@ -483,7 +484,7 @@ public function titlerender(key:KeyPoll, dwgfx:dwgraphicsclass, map:mapclass, ga /* dwgfx.bigprint( -1, 30, "Unlock Time Trials", tr, tg, tb, true); dwgfx.print( -1, 65, "You can unlock each time", tr, tg, tb, true); - dwgfx.print( -1, 75, "trial separately.", tr, tg, tb, true); + dwgfx.print( -1, 75, "trial seperately.", tr, tg, tb, true); */ }else if (game.currentmenuname == "timetrials") { /* @@ -688,27 +689,27 @@ public function titlerender(key:KeyPoll, dwgfx:dwgraphicsclass, map:mapclass, ga }else if (game.currentmenuname == "unlocktimetrial") { dwgfx.bigprint( -1, 45, "Congratulations!", tr, tg, tb, true, 2); - dwgfx.print( -1, 125, "You have unlocked", tr, tg, tb, true); + dwgfx.print( -1, 125, "Your have unlocked", tr, tg, tb, true); dwgfx.print( -1, 135, "a new Time Trial.", tr, tg, tb, true); }else if (game.currentmenuname == "unlocktimetrials") { dwgfx.bigprint( -1, 45, "Congratulations!", tr, tg, tb, true, 2); - dwgfx.print( -1, 125, "You have unlocked some", tr, tg, tb, true); + dwgfx.print( -1, 125, "Your have unlocked some", tr, tg, tb, true); dwgfx.print( -1, 135, "new Time Trials.", tr, tg, tb, true); }else if (game.currentmenuname == "unlocknodeathmode") { dwgfx.bigprint( -1, 45, "Congratulations!", tr, tg, tb, true, 2); - dwgfx.print( -1, 125, "You have unlocked", tr, tg, tb, true); + dwgfx.print( -1, 125, "Your have unlocked", tr, tg, tb, true); dwgfx.print( -1, 135, "No Death Mode.", tr, tg, tb, true); }else if (game.currentmenuname == "unlockflipmode") { dwgfx.bigprint( -1, 45, "Congratulations!", tr, tg, tb, true, 2); - dwgfx.print( -1, 125, "You have unlocked", tr, tg, tb, true); + dwgfx.print( -1, 125, "Your have unlocked", tr, tg, tb, true); dwgfx.print( -1, 135, "Flip Mode.", tr, tg, tb, true); }else if (game.currentmenuname == "unlockintermission") { dwgfx.bigprint( -1, 45, "Congratulations!", tr, tg, tb, true, 2); - dwgfx.print( -1, 125, "You have unlocked", tr, tg, tb, true); + dwgfx.print( -1, 125, "Your have unlocked", tr, tg, tb, true); dwgfx.print( -1, 135, "the intermission levels.", tr, tg, tb, true); } @@ -784,14 +785,14 @@ public function titlerender(key:KeyPoll, dwgfx:dwgraphicsclass, map:mapclass, ga }else{ dwgfx.render(); } - dwgfx.backbuffer.unlock(); + //dwgfx.backbuffer.unlock(); } -public function gamecompleterender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, obj:entityclass, help:helpclass):void { +public function gamecompleterender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, map:mapclass, obj:entityclass, help:helpclass):void { dwgfx.drawbutton(game, help); - dwgfx.backbuffer.lock(); - dwgfx.backbuffer.fillRect(dwgfx.backbuffer.rect, 0x000000); + //dwgfx.backbuffer.lock(); + dwgfx.cls(dwgfx.tower_bgdarkcol[map.colstate]); if(!game.colourblindmode) dwgfx.drawtowerbackgroundsolo(map); //dwgfx.drawtowermap(map); @@ -856,7 +857,7 @@ public function gamecompleterender(key:KeyPoll, dwgfx:dwgraphicsclass, game:game if (dwgfx.onscreen(640 + game.creditposition)) { dwgfx.print(40, 640 + game.creditposition, "With Music by", tr, tg, tb); - dwgfx.bigprint(60, 650 + game.creditposition, "Magnus P~lsson", tr, tg, tb); + dwgfx.bigprint(60, 650 + game.creditposition, "Magnus Palsson", tr, tg, tb); } if (dwgfx.onscreen(680 + game.creditposition)) { @@ -934,7 +935,7 @@ if (dwgfx.onscreen(1410 + game.creditposition)) dwgfx.print(-1, 1420 + game.cred if (dwgfx.onscreen(1420 + game.creditposition)) dwgfx.print(-1, 1430 + game.creditposition,"Joshua Buergel", tr, tg, tb, true); if (dwgfx.onscreen(1430 + game.creditposition)) dwgfx.print(-1, 1440 + game.creditposition,"Joshua Hochner", tr, tg, tb, true); if (dwgfx.onscreen(1440 + game.creditposition)) dwgfx.print(-1, 1450 + game.creditposition,"Kurt Ostfeld", tr, tg, tb, true); -if (dwgfx.onscreen(1450 + game.creditposition)) dwgfx.print(-1, 1460 + game.creditposition, "Magnus P~lsson", tr, tg, tb, true); +if (dwgfx.onscreen(1450 + game.creditposition)) dwgfx.print(-1, 1460 + game.creditposition, "Magnus Palsson", tr, tg, tb, true); if (dwgfx.onscreen(1460 + game.creditposition)) dwgfx.print(-1, 1470 + game.creditposition,"Mark Neschadimenko", tr, tg, tb, true); if (dwgfx.onscreen(1470 + game.creditposition)) dwgfx.print(-1, 1480 + game.creditposition,"Matt Antonellis", tr, tg, tb, true); if (dwgfx.onscreen(1480 + game.creditposition)) dwgfx.print(-1, 1490 + game.creditposition,"Matthew Reppert", tr, tg, tb, true); @@ -971,14 +972,14 @@ if (dwgfx.onscreen(1760 + game.creditposition)) dwgfx.bigprint( -1, 1760 + game. }else{ dwgfx.render(); } - dwgfx.backbuffer.unlock(); + //dwgfx.backbuffer.unlock(); } public function gamecompleterender2(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, obj:entityclass, help:helpclass):void { dwgfx.drawbutton(game, help); - dwgfx.backbuffer.lock(); - dwgfx.backbuffer.fillRect(dwgfx.backbuffer.rect, 0x000000); + //dwgfx.backbuffer.lock(); + dwgfx.cls(0x000000); dwgfx.drawimage(10, 0, 0); @@ -1013,7 +1014,7 @@ public function gamecompleterender2(key:KeyPoll, dwgfx:dwgraphicsclass, game:gam }else{ dwgfx.render(); } - dwgfx.backbuffer.unlock(); + //dwgfx.backbuffer.unlock(); } public function gamerender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, map:mapclass, @@ -1021,7 +1022,7 @@ public function gamerender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, m dwgfx.drawbutton(game, help); - dwgfx.backbuffer.lock(); + //dwgfx.backbuffer.lock(); if(!game.blackout){ if(!game.colourblindmode) dwgfx.drawbackground(map.background, map); @@ -1050,6 +1051,8 @@ public function gamerender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, m } dwgfx.drawentities(map, obj, help); + }else { + dwgfx.cls(0x000000); } /*for(i=0; i 100 && !game.advancetext && game.hascontrol && !script.running && !game.intimetrial) { i = obj.getplayer(); if(dwgfx.flipmode){ - dwgfx.bprint(5, 20, "- Press ENTER to Teleport -", game.readytotele - 20 - (help.glow / 2), game.readytotele - 20 - (help.glow / 2), game.readytotele, true); + dwgfx.print(5, 20, "- Press ENTER to Teleport -", game.readytotele - 20 - (help.glow / 2), game.readytotele - 20 - (help.glow / 2), game.readytotele, true); }else { - dwgfx.bprint(5, 210, "- Press ENTER to Teleport -", game.readytotele - 20 - (help.glow / 2), game.readytotele - 20 - (help.glow / 2), game.readytotele, true); + dwgfx.print(5, 210, "- Press ENTER to Teleport -", game.readytotele - 20 - (help.glow / 2), game.readytotele - 20 - (help.glow / 2), game.readytotele, true); } } } @@ -1304,7 +1307,7 @@ public function gamerender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, m dwgfx.render(); } - dwgfx.backbuffer.unlock(); + //dwgfx.backbuffer.unlock(); } public function maprender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, map:mapclass, @@ -1312,13 +1315,14 @@ public function maprender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma dwgfx.drawbutton(game, help); - dwgfx.backbuffer.lock(); + //dwgfx.backbuffer.lock(); //dwgfx.drawgui(help); //draw screen alliteration //Roomname: + dwgfx.drawfillrect(0, 0, 320, 12, 0, 0, 0); temp = map.area(game.roomx, game.roomy); if (temp < 2 && !map.custommode && dwgfx.fademode == 0) { if (game.roomx >= 102 && game.roomx <= 104 && game.roomy >= 110 && game.roomy <= 111) { @@ -1343,19 +1347,17 @@ public function maprender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma dwgfx.crewframedelay = 8; dwgfx.crewframe = (dwgfx.crewframe + 1) % 2; } - - //Menubar: - dwgfx.drawtextbox( -10, 212, 42, 3, 65, 185, 207); - switch(game.menupage) { + //Menubar: + //dwgfx.drawtextbox( -10, 212, 42, 3, 65, 185, 207); + dwgfx.drawfillrect(0, 212, 320, 24, 11, 31, 35); + dwgfx.drawfillrect(0, 212, 320, 2, 65, 185, 207); + dwgfx.drawfillrect(0, 215, 320, 1, 65, 185, 207); + dwgfx.drawfillrect(0, 230, 320, 1, 65, 185, 207); + dwgfx.drawfillrect(0, 232, 320, 2, 65, 185, 207); + + switch(game.menupage) { case 0: - dwgfx.print(30 - 8, 220, "[MAP]", 196, 196, 255 - help.glow); - if (game.insecretlab) { dwgfx.print(103, 220, "GRAV", 64, 64, 64); - }else if (obj.flags[67] == 1 && !map.custommode) { dwgfx.print(103, 220, "SHIP", 64,64,64); - }else{ dwgfx.print(103, 220, "CREW", 64,64,64);} - dwgfx.print(185-4, 220, "STATS", 64,64,64); - dwgfx.print(258, 220, "SAVE", 64, 64, 64); - if (game.mobilemenu) { dwgfx.drawmobilebutton(game, 30 - 8, 220, 56, dwgfx.b_size, "MAP", 65, 185, 207); if (game.insecretlab) { dwgfx.drawmobilebutton(game, 103, 220, 56, dwgfx.b_size, "GRAV", 32, 92, 104); @@ -1363,6 +1365,13 @@ public function maprender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma }else{ dwgfx.drawmobilebutton(game, 103, 220, 56, dwgfx.b_size, "CREW", 32, 92, 104);} dwgfx.drawmobilebutton(game, 185-4, 220, 56, dwgfx.b_size, "STATS", 32, 92, 104); dwgfx.drawmobilebutton(game, 258, 220, 56, dwgfx.b_size, "QUIT", 32, 92, 104); + }else { + dwgfx.print(30 - 8, 220, "[MAP]", 196, 196, 255 - help.glow); + if (game.insecretlab) { dwgfx.print(103, 220, "GRAV", 64, 64, 64); + }else if (obj.flags[67] == 1 && !map.custommode) { dwgfx.print(103, 220, "SHIP", 64,64,64); + }else{ dwgfx.print(103, 220, "CREW", 64,64,64);} + dwgfx.print(185-4, 220, "STATS", 64,64,64); + dwgfx.print(258, 220, "SAVE", 64, 64, 64); } if (map.finalmode || (map.custommode && !map.customshowmm)) { @@ -1379,7 +1388,7 @@ public function maprender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma }else if(map.custommode){ //draw the map image dwgfx.drawcustompixeltextbox(35 + map.custommmxoff, 16 + map.custommmyoff, map.custommmxsize + 10, map.custommmysize + 10, (map.custommmxsize + 10) / 8, (map.custommmysize + 10) / 8, 65, 185, 207, 4, 0); - dwgfx.drawpartimage(12, 40 + map.custommmxoff, 21 + map.custommmyoff, map.custommmxsize, map.custommmysize); + dwgfx.drawpartimage(dwgfx.customminimap as Texture, 40 + map.custommmxoff, 21 + map.custommmyoff, map.custommmxsize, map.custommmysize); //Black out here if(map.customzoom==4){ @@ -1564,16 +1573,16 @@ public function maprender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma break; case 1: if (game.insecretlab) { - dwgfx.print(30, 220, "MAP", 64,64,64); - dwgfx.print(103-8, 220, "[GRAV]", 196, 196, 255 - help.glow); - dwgfx.print(185-4, 220, "STATS", 64,64,64); - dwgfx.print(258, 220, "SAVE", 64, 64, 64); - if (game.mobilemenu) { dwgfx.drawmobilebutton(game, 30 - 8, 220, 56, dwgfx.b_size, "MAP", 32, 92, 104); dwgfx.drawmobilebutton(game, 103, 220, 56, dwgfx.b_size, "GRAV", 65, 185, 207); dwgfx.drawmobilebutton(game, 185-4, 220, 56, dwgfx.b_size, "STATS", 32, 92, 104); dwgfx.drawmobilebutton(game, 258, 220, 56, dwgfx.b_size, "QUIT", 32, 92, 104); + }else { + dwgfx.print(30, 220, "MAP", 64,64,64); + dwgfx.print(103-8, 220, "[GRAV]", 196, 196, 255 - help.glow); + dwgfx.print(185-4, 220, "STATS", 64,64,64); + dwgfx.print(258, 220, "SAVE", 64, 64, 64); } if (dwgfx.flipmode) { @@ -1610,16 +1619,16 @@ public function maprender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma } } }else if (obj.flags[67] == 1 && !map.custommode) { - dwgfx.print(30, 220, "MAP", 64,64,64); - dwgfx.print(103-8, 220, "[SHIP]", 196, 196, 255 - help.glow); - dwgfx.print(185-4, 220, "STATS", 64,64,64); - dwgfx.print(258, 220, "SAVE", 64, 64, 64); - if (game.mobilemenu) { dwgfx.drawmobilebutton(game, 30 - 8, 220, 56, dwgfx.b_size, "MAP", 32, 92, 104); dwgfx.drawmobilebutton(game, 103, 220, 56, dwgfx.b_size, "SHIP", 65, 185, 207); dwgfx.drawmobilebutton(game, 185-4, 220, 56, dwgfx.b_size, "STATS", 32, 92, 104); dwgfx.drawmobilebutton(game, 258, 220, 56, dwgfx.b_size, "QUIT", 32, 92, 104); + }else { + dwgfx.print(30, 220, "MAP", 64,64,64); + dwgfx.print(103-8, 220, "[SHIP]", 196, 196, 255 - help.glow); + dwgfx.print(185-4, 220, "STATS", 64,64,64); + dwgfx.print(258, 220, "SAVE", 64, 64, 64); } if (game.mobilemenu) { @@ -1635,16 +1644,16 @@ public function maprender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma dwgfx.print(0, 105, "Press ACTION to warp to the ship.", 196, 196, 255 - help.glow, true); } }else if(map.custommode){ - dwgfx.print(30, 220, "MAP", 64,64,64); - dwgfx.print(103-8, 220, "[CREW]", 196, 196, 255 - help.glow); - dwgfx.print(185-4, 220, "STATS", 64,64,64); - dwgfx.print(258, 220, "SAVE", 64, 64, 64); - if (game.mobilemenu) { dwgfx.drawmobilebutton(game, 30 - 8, 220, 56, dwgfx.b_size, "MAP", 32, 92, 104); dwgfx.drawmobilebutton(game, 103, 220, 56, dwgfx.b_size, "CREW", 65, 185, 207); dwgfx.drawmobilebutton(game, 185-4, 220, 56, dwgfx.b_size, "STATS", 32, 92, 104); dwgfx.drawmobilebutton(game, 258, 220, 56, dwgfx.b_size, "QUIT", 32, 92, 104); + }else { + dwgfx.print(30, 220, "MAP", 64,64,64); + dwgfx.print(103-8, 220, "[CREW]", 196, 196, 255 - help.glow); + dwgfx.print(185-4, 220, "STATS", 64,64,64); + dwgfx.print(258, 220, "SAVE", 64, 64, 64); } if (dwgfx.flipmode){ @@ -1675,16 +1684,16 @@ public function maprender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma } } }else{ - dwgfx.print(30, 220, "MAP", 64,64,64); - dwgfx.print(103-8, 220, "[CREW]", 196, 196, 255 - help.glow); - dwgfx.print(185-4, 220, "STATS", 64,64,64); - dwgfx.print(258, 220, "SAVE", 64, 64, 64); - if (game.mobilemenu) { dwgfx.drawmobilebutton(game, 30 - 8, 220, 56, dwgfx.b_size, "MAP", 32, 92, 104); dwgfx.drawmobilebutton(game, 103, 220, 56, dwgfx.b_size, "CREW", 65, 185, 207); dwgfx.drawmobilebutton(game, 185-4, 220, 56, dwgfx.b_size, "STATS", 32, 92, 104); dwgfx.drawmobilebutton(game, 258, 220, 56, dwgfx.b_size, "QUIT", 32, 92, 104); + }else { + dwgfx.print(30, 220, "MAP", 64,64,64); + dwgfx.print(103-8, 220, "[CREW]", 196, 196, 255 - help.glow); + dwgfx.print(185-4, 220, "STATS", 64,64,64); + dwgfx.print(258, 220, "SAVE", 64, 64, 64); } if (dwgfx.flipmode) { @@ -1731,20 +1740,20 @@ public function maprender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma } break; case 2: - dwgfx.print(30, 220, "MAP", 64,64,64); - if (game.insecretlab) { dwgfx.print(103, 220, "GRAV", 64, 64, 64); - }else if (obj.flags[67] == 1 && !map.custommode) { dwgfx.print(103, 220, "SHIP", 64,64,64); - }else{ dwgfx.print(103, 220, "CREW", 64,64,64);} - dwgfx.print(185-12, 220, "[STATS]", 196, 196, 255 - help.glow); - dwgfx.print(258, 220, "SAVE", 64, 64, 64); - - if (game.mobilemenu) { + if (game.mobilemenu) { dwgfx.drawmobilebutton(game, 30 - 8, 220, 56, dwgfx.b_size, "MAP", 32, 92, 104); if (game.insecretlab) { dwgfx.drawmobilebutton(game, 103, 220, 56, dwgfx.b_size, "GRAV", 32, 92, 104); }else if (obj.flags[67] == 1 && !map.custommode) { dwgfx.drawmobilebutton(game, 103, 220, 56, dwgfx.b_size, "SHIP", 32, 92, 104); }else{ dwgfx.drawmobilebutton(game, 103, 220, 56, dwgfx.b_size, "CREW", 32, 92, 104);} dwgfx.drawmobilebutton(game, 185-4, 220, 56, dwgfx.b_size, "STATS", 65, 185, 207); dwgfx.drawmobilebutton(game, 258, 220, 56, dwgfx.b_size, "QUIT", 32, 92, 104); + }else { + dwgfx.print(30, 220, "MAP", 64,64,64); + if (game.insecretlab) { dwgfx.print(103, 220, "GRAV", 64, 64, 64); + }else if (obj.flags[67] == 1 && !map.custommode) { dwgfx.print(103, 220, "SHIP", 64,64,64); + }else{ dwgfx.print(103, 220, "CREW", 64,64,64);} + dwgfx.print(185-12, 220, "[STATS]", 196, 196, 255 - help.glow); + dwgfx.print(258, 220, "SAVE", 64, 64, 64); } if (map.custommode) { @@ -1790,13 +1799,6 @@ public function maprender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma } break; case 3: - dwgfx.print(30, 220, "MAP", 64,64,64); - if (game.insecretlab) { dwgfx.print(103, 220, "GRAV", 64, 64, 64); - }else if (obj.flags[67] == 1 && !map.custommode) { dwgfx.print(103, 220, "SHIP", 64,64,64); - }else{ dwgfx.print(103, 220, "CREW", 64,64,64);} - dwgfx.print(185-4, 220, "STATS", 64,64,64); - dwgfx.print(258 - 8, 220, "[SAVE]", 196, 196, 255 - help.glow); - if (game.mobilemenu) { dwgfx.drawmobilebutton(game, 30 - 8, 220, 56, dwgfx.b_size, "MAP", 32, 92, 104); if (game.insecretlab) { dwgfx.drawmobilebutton(game, 103, 220, 56, dwgfx.b_size, "GRAV", 32, 92, 104); @@ -1804,6 +1806,13 @@ public function maprender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma }else{ dwgfx.drawmobilebutton(game, 103, 220, 56, dwgfx.b_size, "CREW", 32, 92, 104);} dwgfx.drawmobilebutton(game, 185-4, 220, 56, dwgfx.b_size, "STATS", 32, 92, 104); dwgfx.drawmobilebutton(game, 258, 220, 56, dwgfx.b_size, "QUIT", 65, 185, 207); + }else { + dwgfx.print(30, 220, "MAP", 64,64,64); + if (game.insecretlab) { dwgfx.print(103, 220, "GRAV", 64, 64, 64); + }else if (obj.flags[67] == 1 && !map.custommode) { dwgfx.print(103, 220, "SHIP", 64,64,64); + }else{ dwgfx.print(103, 220, "CREW", 64,64,64);} + dwgfx.print(185-4, 220, "STATS", 64,64,64); + dwgfx.print(258 - 8, 220, "[SAVE]", 196, 196, 255 - help.glow); } dwgfx.print(0, 80, "Quit to main menu?", 255 - (help.glow * 2), 255 - (help.glow * 2), 255 - help.glow, true); @@ -2140,21 +2149,20 @@ public function maprender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, ma } } - dwgfx.backbuffer.unlock(); + //dwgfx.backbuffer.unlock(); } public function towerrender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, map:mapclass, obj:entityclass, help:helpclass):void { - dwgfx.drawbutton(game, help); //Couple of changes for the towermode - dwgfx.backbuffer.lock(); - dwgfx.backbuffer.fillRect(dwgfx.backbuffer.rect, 0x000000); + //dwgfx.backbuffer.lock(); + dwgfx.cls(dwgfx.tower_bgdarkcol[map.colstate]); if (!game.colourblindmode) { dwgfx.drawtowerbackground(map); - dwgfx.drawtowermap(map); + dwgfx.drawtowermap(game, map); }else { dwgfx.drawtowermap_nobackground(map); } @@ -2191,12 +2199,12 @@ public function towerrender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, dwgfx.drawgui(help); if (dwgfx.flipmode) { - if (game.advancetext) dwgfx.bprint(5, 228, "- Tap screen to advance text -", 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2), true); + if (game.advancetext) dwgfx.print(5, 228, "- Tap screen to advance text -", 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2), true); }else{ - if (game.advancetext) dwgfx.bprint(5, 5, "- Tap screen to advance text -", 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2), true); + if (game.advancetext) dwgfx.print(5, 5, "- Tap screen to advance text -", 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2), true); } - dwgfx.backbuffer.fillRect(dwgfx.footerrect, 0x000000); + dwgfx.drawfillrect(dwgfx.footerrect.x, dwgfx.footerrect.y, dwgfx.footerrect.width, dwgfx.footerrect.height, 0); dwgfx.print(5, 231, map.roomname, 196, 196, 255 - help.glow, true); //dwgfx.rprint(5, 231, String(game.coins), 255 - help.glow/2, 255 - help.glow/2, 196, true); @@ -2259,17 +2267,18 @@ public function towerrender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, } dwgfx.render(); - dwgfx.backbuffer.unlock(); + //dwgfx.backbuffer.unlock(); } public function teleporterrender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, map:mapclass, obj:entityclass, help:helpclass):void { dwgfx.drawbutton(game, help); - dwgfx.backbuffer.lock(); + //dwgfx.backbuffer.lock(); //draw screen alliteration //Roomname: + dwgfx.drawfillrect(0, 0, 320, 12, 0, 0, 0); temp = map.area(game.roomx, game.roomy); if (temp < 2 && !map.custommode && dwgfx.fademode==0) { if (game.roomx >= 102 && game.roomx <= 104 && game.roomy >= 110 && game.roomy <= 111) { @@ -2364,9 +2373,9 @@ public function teleporterrender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gamecl dwgfx.drawgui(help); if (dwgfx.flipmode) { - if (game.advancetext) dwgfx.bprint(5, 228, "- Tap screen to advance text -", 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2), true); + if (game.advancetext) dwgfx.print(5, 228, "- Tap screen to advance text -", 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2), true); }else{ - if (game.advancetext) dwgfx.bprint(5, 5, "- Tap screen to advance text -", 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2), true); + if (game.advancetext) dwgfx.print(5, 5, "- Tap screen to advance text -", 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2), true); } @@ -2410,14 +2419,14 @@ public function teleporterrender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gamecl } } - dwgfx.backbuffer.unlock(); + //dwgfx.backbuffer.unlock(); } public function controltutorialrender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, map:mapclass, obj:entityclass, help:helpclass):void { dwgfx.drawbutton(game, help); - dwgfx.backbuffer.lock(); + //dwgfx.backbuffer.lock(); //Background color dwgfx.drawfillrect(0, 0, 320, 240, 10, 24, 26); @@ -2444,12 +2453,12 @@ public function controltutorialrender(key:KeyPoll, dwgfx:dwgraphicsclass, game:g dwgfx.print(5, 10, "-= TOUCHSCREEN CONTROLS =-", 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2), true); if(game.controltutorialstate>=3 &&game.controltutorialstate<=6){ - dwgfx.print(5, 195, "Swipe and hold on the left side", 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2), true); - dwgfx.print(5, 205, "of the screen to move", 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2), true); + dwgfx.print(5, 195 + 8, "Swipe and hold on the left side", 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2), true); + dwgfx.print(5, 205 + 8, "of the screen to move", 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2), true); } if(game.controltutorialstate>=7 &&game.controltutorialstate<=11){ - dwgfx.print(5, 200, "Tap on the right to flip", 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2), true); + dwgfx.print(5, 200 + 8, "Tap on the right to flip", 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2), true); } if (game.controltutorialstate >= 13) { @@ -2520,5 +2529,5 @@ public function controltutorialrender(key:KeyPoll, dwgfx:dwgraphicsclass, game:g dwgfx.render(); } - dwgfx.backbuffer.unlock(); -} + //dwgfx.backbuffer.unlock(); +} \ No newline at end of file diff --git a/mobile_version/src/includes/scripts.as b/mobile_version/src/includes/scripts.as index 1488233f..7e3e555b 100644 --- a/mobile_version/src/includes/scripts.as +++ b/mobile_version/src/includes/scripts.as @@ -4710,7 +4710,7 @@ add("squeak(blue)"); add("text(blue,0,0,3)"); - add("This lab is amazing! The scientists"); + add("This lab is amazing! The scentists"); add("who worked here know a lot more"); add("about warp technology than we do!"); add("position(blue,below)"); @@ -5819,4 +5819,4 @@ } running = true; -} +} \ No newline at end of file diff --git a/mobile_version/src/includes/terminalscripts.as b/mobile_version/src/includes/terminalscripts.as index bf13cefb..cd07a551 100644 --- a/mobile_version/src/includes/terminalscripts.as +++ b/mobile_version/src/includes/terminalscripts.as @@ -674,7 +674,7 @@ add(" NEXT UNLOCK: "); add(" 5 Trinkets"); add(""); - add(" Pushing Onwards "); + add(" Pushing onwards "); add("position(center)"); add("speak_active"); add("endtext"); @@ -687,7 +687,7 @@ add(" NEXT UNLOCK: "); add(" 8 Trinkets"); add(""); - add(" Positive Force "); + add(" Positive force "); add("position(center)"); add("speak_active"); add("endtext"); @@ -713,7 +713,7 @@ add(" NEXT UNLOCK: "); add(" 12 Trinkets"); add(""); - add(" Potential for Anything "); + add(" Potential for anything "); add("position(center)"); add("speak_active"); add("endtext"); @@ -739,7 +739,7 @@ add(" NEXT UNLOCK: "); add(" 16 Trinkets"); add(""); - add(" Predestined Fate "); + add(" Predestined fate "); add("position(center)"); add("speak_active"); add("endtext"); @@ -813,4 +813,4 @@ add("squeak(terminal)"); add("jukebox(10)"); } -} +} \ No newline at end of file diff --git a/mobile_version/src/mapclass.as b/mobile_version/src/mapclass.as index 3ee1ddfd..692ce9ef 100644 --- a/mobile_version/src/mapclass.as +++ b/mobile_version/src/mapclass.as @@ -1,10 +1,9 @@ -package { - import flash.display.*; +package { import flash.geom.*; import flash.events.*; import flash.net.*; - public class mapclass extends Sprite { + public class mapclass { public function mapclass():void { //Start here! r = 196; g = 196; b = 196; @@ -612,6 +611,8 @@ game.activetele = false; game.readytotele = 0; game.mobilequicksave_thisroom = false; + dwgfx.forcescreenupdates(); + obj.opt_useblock = false; obj.opt_usetrigger = false; obj.opt_usedamage = false; @@ -790,7 +791,7 @@ case 1: return "Dimension VVVVVV"; break; case 2: return "Laboratory"; break; case 3: return "The Tower"; break; - case 4: return "Warp Zone"; break; + case 4: return "Warpzone"; break; case 5: return "Space Station"; break; case 6: return "Outside Dimension VVVVVV"; break; case 7: return "Outside Dimension VVVVVV"; break; @@ -892,7 +893,7 @@ } } - if (rx == 119 && ry == 108 && !custommode) { + if (rx == 119 && ry == 108) { background = 5; dwgfx.rcol = 3; warpx = true; @@ -1608,4 +1609,4 @@ obj.createentity(game, 72, 156, 11, 200); // (horizontal gravity line) public var spikecount:int; } -} +} \ No newline at end of file diff --git a/mobile_version/src/musicclass.as b/mobile_version/src/musicclass.as index 254cd2c1..950e4aa8 100644 --- a/mobile_version/src/musicclass.as +++ b/mobile_version/src/musicclass.as @@ -1,9 +1,8 @@ -package { - import flash.display.*; +package { import flash.media.*; import flash.events.*; - public class musicclass extends Sprite { + public class musicclass { //For Music stuff public function play(t:int):void { if (currentsong !=t) { diff --git a/mobile_version/src/platformclass.as b/mobile_version/src/platformclass.as index fc5e0d69..06e2d7b5 100644 --- a/mobile_version/src/platformclass.as +++ b/mobile_version/src/platformclass.as @@ -2,13 +2,10 @@ package { //import com.sticksports.nativeExtensions.SilentSwitch; import flash.events.*; - import flash.media.SoundMixer; - import flash.media.AudioPlaybackMode; public class platformclass { public function init():void { //SilentSwitch.apply(); - SoundMixer.audioPlaybackMode = AudioPlaybackMode.AMBIENT; } public function callonwake():void { diff --git a/mobile_version/src/saveclass.as b/mobile_version/src/saveclass.as index 7868839d..164fc397 100644 --- a/mobile_version/src/saveclass.as +++ b/mobile_version/src/saveclass.as @@ -1,10 +1,9 @@ -package { - import flash.display.*; +package { import flash.geom.*; import flash.events.*; import flash.net.*; - public class saveclass extends Sprite { + public class saveclass { public function saveclass():void { } diff --git a/mobile_version/src/scoreclass.as b/mobile_version/src/scoreclass.as index 076c5a75..bad5cc6c 100644 --- a/mobile_version/src/scoreclass.as +++ b/mobile_version/src/scoreclass.as @@ -1,10 +1,11 @@ package { - import com.milkmangames.nativeextensions.ios.*; - import com.milkmangames.nativeextensions.ios.events.*; + //import com.milkmangames.nativeextensions.ios.*; + //import com.milkmangames.nativeextensions.ios.events.*; import flash.display.Stage; public class scoreclass { public function init(mystage:Stage):void { + /* if (!GameCenter.isSupported()){ trace("GameCenter is not supported on this platform."); } @@ -32,41 +33,51 @@ package { gameCenter.addEventListener(GameCenterErrorEvent.ACHIEVEMENT_REPORT_FAILED,onAchievementFailed); gameCenter.addEventListener(GameCenterErrorEvent.ACHIEVEMENT_RESET_FAILED,onResetFailed); GameCenter.gameCenter.authenticateLocalUser(); + */ } CONFIG::iphonemode { /** Check Authentication */ - private function checkAuthentication():Boolean{ + private function checkAuthentication():Boolean { + /* if (!GameCenter.gameCenter.isUserAuthenticated()){ trace("not logged in!"); return false; } + */ return true; } /** Reset Achievements */ - public function resetAchievements():void{ + public function resetAchievements():void { + /* if (!checkAuthentication()) return; GameCenter.gameCenter.resetAchievements(); + */ } public function reportScore(t:int):void{ // we make sure you're logged in before bothering to report the score. // later iOS versions may take care of waiting/resubmitting for you, but earlier ones won't. + /* if (!checkAuthentication()) return; t = t * 2; //Score is in 30 frame increments GameCenter.gameCenter.reportScoreForCategory(t, "grp.supgravleaderboard"); + */ } public function opengamecenter():void { + /* if (!checkAuthentication()) return; GameCenter.gameCenter.showLeaderboardForCategory("grp.supgravleaderboard"); + */ } /** Show Achievements */ - public function showAchievements():void{ + public function showAchievements():void { + /* if (!checkAuthentication()) return; //trace("showing achievements..."); @@ -75,6 +86,7 @@ package { }catch (e:Error){ //trace("ERR showachievements:"+e.message+"/"+e.name+"/"+e.errorID); } + */ } public var vvvvvvgamecomplete:int = 0; @@ -97,7 +109,8 @@ package { public var vvvvvvsupgrav60:int = 17; public var vvvvvvmaster:int = 18; - public function reportAchievement(t:int):void{ + public function reportAchievement(t:int):void { + /* if (!checkAuthentication()) return; // the '1.0' is a float (Number) value from 0.0-100.0 the percent completion of the achievement. @@ -122,11 +135,13 @@ package { case 17: GameCenter.gameCenter.reportAchievement("grp.vvvvvvsupgrav60", 100.0); break; case 18: GameCenter.gameCenter.reportAchievement("grp.vvvvvvmaster", 100.0); break; } + */ } // // Events // + /* private function onAuthSucceeded(e:GameCenterEvent):void { trace("Auth succeeded!"); @@ -176,10 +191,11 @@ package { { trace("failed to reset:"+e.message); } + */ } CONFIG::iphonemode { - public var gameCenter:GameCenter; + //public var gameCenter:GameCenter; public var gamecenteron:Boolean = true; } } diff --git a/mobile_version/src/scriptclass.as b/mobile_version/src/scriptclass.as index b1d12d8e..9d511365 100644 --- a/mobile_version/src/scriptclass.as +++ b/mobile_version/src/scriptclass.as @@ -1,13 +1,12 @@ -package { +package { import flash.ui.Keyboard; - import flash.display.*; import flash.geom.*; import flash.events.*; import flash.net.*; import bigroom.input.KeyPoll; import flash.system.fscommand; - public class scriptclass extends Sprite { + public class scriptclass { public var GAMEMODE:int = 0; public var TITLEMODE:int = 1; public var CLICKTOSTART:int = 2; @@ -772,7 +771,7 @@ game.gamestate = 5; dwgfx.menuoffset = 240; //actually this should count the roomname if (map.extrarow) dwgfx.menuoffset -= 10; - dwgfx.menubuffer.copyPixels(dwgfx.screenbuffer, dwgfx.screenbuffer.rect, dwgfx.tl, null, null, false); + dwgfx.setup_menubuffer(); dwgfx.resumegamemode = false; game.useteleporter = false; //good heavens don't actually use it @@ -1063,7 +1062,7 @@ }else if (words[0] == "foundlab2") { dwgfx.textboxremovefast(); - dwgfx.createtextbox("The secret lab is separate from", 50, 85, 174, 174, 174); + dwgfx.createtextbox("The secret lab is seperate from", 50, 85, 174, 174, 174); dwgfx.addline("the rest of the game. You can"); dwgfx.addline("now come back here at any time"); dwgfx.addline("by selecting the new SECRET LAB"); diff --git a/mobile_version/src/starling/animation/DelayedCall.as b/mobile_version/src/starling/animation/DelayedCall.as new file mode 100644 index 00000000..46e6e673 --- /dev/null +++ b/mobile_version/src/starling/animation/DelayedCall.as @@ -0,0 +1,140 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.animation +{ + import starling.core.starling_internal; + import starling.events.Event; + import starling.events.EventDispatcher; + + /** A DelayedCall allows you to execute a method after a certain time has passed. Since it + * implements the IAnimatable interface, it can be added to a juggler. In most cases, you + * do not have to use this class directly; the juggler class contains a method to delay + * calls directly. + * + *

DelayedCall dispatches an Event of type 'Event.REMOVE_FROM_JUGGLER' when it is finished, + * so that the juggler automatically removes it when its no longer needed.

+ * + * @see Juggler + */ + public class DelayedCall extends EventDispatcher implements IAnimatable + { + private var _currentTime:Number; + private var _totalTime:Number; + private var _callback:Function; + private var _args:Array; + private var _repeatCount:int; + + /** Creates a delayed call. */ + public function DelayedCall(callback:Function, delay:Number, args:Array=null) + { + reset(callback, delay, args); + } + + /** Resets the delayed call to its default values, which is useful for pooling. */ + public function reset(callback:Function, delay:Number, args:Array=null):DelayedCall + { + _currentTime = 0; + _totalTime = Math.max(delay, 0.0001); + _callback = callback; + _args = args; + _repeatCount = 1; + + return this; + } + + /** @inheritDoc */ + public function advanceTime(time:Number):void + { + var previousTime:Number = _currentTime; + _currentTime += time; + + if (_currentTime > _totalTime) + _currentTime = _totalTime; + + if (previousTime < _totalTime && _currentTime >= _totalTime) + { + if (_repeatCount == 0 || _repeatCount > 1) + { + _callback.apply(null, _args); + + if (_repeatCount > 0) _repeatCount -= 1; + _currentTime = 0; + advanceTime((previousTime + time) - _totalTime); + } + else + { + // save call & args: they might be changed through an event listener + var call:Function = _callback; + var args:Array = _args; + + // in the callback, people might want to call "reset" and re-add it to the + // juggler; so this event has to be dispatched *before* executing 'call'. + dispatchEventWith(Event.REMOVE_FROM_JUGGLER); + call.apply(null, args); + } + } + } + + /** Advances the delayed call so that it is executed right away. If 'repeatCount' is + * anything else than '1', this method will complete only the current iteration. */ + public function complete():void + { + var restTime:Number = _totalTime - _currentTime; + if (restTime > 0) advanceTime(restTime); + } + + /** Indicates if enough time has passed, and the call has already been executed. */ + public function get isComplete():Boolean + { + return _repeatCount == 1 && _currentTime >= _totalTime; + } + + /** The time for which calls will be delayed (in seconds). */ + public function get totalTime():Number { return _totalTime; } + + /** The time that has already passed (in seconds). */ + public function get currentTime():Number { return _currentTime; } + + /** The number of times the call will be repeated. + * Set to '0' to repeat indefinitely. @default 1 */ + public function get repeatCount():int { return _repeatCount; } + public function set repeatCount(value:int):void { _repeatCount = value; } + + /** The callback that will be executed when the time is up. */ + public function get callback():Function { return _callback; } + + /** The arguments that the callback will be executed with. + * Beware: not a copy, but the actual object! */ + public function get arguments():Array { return _args; } + + // delayed call pooling + + private static var sPool:Vector. = new []; + + /** @private */ + starling_internal static function fromPool(call:Function, delay:Number, + args:Array=null):DelayedCall + { + if (sPool.length) return sPool.pop().reset(call, delay, args); + else return new DelayedCall(call, delay, args); + } + + /** @private */ + starling_internal static function toPool(delayedCall:DelayedCall):void + { + // reset any object-references, to make sure we don't prevent any garbage collection + delayedCall._callback = null; + delayedCall._args = null; + delayedCall.removeEventListeners(); + sPool.push(delayedCall); + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/animation/IAnimatable.as b/mobile_version/src/starling/animation/IAnimatable.as new file mode 100644 index 00000000..d954426f --- /dev/null +++ b/mobile_version/src/starling/animation/IAnimatable.as @@ -0,0 +1,30 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.animation +{ + /** The IAnimatable interface describes objects that are animated depending on the passed time. + * Any object that implements this interface can be added to a juggler. + * + *

When an object should no longer be animated, it has to be removed from the juggler. + * To do this, you can manually remove it via the method juggler.remove(object), + * or the object can request to be removed by dispatching a Starling event with the type + * Event.REMOVE_FROM_JUGGLER. The "Tween" class is an example of a class that + * dispatches such an event; you don't have to remove tweens manually from the juggler.

+ * + * @see Juggler + * @see Tween + */ + public interface IAnimatable + { + /** Advance the time by a number of seconds. @param time in seconds. */ + function advanceTime(time:Number):void; + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/animation/Juggler.as b/mobile_version/src/starling/animation/Juggler.as new file mode 100644 index 00000000..810a7e82 --- /dev/null +++ b/mobile_version/src/starling/animation/Juggler.as @@ -0,0 +1,396 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.animation +{ + import flash.utils.Dictionary; + + import starling.core.starling_internal; + import starling.events.Event; + import starling.events.EventDispatcher; + + /** The Juggler takes objects that implement IAnimatable (like Tweens) and executes them. + * + *

A juggler is a simple object. It does no more than saving a list of objects implementing + * "IAnimatable" and advancing their time if it 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 available at the Starling class:

+ * + *
+     *  var juggler:Juggler = Starling.juggler;
+     *  
+ * + *

You can create juggler objects yourself, just as well. That way, you can group + * your game into logical components that handle their animations independently. All you have + * to do is call the "advanceTime" method on your custom juggler once per frame.

+ * + *

Another handy feature of the juggler is the "delayCall"-method. Use it to + * execute a function at a later time. Different to conventional approaches, the method + * will only be called when the juggler is advanced, giving you perfect control over the + * call.

+ * + *
+     *  juggler.delayCall(object.removeFromParent, 1.0);
+     *  juggler.delayCall(object.addChild, 2.0, theChild);
+     *  juggler.delayCall(function():void { rotation += 0.1; }, 3.0);
+     *  
+ * + * @see Tween + * @see DelayedCall + */ + public class Juggler implements IAnimatable + { + private var _objects:Vector.; + private var _objectIDs:Dictionary; + private var _elapsedTime:Number; + private var _timeScale:Number; + + private static var sCurrentObjectID:uint; + + /** Create an empty juggler. */ + public function Juggler() + { + _elapsedTime = 0; + _timeScale = 1.0; + _objects = new []; + _objectIDs = new Dictionary(true); + } + + /** Adds an object to the juggler. + * + * @return Unique numeric identifier for the animation. This identifier may be used + * to remove the object via removeByID(). + */ + public function add(object:IAnimatable):uint + { + return addWithID(object, getNextID()); + } + + private function addWithID(object:IAnimatable, objectID:uint):uint + { + if (object && !(object in _objectIDs)) + { + var dispatcher:EventDispatcher = object as EventDispatcher; + if (dispatcher) dispatcher.addEventListener(Event.REMOVE_FROM_JUGGLER, onRemove); + + _objects[_objects.length] = object; + _objectIDs[object] = objectID; + + return objectID; + } + else return 0; + } + + /** Determines if an object has been added to the juggler. */ + public function contains(object:IAnimatable):Boolean + { + return object in _objectIDs; + } + + /** Removes an object from the juggler. + * + * @return The (now meaningless) unique numeric identifier for the animation, or zero + * if the object was not found. + */ + public function remove(object:IAnimatable):uint + { + var objectID:uint = 0; + + if (object && object in _objectIDs) + { + var dispatcher:EventDispatcher = object as EventDispatcher; + if (dispatcher) dispatcher.removeEventListener(Event.REMOVE_FROM_JUGGLER, onRemove); + + var index:int = _objects.indexOf(object); + _objects[index] = null; + + objectID = _objectIDs[object]; + delete _objectIDs[object]; + } + + return objectID; + } + + /** Removes an object from the juggler, identified by the unique numeric identifier you + * received when adding it. + * + *

It's not uncommon that an animatable object is added to a juggler repeatedly, + * e.g. when using an object-pool. Thus, when using the remove method, + * you might accidentally remove an object that has changed its context. By using + * removeByID instead, you can be sure to avoid that, since the objectID + * will always be unique.

+ * + * @return if successful, the passed objectID; if the object was not found, zero. + */ + public function removeByID(objectID:uint):uint + { + for (var i:int=_objects.length-1; i>=0; --i) + { + var object:IAnimatable = _objects[i]; + + if (_objectIDs[object] == objectID) + { + remove(object); + return objectID; + } + } + + return 0; + } + + /** Removes all tweens with a certain target. */ + public function removeTweens(target:Object):void + { + if (target == null) return; + + for (var i:int=_objects.length-1; i>=0; --i) + { + var tween:Tween = _objects[i] as Tween; + if (tween && tween.target == target) + { + tween.removeEventListener(Event.REMOVE_FROM_JUGGLER, onRemove); + _objects[i] = null; + delete _objectIDs[tween]; + } + } + } + + /** Removes all delayed and repeated calls with a certain callback. */ + public function removeDelayedCalls(callback:Function):void + { + if (callback == null) return; + + for (var i:int=_objects.length-1; i>=0; --i) + { + var delayedCall:DelayedCall = _objects[i] as DelayedCall; + if (delayedCall && delayedCall.callback == callback) + { + delayedCall.removeEventListener(Event.REMOVE_FROM_JUGGLER, onRemove); + _objects[i] = null; + delete _objectIDs[tween]; + } + } + } + + /** Figures out if the juggler contains one or more tweens with a certain target. */ + public function containsTweens(target:Object):Boolean + { + if (target) + { + for (var i:int=_objects.length-1; i>=0; --i) + { + var tween:Tween = _objects[i] as Tween; + if (tween && tween.target == target) return true; + } + } + + return false; + } + + /** Figures out if the juggler contains one or more delayed calls with a certain callback. */ + public function containsDelayedCalls(callback:Function):Boolean + { + if (callback != null) + { + for (var i:int=_objects.length-1; i>=0; --i) + { + var delayedCall:DelayedCall = _objects[i] as DelayedCall; + if (delayedCall && delayedCall.callback == callback) return true; + } + } + + return false; + } + + /** Removes all objects at once. */ + public function purge():void + { + // the object vector is not purged right away, because if this method is called + // from an 'advanceTime' call, this would make the loop crash. Instead, the + // vector is filled with 'null' values. They will be cleaned up on the next call + // to 'advanceTime'. + + for (var i:int=_objects.length-1; i>=0; --i) + { + var object:IAnimatable = _objects[i]; + var dispatcher:EventDispatcher = object as EventDispatcher; + if (dispatcher) dispatcher.removeEventListener(Event.REMOVE_FROM_JUGGLER, onRemove); + _objects[i] = null; + delete _objectIDs[object]; + } + } + + /** Delays the execution of a function until delay seconds have passed. + * This method provides a convenient alternative for creating and adding a DelayedCall + * manually. + * + * @return Unique numeric identifier for the delayed call. This identifier may be used + * to remove the object via removeByID(). + */ + public function delayCall(call:Function, delay:Number, ...args):uint + { + if (call == null) throw new ArgumentError("call must not be null"); + + var delayedCall:DelayedCall = DelayedCall.starling_internal::fromPool(call, delay, args); + delayedCall.addEventListener(Event.REMOVE_FROM_JUGGLER, onPooledDelayedCallComplete); + return add(delayedCall); + } + + /** Runs a function at a specified interval (in seconds). A 'repeatCount' of zero + * means that it runs indefinitely. + * + * @return Unique numeric identifier for the delayed call. This identifier may be used + * to remove the object via removeByID(). + */ + public function repeatCall(call:Function, interval:Number, repeatCount:int=0, ...args):uint + { + if (call == null) throw new ArgumentError("call must not be null"); + + var delayedCall:DelayedCall = DelayedCall.starling_internal::fromPool(call, interval, args); + delayedCall.repeatCount = repeatCount; + delayedCall.addEventListener(Event.REMOVE_FROM_JUGGLER, onPooledDelayedCallComplete); + return add(delayedCall); + } + + private function onPooledDelayedCallComplete(event:Event):void + { + DelayedCall.starling_internal::toPool(event.target as DelayedCall); + } + + /** Utilizes a tween to animate the target object over time seconds. Internally, + * this method uses a tween instance (taken from an object pool) that is added to the + * juggler right away. This method provides a convenient alternative for creating + * and adding a tween manually. + * + *

Fill 'properties' with key-value pairs that describe both the + * tween and the animation target. Here is an example:

+ * + *
+         *  juggler.tween(object, 2.0, {
+         *      transition: Transitions.EASE_IN_OUT,
+         *      delay: 20, // -> tween.delay = 20
+         *      x: 50      // -> tween.animate("x", 50)
+         *  });
+         *  
+ * + *

To cancel the tween, call 'Juggler.removeTweens' with the same target, or pass + * the returned 'IAnimatable' instance to 'Juggler.remove()'. Do not use the returned + * IAnimatable otherwise; it is taken from a pool and will be reused.

+ * + *

Note that some property types may be animated in a special way:

+ *
    + *
  • If the property contains the string color or Color, + * it will be treated as an unsigned integer with a color value + * (e.g. 0xff0000 for red). Each color channel will be animated + * individually.
  • + *
  • The same happens if you append the string #rgb to the name.
  • + *
  • If you append #rad, the property is treated as an angle in radians, + * making sure it always uses the shortest possible arc for the rotation.
  • + *
  • The string #deg does the same for angles in degrees.
  • + *
+ */ + public function tween(target:Object, time:Number, properties:Object):uint + { + if (target == null) throw new ArgumentError("target must not be null"); + + var tween:Tween = Tween.starling_internal::fromPool(target, time); + + for (var property:String in properties) + { + var value:Object = properties[property]; + + if (tween.hasOwnProperty(property)) + tween[property] = value; + else if (target.hasOwnProperty(Tween.getPropertyName(property))) + tween.animate(property, value as Number); + else + throw new ArgumentError("Invalid property: " + property); + } + + tween.addEventListener(Event.REMOVE_FROM_JUGGLER, onPooledTweenComplete); + return add(tween); + } + + private function onPooledTweenComplete(event:Event):void + { + Tween.starling_internal::toPool(event.target as Tween); + } + + /** Advances all objects by a certain time (in seconds). */ + public function advanceTime(time:Number):void + { + var numObjects:int = _objects.length; + var currentIndex:int = 0; + var i:int; + + time *= _timeScale; + if (numObjects == 0 || time == 0) return; + _elapsedTime += time; + + // there is a high probability that the "advanceTime" function modifies the list + // of animatables. we must not process new objects right now (they will be processed + // in the next frame), and we need to clean up any empty slots in the list. + + for (i=0; i { return _objects; } + } +} diff --git a/mobile_version/src/starling/animation/Transitions.as b/mobile_version/src/starling/animation/Transitions.as new file mode 100644 index 00000000..de708b61 --- /dev/null +++ b/mobile_version/src/starling/animation/Transitions.as @@ -0,0 +1,233 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= +// +// easing functions thankfully taken from http://dojotoolkit.org +// and http://www.robertpenner.com/easing +// + +package starling.animation +{ + import flash.utils.Dictionary; + + import starling.errors.AbstractClassError; + + /** The Transitions class contains static methods that define easing functions. + * Those functions are used by the Tween class to execute animations. + * + *

Here is a visual representation of the available transitions:

+ * + * + *

You can define your own transitions through the "registerTransition" function. A + * transition function must have the following signature, where ratio is + * in the range 0-1:

+ * + *
function myTransition(ratio:Number):Number
+ */ + public class Transitions + { + public static const LINEAR:String = "linear"; + public static const EASE_IN:String = "easeIn"; + public static const EASE_OUT:String = "easeOut"; + public static const EASE_IN_OUT:String = "easeInOut"; + public static const EASE_OUT_IN:String = "easeOutIn"; + public static const EASE_IN_BACK:String = "easeInBack"; + public static const EASE_OUT_BACK:String = "easeOutBack"; + public static const EASE_IN_OUT_BACK:String = "easeInOutBack"; + public static const EASE_OUT_IN_BACK:String = "easeOutInBack"; + public static const EASE_IN_ELASTIC:String = "easeInElastic"; + public static const EASE_OUT_ELASTIC:String = "easeOutElastic"; + public static const EASE_IN_OUT_ELASTIC:String = "easeInOutElastic"; + public static const EASE_OUT_IN_ELASTIC:String = "easeOutInElastic"; + public static const EASE_IN_BOUNCE:String = "easeInBounce"; + public static const EASE_OUT_BOUNCE:String = "easeOutBounce"; + public static const EASE_IN_OUT_BOUNCE:String = "easeInOutBounce"; + public static const EASE_OUT_IN_BOUNCE:String = "easeOutInBounce"; + + private static var sTransitions:Dictionary; + + /** @private */ + public function Transitions() { throw new AbstractClassError(); } + + /** Returns the transition function that was registered under a certain name. */ + public static function getTransition(name:String):Function + { + if (sTransitions == null) registerDefaults(); + return sTransitions[name]; + } + + /** Registers a new transition function under a certain name. */ + public static function register(name:String, func:Function):void + { + if (sTransitions == null) registerDefaults(); + sTransitions[name] = func; + } + + private static function registerDefaults():void + { + sTransitions = new Dictionary(); + + register(LINEAR, linear); + register(EASE_IN, easeIn); + register(EASE_OUT, easeOut); + register(EASE_IN_OUT, easeInOut); + register(EASE_OUT_IN, easeOutIn); + register(EASE_IN_BACK, easeInBack); + register(EASE_OUT_BACK, easeOutBack); + register(EASE_IN_OUT_BACK, easeInOutBack); + register(EASE_OUT_IN_BACK, easeOutInBack); + register(EASE_IN_ELASTIC, easeInElastic); + register(EASE_OUT_ELASTIC, easeOutElastic); + register(EASE_IN_OUT_ELASTIC, easeInOutElastic); + register(EASE_OUT_IN_ELASTIC, easeOutInElastic); + register(EASE_IN_BOUNCE, easeInBounce); + register(EASE_OUT_BOUNCE, easeOutBounce); + register(EASE_IN_OUT_BOUNCE, easeInOutBounce); + register(EASE_OUT_IN_BOUNCE, easeOutInBounce); + } + + // transition functions + + protected static function linear(ratio:Number):Number + { + return ratio; + } + + protected static function easeIn(ratio:Number):Number + { + return ratio * ratio * ratio; + } + + protected static function easeOut(ratio:Number):Number + { + var invRatio:Number = ratio - 1.0; + return invRatio * invRatio * invRatio + 1; + } + + protected static function easeInOut(ratio:Number):Number + { + return easeCombined(easeIn, easeOut, ratio); + } + + protected static function easeOutIn(ratio:Number):Number + { + return easeCombined(easeOut, easeIn, ratio); + } + + protected static function easeInBack(ratio:Number):Number + { + var s:Number = 1.70158; + return Math.pow(ratio, 2) * ((s + 1.0)*ratio - s); + } + + protected static function easeOutBack(ratio:Number):Number + { + var invRatio:Number = ratio - 1.0; + var s:Number = 1.70158; + return Math.pow(invRatio, 2) * ((s + 1.0)*invRatio + s) + 1.0; + } + + protected static function easeInOutBack(ratio:Number):Number + { + return easeCombined(easeInBack, easeOutBack, ratio); + } + + protected static function easeOutInBack(ratio:Number):Number + { + return easeCombined(easeOutBack, easeInBack, ratio); + } + + protected static function easeInElastic(ratio:Number):Number + { + if (ratio == 0 || ratio == 1) return ratio; + else + { + var p:Number = 0.3; + var s:Number = p/4.0; + var invRatio:Number = ratio - 1; + return -1.0 * Math.pow(2.0, 10.0*invRatio) * Math.sin((invRatio-s)*(2.0*Math.PI)/p); + } + } + + protected static function easeOutElastic(ratio:Number):Number + { + if (ratio == 0 || ratio == 1) return ratio; + else + { + var p:Number = 0.3; + var s:Number = p/4.0; + return Math.pow(2.0, -10.0*ratio) * Math.sin((ratio-s)*(2.0*Math.PI)/p) + 1; + } + } + + protected static function easeInOutElastic(ratio:Number):Number + { + return easeCombined(easeInElastic, easeOutElastic, ratio); + } + + protected static function easeOutInElastic(ratio:Number):Number + { + return easeCombined(easeOutElastic, easeInElastic, ratio); + } + + protected static function easeInBounce(ratio:Number):Number + { + return 1.0 - easeOutBounce(1.0 - ratio); + } + + protected static function easeOutBounce(ratio:Number):Number + { + var s:Number = 7.5625; + var p:Number = 2.75; + var l:Number; + if (ratio < (1.0/p)) + { + l = s * Math.pow(ratio, 2); + } + else + { + if (ratio < (2.0/p)) + { + ratio -= 1.5/p; + l = s * Math.pow(ratio, 2) + 0.75; + } + else + { + if (ratio < 2.5/p) + { + ratio -= 2.25/p; + l = s * Math.pow(ratio, 2) + 0.9375; + } + else + { + ratio -= 2.625/p; + l = s * Math.pow(ratio, 2) + 0.984375; + } + } + } + return l; + } + + protected static function easeInOutBounce(ratio:Number):Number + { + return easeCombined(easeInBounce, easeOutBounce, ratio); + } + + protected static function easeOutInBounce(ratio:Number):Number + { + return easeCombined(easeOutBounce, easeInBounce, ratio); + } + + protected static function easeCombined(startFunc:Function, endFunc:Function, ratio:Number):Number + { + if (ratio < 0.5) return 0.5 * startFunc(ratio*2.0); + else return 0.5 * endFunc((ratio-0.5)*2.0) + 0.5; + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/animation/Tween.as b/mobile_version/src/starling/animation/Tween.as new file mode 100644 index 00000000..561bd944 --- /dev/null +++ b/mobile_version/src/starling/animation/Tween.as @@ -0,0 +1,451 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + + +package starling.animation +{ + import starling.core.starling_internal; + import starling.events.Event; + import starling.events.EventDispatcher; + import starling.utils.Color; + + /** A Tween 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, uint, Number), the tween can handle it. For a list + * of available Transition types, look at the "Transitions" class.

+ * + *

Here is an example of a tween that moves an object to the right, rotates it, and + * fades it out:

+ * + * + * var tween:Tween = new Tween(object, 2.0, Transitions.EASE_IN_OUT); + * tween.animate("x", object.x + 50); + * tween.animate("rotation", deg2rad(45)); + * tween.fadeTo(0); // equivalent to 'animate("alpha", 0)' + * Starling.juggler.add(tween); + * + *

Note that the object is added to a juggler at the end of this sample. That's because a + * tween will only be executed if its "advanceTime" method is executed regularly - the + * juggler will do that for you, and will remove the tween when it is finished.

+ * + * @see Juggler + * @see Transitions + */ + public class Tween extends EventDispatcher implements IAnimatable + { + private static const HINT_MARKER:String = '#'; + + private var _target:Object; + private var _transitionFunc:Function; + private var _transitionName:String; + + private var _properties:Vector.; + private var _startValues:Vector.; + private var _endValues:Vector.; + private var _updateFuncs:Vector.; + + private var _onStart:Function; + private var _onUpdate:Function; + private var _onRepeat:Function; + private var _onComplete:Function; + + private var _onStartArgs:Array; + private var _onUpdateArgs:Array; + private var _onRepeatArgs:Array; + private var _onCompleteArgs:Array; + + private var _totalTime:Number; + private var _currentTime:Number; + private var _progress:Number; + private var _delay:Number; + private var _roundToInt:Boolean; + private var _nextTween:Tween; + private var _repeatCount:int; + private var _repeatDelay:Number; + private var _reverse:Boolean; + private var _currentCycle:int; + + /** Creates a tween with a target, duration (in seconds) and a transition function. + * @param target the object that you want to animate + * @param time the duration of the Tween (in seconds) + * @param transition can be either a String (e.g. one of the constants defined in the + * Transitions class) or a function. Look up the 'Transitions' class for a + * documentation about the required function signature. */ + public function Tween(target:Object, time:Number, transition:Object="linear") + { + reset(target, time, transition); + } + + /** Resets the tween to its default values. Useful for pooling tweens. */ + public function reset(target:Object, time:Number, transition:Object="linear"):Tween + { + _target = target; + _currentTime = 0.0; + _totalTime = Math.max(0.0001, time); + _progress = 0.0; + _delay = _repeatDelay = 0.0; + _onStart = _onUpdate = _onRepeat = _onComplete = null; + _onStartArgs = _onUpdateArgs = _onRepeatArgs = _onCompleteArgs = null; + _roundToInt = _reverse = false; + _repeatCount = 1; + _currentCycle = -1; + _nextTween = null; + + if (transition is String) + this.transition = transition as String; + else if (transition is Function) + this.transitionFunc = transition as Function; + else + throw new ArgumentError("Transition must be either a string or a function"); + + if (_properties) _properties.length = 0; else _properties = new []; + if (_startValues) _startValues.length = 0; else _startValues = new []; + if (_endValues) _endValues.length = 0; else _endValues = new []; + if (_updateFuncs) _updateFuncs.length = 0; else _updateFuncs = new []; + + return this; + } + + /** Animates the property of the target to a certain value. You can call this method + * multiple times on one tween. + * + *

Some property types are handled in a special way:

+ *
    + *
  • If the property contains the string color or Color, + * it will be treated as an unsigned integer with a color value + * (e.g. 0xff0000 for red). Each color channel will be animated + * individually.
  • + *
  • The same happens if you append the string #rgb to the name.
  • + *
  • If you append #rad, the property is treated as an angle in radians, + * making sure it always uses the shortest possible arc for the rotation.
  • + *
  • The string #deg does the same for angles in degrees.
  • + *
+ */ + public function animate(property:String, endValue:Number):void + { + if (_target == null) return; // tweening null just does nothing. + + var pos:int = _properties.length; + var updateFunc:Function = getUpdateFuncFromProperty(property); + + _properties[pos] = getPropertyName(property); + _startValues[pos] = Number.NaN; + _endValues[pos] = endValue; + _updateFuncs[pos] = updateFunc; + } + + /** Animates the 'scaleX' and 'scaleY' properties of an object simultaneously. */ + public function scaleTo(factor:Number):void + { + animate("scaleX", factor); + animate("scaleY", factor); + } + + /** Animates the 'x' and 'y' properties of an object simultaneously. */ + public function moveTo(x:Number, y:Number):void + { + animate("x", x); + animate("y", y); + } + + /** Animates the 'alpha' property of an object to a certain target value. */ + public function fadeTo(alpha:Number):void + { + animate("alpha", alpha); + } + + /** Animates the 'rotation' property of an object to a certain target value, using the + * smallest possible arc. 'type' may be either 'rad' or 'deg', depending on the unit of + * measurement. */ + public function rotateTo(angle:Number, type:String="rad"):void + { + animate("rotation#" + type, angle); + } + + /** @inheritDoc */ + public function advanceTime(time:Number):void + { + if (time == 0 || (_repeatCount == 1 && _currentTime == _totalTime)) return; + + var i:int; + var previousTime:Number = _currentTime; + var restTime:Number = _totalTime - _currentTime; + var carryOverTime:Number = time > restTime ? time - restTime : 0.0; + + _currentTime += time; + + if (_currentTime <= 0) + return; // the delay is not over yet + else if (_currentTime > _totalTime) + _currentTime = _totalTime; + + if (_currentCycle < 0 && previousTime <= 0 && _currentTime > 0) + { + _currentCycle++; + if (_onStart != null) _onStart.apply(this, _onStartArgs); + } + + var ratio:Number = _currentTime / _totalTime; + var reversed:Boolean = _reverse && (_currentCycle % 2 == 1); + var numProperties:int = _startValues.length; + _progress = reversed ? _transitionFunc(1.0 - ratio) : _transitionFunc(ratio); + + for (i=0; i= _totalTime) + { + if (_repeatCount == 0 || _repeatCount > 1) + { + _currentTime = -_repeatDelay; + _currentCycle++; + if (_repeatCount > 1) _repeatCount--; + if (_onRepeat != null) _onRepeat.apply(this, _onRepeatArgs); + } + else + { + // save callback & args: they might be changed through an event listener + var onComplete:Function = _onComplete; + var onCompleteArgs:Array = _onCompleteArgs; + + // in the 'onComplete' callback, people might want to call "tween.reset" and + // add it to another juggler; so this event has to be dispatched *before* + // executing 'onComplete'. + dispatchEventWith(Event.REMOVE_FROM_JUGGLER); + if (onComplete != null) onComplete.apply(this, onCompleteArgs); + if (_currentTime == 0) carryOverTime = 0; // tween was reset + } + } + + if (carryOverTime) + advanceTime(carryOverTime); + } + + // animation hints + + private function getUpdateFuncFromProperty(property:String):Function + { + var updateFunc:Function; + var hint:String = getPropertyHint(property); + + switch (hint) + { + case null: updateFunc = updateStandard; break; + case "rgb": updateFunc = updateRgb; break; + case "rad": updateFunc = updateRad; break; + case "deg": updateFunc = updateDeg; break; + default: + trace("[Starling] Ignoring unknown property hint:", hint); + updateFunc = updateStandard; + } + + return updateFunc; + } + + /** @private */ + internal static function getPropertyHint(property:String):String + { + // colorization is special; it does not require a hint marker, just the word 'color'. + if (property.indexOf("color") != -1 || property.indexOf("Color") != -1) + return "rgb"; + + var hintMarkerIndex:int = property.indexOf(HINT_MARKER); + if (hintMarkerIndex != -1) return property.substr(hintMarkerIndex+1); + else return null; + } + + /** @private */ + internal static function getPropertyName(property:String):String + { + var hintMarkerIndex:int = property.indexOf(HINT_MARKER); + if (hintMarkerIndex != -1) return property.substring(0, hintMarkerIndex); + else return property; + } + + private function updateStandard(property:String, startValue:Number, endValue:Number):void + { + var newValue:Number = startValue + _progress * (endValue - startValue); + if (_roundToInt) newValue = Math.round(newValue); + _target[property] = newValue; + } + + private function updateRgb(property:String, startValue:Number, endValue:Number):void + { + _target[property] = Color.interpolate(uint(startValue), uint(endValue), _progress); + } + + private function updateRad(property:String, startValue:Number, endValue:Number):void + { + updateAngle(Math.PI, property, startValue, endValue); + } + + private function updateDeg(property:String, startValue:Number, endValue:Number):void + { + updateAngle(180, property, startValue, endValue); + } + + private function updateAngle(pi:Number, property:String, startValue:Number, endValue:Number):void + { + while (Math.abs(endValue - startValue) > pi) + { + if (startValue < endValue) endValue -= 2.0 * pi; + else endValue += 2.0 * pi; + } + + updateStandard(property, startValue, endValue); + } + + /** The end value a certain property is animated to. Throws an ArgumentError if the + * property is not being animated. */ + public function getEndValue(property:String):Number + { + var index:int = _properties.indexOf(property); + if (index == -1) throw new ArgumentError("The property '" + property + "' is not animated"); + else return _endValues[index] as Number; + } + + /** Indicates if the tween is finished. */ + public function get isComplete():Boolean + { + return _currentTime >= _totalTime && _repeatCount == 1; + } + + /** The target object that is animated. */ + public function get target():Object { return _target; } + + /** The transition method used for the animation. @see Transitions */ + public function get transition():String { return _transitionName; } + public function set transition(value:String):void + { + _transitionName = value; + _transitionFunc = Transitions.getTransition(value); + + if (_transitionFunc == null) + throw new ArgumentError("Invalid transiton: " + value); + } + + /** The actual transition function used for the animation. */ + public function get transitionFunc():Function { return _transitionFunc; } + public function set transitionFunc(value:Function):void + { + _transitionName = "custom"; + _transitionFunc = value; + } + + /** The total time the tween will take per repetition (in seconds). */ + public function get totalTime():Number { return _totalTime; } + + /** The time that has passed since the tween was created (in seconds). */ + public function get currentTime():Number { return _currentTime; } + + /** The current progress between 0 and 1, as calculated by the transition function. */ + public function get progress():Number { return _progress; } + + /** The delay before the tween is started (in seconds). @default 0 */ + public function get delay():Number { return _delay; } + public function set delay(value:Number):void + { + _currentTime = _currentTime + _delay - value; + _delay = value; + } + + /** The number of times the tween will be executed. + * Set to '0' to tween indefinitely. @default 1 */ + public function get repeatCount():int { return _repeatCount; } + public function set repeatCount(value:int):void { _repeatCount = value; } + + /** The amount of time to wait between repeat cycles (in seconds). @default 0 */ + public function get repeatDelay():Number { return _repeatDelay; } + public function set repeatDelay(value:Number):void { _repeatDelay = value; } + + /** Indicates if the tween should be reversed when it is repeating. If enabled, + * every second repetition will be reversed. @default false */ + public function get reverse():Boolean { return _reverse; } + public function set reverse(value:Boolean):void { _reverse = value; } + + /** Indicates if the numeric values should be cast to Integers. @default false */ + public function get roundToInt():Boolean { return _roundToInt; } + public function set roundToInt(value:Boolean):void { _roundToInt = value; } + + /** A function that will be called when the tween starts (after a possible delay). */ + public function get onStart():Function { return _onStart; } + public function set onStart(value:Function):void { _onStart = value; } + + /** A function that will be called each time the tween is advanced. */ + public function get onUpdate():Function { return _onUpdate; } + public function set onUpdate(value:Function):void { _onUpdate = value; } + + /** A function that will be called each time the tween finishes one repetition + * (except the last, which will trigger 'onComplete'). */ + public function get onRepeat():Function { return _onRepeat; } + public function set onRepeat(value:Function):void { _onRepeat = value; } + + /** A function that will be called when the tween is complete. */ + public function get onComplete():Function { return _onComplete; } + public function set onComplete(value:Function):void { _onComplete = value; } + + /** The arguments that will be passed to the 'onStart' function. */ + public function get onStartArgs():Array { return _onStartArgs; } + public function set onStartArgs(value:Array):void { _onStartArgs = value; } + + /** The arguments that will be passed to the 'onUpdate' function. */ + public function get onUpdateArgs():Array { return _onUpdateArgs; } + public function set onUpdateArgs(value:Array):void { _onUpdateArgs = value; } + + /** The arguments that will be passed to the 'onRepeat' function. */ + public function get onRepeatArgs():Array { return _onRepeatArgs; } + public function set onRepeatArgs(value:Array):void { _onRepeatArgs = value; } + + /** The arguments that will be passed to the 'onComplete' function. */ + public function get onCompleteArgs():Array { return _onCompleteArgs; } + public function set onCompleteArgs(value:Array):void { _onCompleteArgs = value; } + + /** Another tween that will be started (i.e. added to the same juggler) as soon as + * this tween is completed. */ + public function get nextTween():Tween { return _nextTween; } + public function set nextTween(value:Tween):void { _nextTween = value; } + + // tween pooling + + private static var sTweenPool:Vector. = new []; + + /** @private */ + starling_internal static function fromPool(target:Object, time:Number, + transition:Object="linear"):Tween + { + if (sTweenPool.length) return sTweenPool.pop().reset(target, time, transition); + else return new Tween(target, time, transition); + } + + /** @private */ + starling_internal static function toPool(tween:Tween):void + { + // reset any object-references, to make sure we don't prevent any garbage collection + tween._onStart = tween._onUpdate = tween._onRepeat = tween._onComplete = null; + tween._onStartArgs = tween._onUpdateArgs = tween._onRepeatArgs = tween._onCompleteArgs = null; + tween._target = null; + tween._transitionFunc = null; + tween.removeEventListeners(); + sTweenPool.push(tween); + } + } +} diff --git a/mobile_version/src/starling/assets/AssetFactory.as b/mobile_version/src/starling/assets/AssetFactory.as new file mode 100644 index 00000000..01cfeecc --- /dev/null +++ b/mobile_version/src/starling/assets/AssetFactory.as @@ -0,0 +1,122 @@ +package starling.assets +{ + import flash.system.Capabilities; + import flash.utils.ByteArray; + import flash.utils.getQualifiedClassName; + + import starling.errors.AbstractClassError; + + /** An AssetFactory is responsible for creating a concrete instance of an asset. + * + *

The AssetManager contains a list of AssetFactories, registered via 'registerFactory'. + * When the asset queue is processed, each factory (sorted by priority) will be asked if it + * can handle a certain AssetReference (via the 'canHandle') method. If it can, the 'create' + * method will be called, which is responsible for creating at least one asset.

+ * + *

By extending 'AssetFactory' and registering your class at the AssetManager, you can + * customize how assets are being created and even add new types of assets.

+ */ + public class AssetFactory + { + private var _priority:int; + private var _mimeTypes:Vector.; + private var _extensions:Vector.; + + /** Creates a new instance. */ + public function AssetFactory() + { + if (Capabilities.isDebugger && + getQualifiedClassName(this) == "starling.assets::AssetFactory") + { + throw new AbstractClassError(); + } + + _mimeTypes = new []; + _extensions = new []; + } + + /** Returns 'true' if this factory can handle the given reference. The default + * implementation checks if extension and/or mime type of the reference match those + * of the factory. */ + public function canHandle(reference:AssetReference):Boolean + { + var mimeType:String = reference.mimeType; + var extension:String = reference.extension; + + return reference.data is ByteArray && ( + (mimeType && _mimeTypes.indexOf(reference.mimeType.toLowerCase()) != -1) || + (extension && _extensions.indexOf(reference.extension.toLowerCase()) != -1)); + } + + /** This method will only be called if 'canHandle' returned 'true' for the given reference. + * It's responsible for creating at least one concrete asset and passing it to 'onComplete'. + * + * @param reference The asset to be created. If a local or remote URL is referenced, + * it will already have been loaded, and 'data' will contain a ByteArray. + * @param helper Contains useful utility methods to be used by the factory. Look + * at the class documentation for more information. + * @param onComplete To be called when loading is successful. 'type' parameter is optional. + *
function(name:String, asset:Object, type:String):void;
+ * @param onError To be called when creation fails for some reason. Do not call + * 'onComplete' when that happens.
function(error:String):void
+ */ + public function create(reference:AssetReference, helper:AssetFactoryHelper, + onComplete:Function, onError:Function):void + { + // to be implemented by subclasses + } + + /** Add one or more mime types that identify the supported data types. Used by + * 'canHandle' to figure out if the factory is suitable for an asset reference. */ + public function addMimeTypes(...args):void + { + for each (var mimeType:String in args) + { + mimeType = mimeType.toLowerCase(); + + if (_mimeTypes.indexOf(mimeType) == -1) + _mimeTypes[_mimeTypes.length] = mimeType; + } + } + + /** Add one or more file extensions (without leading dot) that identify the supported data + * types. Used by 'canHandle' to figure out if the factory is suitable for an asset + * reference. */ + public function addExtensions(...args):void + { + for each (var extension:String in args) + { + extension = extension.toLowerCase(); + + if (_extensions.indexOf(extension) == -1) + _extensions[_extensions.length] = extension; + } + } + + /** Returns the mime types this factory supports. */ + public function getMimeTypes(out:Vector.=null):Vector. + { + out ||= new Vector.(); + + for (var i:int=0; i<_mimeTypes.length; ++i) + out[i] = _mimeTypes[i]; + + return out; + } + + /** Returns the file extensions this factory supports. */ + public function getExtensions(out:Vector.=null):Vector. + { + out ||= new Vector.(); + + for (var i:int=0; i<_extensions.length; ++i) + out[i] = _extensions[i]; + + return out; + } + + /** @private */ + internal function get priority():int { return _priority; } + internal function set priority(value:int):void { _priority = value; } + } +} diff --git a/mobile_version/src/starling/assets/AssetFactoryHelper.as b/mobile_version/src/starling/assets/AssetFactoryHelper.as new file mode 100644 index 00000000..6c8b57ea --- /dev/null +++ b/mobile_version/src/starling/assets/AssetFactoryHelper.as @@ -0,0 +1,124 @@ +package starling.assets +{ + import starling.utils.SystemUtil; + + /** A helper class that's passed to an AssetFactory's "create" method. */ + public class AssetFactoryHelper + { + private var _dataLoader:DataLoader; + private var _getNameFromUrlFunc:Function; + private var _getExtensionFromUrlFunc:Function; + private var _addPostProcessorFunc:Function; + private var _addAssetFunc:Function; + private var _onRestoreFunc:Function; + private var _logFunc:Function; + + /** @private */ + public function AssetFactoryHelper() + { } + + /** Forwarded to the AssetManager's method with the same name. */ + public function getNameFromUrl(url:String):String + { + if (_getNameFromUrlFunc != null) return _getNameFromUrlFunc(url); + else return ""; + } + + /** Forwarded to the AssetManager's method with the same name. */ + public function getExtensionFromUrl(url:String):String + { + if (_getExtensionFromUrlFunc != null) return _getExtensionFromUrlFunc(url); + else return ""; + } + + /** Accesses a URL (local or remote) and passes the loaded ByteArray to the + * 'onComplete' callback - or executes 'onError' when the data can't be loaded. + * + * @param url a string containing an URL. + * @param onComplete function(data:ByteArray, mimeType:String):void; + * @param onError function(error:String):void; + */ + public function loadDataFromUrl(url:String, onComplete:Function, onError:Function):void + { + if (_dataLoader) _dataLoader.load(url, onComplete, onError); + } + + /** Adds a method to be called by the AssetManager when the queue has finished processing. + * Useful e.g. if assets depend on other assets (like an atlas XML depending on the atlas + * texture). + * + * @param processor function(manager:AssetManager):void; + * @param priority Processors with a higher priority will be called first. + * The default processor for texture atlases is called with a + * priority of '100', others with '0'. + */ + public function addPostProcessor(processor:Function, priority:int=0):void + { + if (_addPostProcessorFunc != null) _addPostProcessorFunc(processor, priority); + } + + /** Textures are required to call this method when they begin their restoration process + * after a context loss. */ + public function onBeginRestore():void + { + if (_onRestoreFunc != null) _onRestoreFunc(false); + } + + /** Textures are required to call this method when they have finished their restoration + * process after a context loss. */ + public function onEndRestore():void + { + if (_onRestoreFunc != null) _onRestoreFunc(true); + } + + /** Forwarded to the AssetManager's method with the same name. */ + public function log(message:String):void + { + if (_logFunc != null) _logFunc(message); + } + + /** Adds additional assets to the AssetManager. To be called when the factory + * creates more than one asset. */ + public function addComplementaryAsset(name:String, asset:Object, type:String=null):void + { + if (_addAssetFunc != null) _addAssetFunc(name, asset, type); + } + + /** Delay the execution of 'call' until it's allowed. (On mobile, the context + * may not be accessed while the application is in the background.) + */ + public function executeWhenContextReady(call:Function, ...args):void + { + // On mobile, it is not allowed / endorsed to make stage3D calls while the app + // is in the background. Thus, we pause execution if that's the case. + + if (SystemUtil.isDesktop) call.apply(this, args); + else + { + args.unshift(call); + SystemUtil.executeWhenApplicationIsActive.apply(this, args); + } + } + + /** @private */ + internal function set getNameFromUrlFunc(value:Function):void { _getNameFromUrlFunc = value; } + + /** @private */ + internal function set getExtensionFromUrlFunc(value:Function):void { _getExtensionFromUrlFunc = value; } + + /** @private */ + internal function set dataLoader(value:DataLoader):void { _dataLoader = value; } + + /** @private */ + internal function set logFunc(value:Function):void { _logFunc = value; } + + /** @private */ + internal function set addAssetFunc(value:Function):void { _addAssetFunc = value; } + + /** @private */ + internal function set onRestoreFunc(value:Function):void { _onRestoreFunc = value; } + + /** @private */ + internal function set addPostProcessorFunc(value:Function):void { _addPostProcessorFunc = value; } + } +} diff --git a/mobile_version/src/starling/assets/AssetManager.as b/mobile_version/src/starling/assets/AssetManager.as new file mode 100644 index 00000000..44541910 --- /dev/null +++ b/mobile_version/src/starling/assets/AssetManager.as @@ -0,0 +1,1034 @@ +package starling.assets +{ + import flash.media.Sound; + import flash.media.SoundChannel; + import flash.media.SoundTransform; + import flash.net.URLRequest; + import flash.system.System; + import flash.utils.ByteArray; + import flash.utils.Dictionary; + import flash.utils.describeType; + import flash.utils.getQualifiedClassName; + import flash.utils.setTimeout; + + import starling.core.Starling; + import starling.events.Event; + import starling.events.EventDispatcher; + import starling.text.BitmapFont; + import starling.textures.Texture; + import starling.textures.TextureAtlas; + import starling.textures.TextureOptions; + import starling.utils.MathUtil; + import starling.utils.execute; + + /** Dispatched when all textures have been restored after a context loss. */ + [Event(name="texturesRestored", type="starling.events.Event")] + + /** The AssetManager handles loading and accessing a variety of asset types. You can + * add assets directly (via the 'add...' methods) or asynchronously via a queue. This allows + * you to deal with assets in a unified way, no matter if they are loaded from a file, + * directory, URL, or from an embedded object. + * + *

The class can deal with the following media types: + *

    + *
  • Textures (either from Bitmaps or ATF data)
  • + *
  • Texture atlases
  • + *
  • Bitmap Fonts
  • + *
  • Sounds
  • + *
  • XML data
  • + *
  • JSON data
  • + *
  • ByteArrays
  • + *
  • other AssetManagers
  • + *
+ *

+ * + *

For more information on how to add assets from different sources, read the documentation + * of the "enqueue()" method.

+ * + * Context Loss + * + *

When the stage3D context is lost, the AssetManager will automatically restore all + * loaded textures. To save memory, it will get them from their original sources. Since + * this is done asynchronously, your images might not reappear all at once, but during + * a time frame of several seconds. If you want, you can pause your game during that time; + * the AssetManager dispatches an "Event.TEXTURES_RESTORED" event when all textures have + * been restored.

+ * + * Error Handling + * + *

Loading of some assets may fail while the queue is being processed. In that case, the + * AssetManager will call the 'onError' callback that you can optional provide to the + * 'loadQueue' method. Queue processing will continue after an error, so it's always + * guaranteed that the 'onComplete' callback is executed, too.

+ * + * Texture Properties + * + *

When you enqueue a texture, its properties for "format", "scale", "mipMapping", and + * "repeat" will reflect the settings of the AssetManager at the time they were enqueued. + * This means that you can enqueue a bunch of textures, then change the settings and enqueue + * some more. Like this:

+ * + * + * var appDir:File = File.applicationDirectory; + * var assets:AssetManager = new AssetManager(); + * + * assets.textureOptions.format = Context3DTextureFormat.BGRA; + * assets.enqueue(appDir.resolvePath("textures/32bit")); + * + * assets.textureOptions.format = Context3DTextureFormat.BGRA_PACKED; + * assets.enqueue(appDir.resolvePath("textures/16bit")); + * + * assets.loadQueue(...); + * + * Nesting + * + *

When you enqueue one or more AssetManagers to another one, the "loadQueue" method will + * load the assets of the "child" AssetManager, as well. Later, when accessing assets, + * the "parent" AssetManager will return the "child" assets as well - just like it returns, + * say, the SubTextures from a contained TextureAtlas.

+ * + *

The main advantage of grouping your assets like this is something else, though: it + * allows to remove (and dispose) a complete group of assets in one step. The example + * below loads the assets from two directories. When the contents of one of them are no + * longer needed, all its assets are removed together.

+ * + * + * var manager:AssetManager = new AssetManager(); + * var appDir:File = File.applicationDirectory; + * + * var redAssets:AssetManager = new AssetManager(); + * redAssets.enqueueSingle(appDir.resolvePath("textures/red/")); + * + * var greenAssets:AssetManager = new AssetManager(); + * greenAssets.enqueueSingle(appDir.resolvePath("textures/green/")); + * + * manager.enqueueSingle(redAssets, "redAssets"); + * manager.enqueueSingle(greenAssets, "greenAssets"); + * manager.loadQueue(...); // loads both "red" and "green" assets + * + * // ... later, remove all "red" assets together + * manager.removeAssetManager("redAssets"); + * + * Customization + * + *

You can customize how assets are created by extending the 'AssetFactory' class and + * registering an instance of your new class at the AssetManager via 'registerFactory'. + * Factories are probed by priority; any factory with a priority > 0 will be executed + * before the built-in factories.

+ * + *

An asset type is identified by a unique String. You can add your own asset types + * by creating a custom 'AssetFactory' and having it add the asset with custom string + * identifier.

+ * + *

By overriding the methods 'getNameFromUrl', 'getExtensionFromUrl', 'disposeAsset', + * and 'log', you can customize how assets are named and disposed, and you can forward + * any logging to an external logger. To customize the way data is loaded from URLs or + * files, you can assign a custom 'DataLoader' instance to the AssetManager.

+ * + * @see starling.assets.AssetFactory + * @see starling.assets.AssetType + * @see starling.assets.DataLoader + */ + public class AssetManager extends EventDispatcher + { + private var _starling:Starling; + private var _assets:Dictionary; + private var _verbose:Boolean; + private var _numConnections:int; + private var _dataLoader:DataLoader; + private var _textureOptions:TextureOptions; + private var _queue:Vector.; + private var _registerBitmapFontsWithFontFace:Boolean; + private var _assetFactories:Vector.; + private var _numRestoredTextures:int; + private var _numLostTextures:int; + + // Regex for name / extension extraction from URLs. + private static const NAME_REGEX:RegExp = /(([^?\/\\]+?)(?:\.([\w\-]+))?)(?:\?.*)?$/; + + // fallback for unnamed assets + private static const NO_NAME:String = "unnamed"; + private static var sNoNameCount:int = 0; + + // helper objects + private static var sNames:Vector. = new []; + + /** Create a new instance with the given scale factor. */ + public function AssetManager(scaleFactor:Number=1) + { + _assets = new Dictionary(); + _verbose = true; + _textureOptions = new TextureOptions(scaleFactor); + _queue = new []; + _numConnections = 3; + _dataLoader = new DataLoader(); + _assetFactories = new []; + + registerFactory(new BitmapTextureFactory()); + registerFactory(new AtfTextureFactory()); + registerFactory(new SoundFactory()); + registerFactory(new JsonFactory()); + registerFactory(new XmlFactory()); + registerFactory(new ByteArrayFactory(), -100); + } + + /** Disposes all assets and purges the queue. + * + *

Beware that all references to the assets will remain intact, even though the assets + * are no longer valid. Call 'purge' if you want to remove all resources and reuse + * the AssetManager later.

+ */ + public function dispose():void + { + purgeQueue(); + + for each (var store:Dictionary in _assets) + for each (var asset:Object in store) + disposeAsset(asset); + } + + /** Removes assets of all types (disposing them along the way), empties the queue and + * aborts any pending load operations. */ + public function purge():void + { + log("Purging all assets, emptying queue"); + + purgeQueue(); + dispose(); + + _assets = new Dictionary(); + } + + // queue processing + + /** Enqueues one or more raw assets; they will only be available after successfully + * executing the "loadQueue" method. This method accepts a variety of different objects: + * + *
    + *
  • Strings or URLRequests containing an URL to a local or remote resource. Supported + * types: png, jpg, gif, atf, mp3, xml, fnt, json, binary.
  • + *
  • Instances of the File class (AIR only) pointing to a directory or a file. + * Directories will be scanned recursively for all supported types.
  • + *
  • Classes that contain static embedded assets.
  • + *
  • If the file extension is not recognized, the data is analyzed to see if + * contains XML or JSON data. If it's neither, it is stored as ByteArray.
  • + *
+ * + *

Suitable object names are extracted automatically: A file named "image.png" will be + * accessible under the name "image". When enqueuing embedded assets via a class, + * the variable name of the embedded object will be used as its name. An exception + * are texture atlases: they will have the same name as the actual texture they are + * referencing.

+ * + *

XMLs are made available via "getXml()"; this includes XMLs containing texture + * atlases or bitmap fonts, which are processed along the way. Bitmap fonts are also + * registered at the TextField class.

+ * + *

If you pass in JSON data, it will be parsed into an object and will be available via + * "getObject()".

+ */ + public function enqueue(...assets):void + { + for each (var asset:Object in assets) + { + if (asset is Array) + { + enqueue.apply(this, asset); + } + else if (asset is Class) + { + var typeXml:XML = describeType(asset); + var childNode:XML; + + if (_verbose) + log("Looking for static embedded assets in '" + + (typeXml.@name).split("::").pop() + "'"); + + for each (childNode in typeXml.constant.(@type == "Class")) + enqueueSingle(asset[childNode.@name], childNode.@name); + + for each (childNode in typeXml.variable.(@type == "Class")) + enqueueSingle(asset[childNode.@name], childNode.@name); + } + else if (getQualifiedClassName(asset) == "flash.filesystem::File") + { + if (!asset["exists"]) + { + log("File or directory not found: '" + asset["url"] + "'"); + } + else if (!asset["isHidden"]) + { + if (asset["isDirectory"]) + enqueue.apply(this, asset["getDirectoryListing"]()); + else + enqueueSingle(asset); + } + } + else if (asset is String || asset is URLRequest || asset is AssetManager) + { + enqueueSingle(asset); + } + else + { + log("Ignoring unsupported asset type: " + getQualifiedClassName(asset)); + } + } + } + + /** Enqueues a single asset with a custom name that can be used to access it later. + * If the asset is a texture, you can also add custom texture options. + * + * @param asset The asset that will be enqueued; accepts the same objects as the + * 'enqueue' method. + * @param name The name under which the asset will be found later. If you pass null or + * omit the parameter, it's attempted to generate a name automatically. + * @param options Custom options that will be used if 'asset' points to texture data. + * @return the name with which the asset was registered. + */ + public function enqueueSingle(asset:Object, name:String=null, + options:TextureOptions=null):String + { + if (asset is Class) + asset = new asset(); + + var assetReference:AssetReference = new AssetReference(asset); + assetReference.name = name || getNameFromUrl(assetReference.url) || getUniqueName(); + assetReference.extension = getExtensionFromUrl(assetReference.url); + assetReference.textureOptions = options || _textureOptions; + var logName:String = getFilenameFromUrl(assetReference.url) || assetReference.name; + + _queue.push(assetReference); + log("Enqueuing '" + logName + "'"); + return assetReference.name; + } + + /** Removes the asset(s) with the given name(s) from the queue. Note that this won't work + * after loading has started, even if these specific assets have not yet been processed. */ + public function dequeue(...assetNames):void + { + _queue = _queue.filter(function(asset:AssetReference, i:int, v:*):Boolean + { + return assetNames.indexOf(asset.name) == -1; + }); + } + + /** Empties the queue and aborts any pending load operations. */ + public function purgeQueue():void + { + _queue.length = 0; + _dataLoader.close(); + dispatchEventWith(Event.CANCEL); + } + + /** Loads all enqueued assets asynchronously. The 'onComplete' callback will be executed + * once all assets have been loaded - even when there have been errors, which are + * forwarded to the optional 'onError' callback. The 'onProgress' function will be called + * with a 'ratio' between '0.0' and '1.0' and is also optional. Furthermore, all + * parameters of all the callbacks are optional. + * + *

When you call this method, the manager will save a reference to "Starling.current"; + * all textures that are loaded will be accessible only from within this instance. Thus, + * if you are working with more than one Starling instance, be sure to call + * "makeCurrent()" on the appropriate instance before processing the queue.

+ * + * @param onComplete function(manager:AssetManager):void; + * @param onError function(error:String, asset:AssetReference):void; + * @param onProgress function(ratio:Number):void; + */ + public function loadQueue(onComplete:Function, + onError:Function=null, onProgress:Function=null):void + { + if (_queue.length == 0) + { + finish(); + return; + } + + // By using an event listener, we can make a call to "cancel" affect + // only the currently active loading process(es). + addEventListener(Event.CANCEL, onCanceled); + + var factoryHelper:AssetFactoryHelper = new AssetFactoryHelper(); + factoryHelper.getNameFromUrlFunc = getNameFromUrl; + factoryHelper.getExtensionFromUrlFunc = getExtensionFromUrl; + factoryHelper.addPostProcessorFunc = addPostProcessor; + factoryHelper.addAssetFunc = addAsset; + factoryHelper.onRestoreFunc = onAssetRestored; + factoryHelper.dataLoader = _dataLoader; + factoryHelper.logFunc = log; + + var i:int; + var self:AssetManager = this; + var canceled:Boolean = false; + var queue:Vector. = _queue.concat(); + var numAssets:int = queue.length; + var numComplete:int = 0; + var numConnections:int = MathUtil.min(_numConnections, numAssets); + var assetProgress:Vector. = new Vector.(numAssets, true); + var postProcessors:Vector. = new []; + + _starling = Starling.current; + _queue.length = 0; + + for (i=0; i, progressRatios:Vector., index:int, + helper:AssetFactoryHelper, onComplete:Function, onProgress:Function, + onError:Function, onIntermediateError:Function):void + { + var referenceCount:int = queue.length; + var reference:AssetReference = queue[index]; + progressRatios[index] = 0; + + if (reference.url) + _dataLoader.load(reference.url, onLoadComplete, onLoadError, onLoadProgress); + else if (reference.data is AssetManager) + (reference.data as AssetManager).loadQueue(onManagerComplete, onIntermediateError, onLoadProgress); + else + setTimeout(onLoadComplete, 1, reference.data); + + function onLoadComplete(data:Object, mimeType:String=null, + name:String=null, extension:String=null):void + { + if (_starling) _starling.makeCurrent(); + + onLoadProgress(1.0); + + if (data) reference.data = data; + if (name) reference.name = name; + if (extension) reference.extension = extension; + if (mimeType) reference.mimeType = mimeType; + + var assetFactory:AssetFactory = getFactoryFor(reference); + if (assetFactory == null) + execute(onAnyError, "Warning: no suitable factory found for '" + reference.name + "'"); + else + assetFactory.create(reference, helper, onComplete, onFactoryError); + } + + function onLoadProgress(ratio:Number):void + { + progressRatios[index] = ratio; + + var totalRatio:Number = 0; + var multiplier:Number = 1.0 / referenceCount; + + for (var k:int=0; k 0) totalRatio += multiplier * r; + } + + execute(onProgress, MathUtil.min(totalRatio, 1.0)); + } + + function onLoadError(error:String):void + { + onLoadProgress(1.0); + execute(onAnyError, "Error loading " + reference.name + ": " + error); + } + + function onAnyError(error:String):void + { + log(error); + execute(onError, error, reference); + } + + function onFactoryError(error:String):void + { + execute(onAnyError, "Error creating " + reference.name + ": " + error); + } + + function onManagerComplete():void + { + onComplete(reference.name, reference.data); + } + } + + private function getFactoryFor(asset:AssetReference):AssetFactory + { + var numFactories:int = _assetFactories.length; + for (var i:int=0; iBeware: if the slot (name + type) was already taken, the existing object will be + * disposed and replaced by the new one.

+ * + * @param name The name with which the asset can be retrieved later. Must be + * unique within this asset type. + * @param asset The actual asset to add (e.g. a texture, a sound, etc). + * @param type The type of the asset. If omitted, the type will be determined + * automatically (which works for all standard types defined within + * the 'AssetType' class). + */ + public function addAsset(name:String, asset:Object, type:String=null):void + { + type ||= AssetType.fromAsset(asset); + + var store:Dictionary = _assets[type]; + if (store == null) + { + store = new Dictionary(); + _assets[type] = store; + } + + log("Adding " + type + " '" + name + "'"); + + var prevAsset:Object = store[name]; + if (prevAsset && prevAsset != asset) + { + log("Warning: name was already in use; disposing the previous " + type); + disposeAsset(prevAsset); + } + + store[name] = asset; + } + + /** Retrieves an asset of the given type, with the given name. If 'recursive' is true, + * the method will traverse included texture atlases and asset managers. + * + *

Typically, you will use one of the type-safe convenience methods instead, like + * 'getTexture', 'getSound', etc.

+ */ + public function getAsset(type:String, name:String, recursive:Boolean=true):Object + { + if (recursive) + { + var managerStore:Dictionary = _assets[AssetType.ASSET_MANAGER]; + if (managerStore) + { + for each (var manager:AssetManager in managerStore) + { + var asset:Object = manager.getAsset(type, name, true); + if (asset) return asset; + } + } + + if (type == AssetType.TEXTURE) + { + var atlasStore:Dictionary = _assets[AssetType.TEXTURE_ATLAS]; + if (atlasStore) + { + for each (var atlas:TextureAtlas in atlasStore) + { + var texture:Texture = atlas.getTexture(name); + if (texture) return texture; + } + } + } + } + + var store:Dictionary = _assets[type]; + if (store) return store[name]; + else return null; + } + + /** Retrieves an alphabetically sorted list of all assets that have the given type and + * start with the given prefix. If 'recursive' is true, the method will traverse included + * texture atlases and asset managers. */ + public function getAssetNames(assetType:String, prefix:String="", recursive:Boolean=true, + out:Vector.=null):Vector. + { + out ||= new Vector.(); + + if (recursive) + { + var managerStore:Dictionary = _assets[AssetType.ASSET_MANAGER]; + if (managerStore) + { + for each (var manager:AssetManager in managerStore) + manager.getAssetNames(assetType, prefix, true, out); + } + + if (assetType == AssetType.TEXTURE) + { + var atlasStore:Dictionary = _assets[AssetType.TEXTURE_ATLAS]; + if (atlasStore) + { + for each (var atlas:TextureAtlas in atlasStore) + atlas.getNames(prefix, out); + } + } + } + + getDictionaryKeys(_assets[assetType], prefix, out); + out.sort(Array.CASEINSENSITIVE); + return out; + } + + /** Removes the asset with the given name and type, and will optionally dispose it. */ + public function removeAsset(assetType:String, name:String, dispose:Boolean=true):void + { + var store:Dictionary = _assets[assetType]; + if (store) + { + var asset:Object = store[name]; + if (asset) + { + log("Removing " + assetType + " '" + name + "'"); + if (dispose) disposeAsset(asset); + delete store[name]; + } + } + } + + // convenience access methods + + /** Returns a texture with a certain name. Includes textures stored inside atlases. */ + public function getTexture(name:String):Texture + { + return getAsset(AssetType.TEXTURE, name) as Texture; + } + + /** Returns all textures that start with a certain string, sorted alphabetically + * (especially useful for "MovieClip"). Includes textures stored inside atlases. */ + public function getTextures(prefix:String="", out:Vector.=null):Vector. + { + if (out == null) out = new []; + + for each (var name:String in getTextureNames(prefix, sNames)) + out[out.length] = getTexture(name); // avoid 'push' + + sNames.length = 0; + return out; + } + + /** Returns all texture names that start with a certain string, sorted alphabetically. + * Includes textures stored inside atlases. */ + public function getTextureNames(prefix:String="", out:Vector.=null):Vector. + { + return getAssetNames(AssetType.TEXTURE, prefix, true, out); + } + + /** Returns a texture atlas with a certain name, or null if it's not found. */ + public function getTextureAtlas(name:String):TextureAtlas + { + return getAsset(AssetType.TEXTURE_ATLAS, name) as TextureAtlas; + } + + /** Returns all texture atlas names that start with a certain string, sorted alphabetically. + * If you pass an out-vector, the names will be added to that vector. */ + public function getTextureAtlasNames(prefix:String="", out:Vector.=null):Vector. + { + return getAssetNames(AssetType.TEXTURE_ATLAS, prefix, true, out); + } + + /** Returns a sound with a certain name, or null if it's not found. */ + public function getSound(name:String):Sound + { + return getAsset(AssetType.SOUND, name) as Sound; + } + + /** Returns all sound names that start with a certain string, sorted alphabetically. + * If you pass an out-vector, the names will be added to that vector. */ + public function getSoundNames(prefix:String="", out:Vector.=null):Vector. + { + return getAssetNames(AssetType.SOUND, prefix, true, out); + } + + /** Generates a new SoundChannel object to play back the sound. This method returns a + * SoundChannel object, which you can access to stop the sound and to control volume. */ + public function playSound(name:String, startTime:Number=0, loops:int=0, + transform:SoundTransform=null):SoundChannel + { + var sound:Sound = getSound(name); + if (sound) return sound.play(startTime, loops, transform); + else return null; + } + + /** Returns an XML with a certain name, or null if it's not found. */ + public function getXml(name:String):XML + { + return getAsset(AssetType.XML_DOCUMENT, name) as XML; + } + + /** Returns all XML names that start with a certain string, sorted alphabetically. + * If you pass an out-vector, the names will be added to that vector. */ + public function getXmlNames(prefix:String="", out:Vector.=null):Vector. + { + return getAssetNames(AssetType.XML_DOCUMENT, prefix, true, out); + } + + /** Returns an object with a certain name, or null if it's not found. Enqueued JSON + * data is parsed and can be accessed with this method. */ + public function getObject(name:String):Object + { + return getAsset(AssetType.OBJECT, name); + } + + /** Returns all object names that start with a certain string, sorted alphabetically. + * If you pass an out-vector, the names will be added to that vector. */ + public function getObjectNames(prefix:String="", out:Vector.=null):Vector. + { + return getAssetNames(AssetType.OBJECT, prefix, true, out); + } + + /** Returns a byte array with a certain name, or null if it's not found. */ + public function getByteArray(name:String):ByteArray + { + return getAsset(AssetType.BYTE_ARRAY, name) as ByteArray; + } + + /** Returns all byte array names that start with a certain string, sorted alphabetically. + * If you pass an out-vector, the names will be added to that vector. */ + public function getByteArrayNames(prefix:String="", out:Vector.=null):Vector. + { + return getAssetNames(AssetType.BYTE_ARRAY, prefix, true, out); + } + + /** Returns a bitmap font with a certain name, or null if it's not found. */ + public function getBitmapFont(name:String):BitmapFont + { + return getAsset(AssetType.BITMAP_FONT, name) as BitmapFont; + } + + /** Returns all bitmap font names that start with a certain string, sorted alphabetically. + * If you pass an out-vector, the names will be added to that vector. */ + public function getBitmapFontNames(prefix:String="", out:Vector.=null):Vector. + { + return getAssetNames(AssetType.BITMAP_FONT, prefix, true, out); + } + + /** Returns an asset manager with a certain name, or null if it's not found. */ + public function getAssetManager(name:String):AssetManager + { + return getAsset(AssetType.ASSET_MANAGER, name) as AssetManager; + } + + /** Returns all asset manager names that start with a certain string, sorted alphabetically. + * If you pass an out-vector, the names will be added to that vector. */ + public function getAssetManagerNames(prefix:String="", out:Vector.=null):Vector. + { + return getAssetNames(AssetType.ASSET_MANAGER, prefix, true, out); + } + + /** Removes a certain texture, optionally disposing it. */ + public function removeTexture(name:String, dispose:Boolean=true):void + { + removeAsset(AssetType.TEXTURE, name, dispose); + } + + /** Removes a certain texture atlas, optionally disposing it. */ + public function removeTextureAtlas(name:String, dispose:Boolean=true):void + { + removeAsset(AssetType.TEXTURE_ATLAS, name, dispose); + } + + /** Removes a certain sound. */ + public function removeSound(name:String):void + { + removeAsset(AssetType.SOUND, name); + } + + /** Removes a certain Xml object, optionally disposing it. */ + public function removeXml(name:String, dispose:Boolean=true):void + { + removeAsset(AssetType.XML_DOCUMENT, name, dispose); + } + + /** Removes a certain object. */ + public function removeObject(name:String):void + { + removeAsset(AssetType.OBJECT, name); + } + + /** Removes a certain byte array, optionally disposing its memory right away. */ + public function removeByteArray(name:String, dispose:Boolean=true):void + { + removeAsset(AssetType.BYTE_ARRAY, name, dispose); + } + + /** Removes a certain bitmap font, optionally disposing it. */ + public function removeBitmapFont(name:String, dispose:Boolean=true):void + { + removeAsset(AssetType.BITMAP_FONT, name, dispose); + } + + /** Removes a certain asset manager and optionally disposes it right away. */ + public function removeAssetManager(name:String, dispose:Boolean=true):void + { + removeAsset(AssetType.ASSET_MANAGER, name, dispose); + } + + // registration of factories + + /** Registers a custom AssetFactory. If you use any priority > 0, the factory will + * be called before the default factories. The final factory to be invoked is the + * 'ByteArrayFactory', which is using a priority of '-100'. */ + public function registerFactory(factory:AssetFactory, priority:int=0):void + { + factory.priority = priority; + + _assetFactories.push(factory); + _assetFactories.sort(comparePriorities); + } + + /** Unregisters the specified AssetFactory. */ + public function unregisterFactory(factory:AssetFactory):void + { + var index:int = _assetFactories.indexOf(factory); + if (index != -1) _assetFactories.removeAt(index); + } + + private static function comparePriorities(a:Object, b:Object):int + { + if (a.priority == b.priority) return 0; + return a.priority > b.priority ? -1 : 1; + } + + // helpers + + private function getFilenameFromUrl(url:String):String + { + if (url) + { + var matches:Array = NAME_REGEX.exec(decodeURIComponent(url)); + if (matches && matches.length > 1) return matches[1]; + } + return null; + } + + /** This method is called internally to determine the name under which an asset will be + * accessible; override it if you need a custom naming scheme. + * + * @return the name to be used for the asset, or 'null' if it can't be determined. */ + protected function getNameFromUrl(url:String):String + { + if (url) + { + var matches:Array = NAME_REGEX.exec(decodeURIComponent(url)); + if (matches && matches.length > 2) return matches[2]; + } + return null; + } + + /** This method is called internally to determine the extension that's passed to the + * 'AssetFactory' (via the 'AssetReference'). Override it if you need to customize + * e.g. the extension of a server URL. + * + * @return the extension to be used for the asset, or an empty string if it can't be + * determined. */ + protected function getExtensionFromUrl(url:String):String + { + if (url) + { + var matches:Array = NAME_REGEX.exec(decodeURIComponent(url)); + if (matches && matches.length > 3) return matches[3]; + } + return ""; + } + + /** Disposes the given asset. ByteArrays are cleared, XMLs are disposed using + * 'System.disposeXML'. If the object contains a 'dispose' method, it will be called. + * Override if you need to add custom cleanup code for a certain asset. */ + protected function disposeAsset(asset:Object):void + { + if (asset is ByteArray) (asset as ByteArray).clear(); + if (asset is XML) System.disposeXML(asset as XML); + if ("dispose" in asset) asset["dispose"](); + } + + /** This method is called during loading of assets when 'verbose' is activated. Per + * default, it traces 'message' to the console. */ + protected function log(message:String):void + { + if (_verbose) trace("[AssetManager]", message); + } + + private static function getDictionaryKeys(dictionary:Dictionary, prefix:String="", + out:Vector.=null):Vector. + { + if (out == null) out = new []; + if (dictionary) + { + for (var name:String in dictionary) + if (name.indexOf(prefix) == 0) + out[out.length] = name; // avoid 'push' + + out.sort(Array.CASEINSENSITIVE); + } + return out; + } + + private static function getUniqueName():String + { + return NO_NAME + "-" + sNoNameCount++; + } + + // properties + + /** When activated, the class will trace information about added/enqueued assets. + * @default true */ + public function get verbose():Boolean { return _verbose; } + public function set verbose(value:Boolean):void { _verbose = value; } + + /** Returns the number of raw assets that have been enqueued, but not yet loaded. */ + public function get numQueuedAssets():int { return _queue.length; } + + /** The maximum number of parallel connections that are spawned when loading the queue. + * More connections can reduce loading times, but require more memory. @default 3. */ + public function get numConnections():int { return _numConnections; } + public function set numConnections(value:int):void + { + _numConnections = MathUtil.min(1, value); + } + + /** Textures will be created with the options set up in this object at the time of + * enqueuing. */ + public function get textureOptions():TextureOptions { return _textureOptions; } + public function set textureOptions(value:TextureOptions):void { _textureOptions.copyFrom(value); } + + /** The DataLoader is used to load any data from files or URLs. If you need to customize + * its behavior (e.g. to add a caching mechanism), assign your custom instance here. */ + public function get dataLoader():DataLoader { return _dataLoader; } + public function set dataLoader(value:DataLoader):void { _dataLoader = value; } + + /** Indicates if bitmap fonts should be registered with their "face" attribute from the + * font XML file. Per default, they are registered with the name of the texture file. + * @default false */ + public function get registerBitmapFontsWithFontFace():Boolean + { + return _registerBitmapFontsWithFontFace; + } + + public function set registerBitmapFontsWithFontFace(value:Boolean):void + { + _registerBitmapFontsWithFontFace = value; + } + } +} + +import starling.assets.AssetManager; + +class AssetPostProcessor +{ + private var _priority:int; + private var _callback:Function; + + public function AssetPostProcessor(callback:Function, priority:int) + { + if (callback == null || callback.length != 1) + throw new ArgumentError("callback must be a function " + + "accepting one 'AssetStore' parameter"); + + _callback = callback; + _priority = priority; + } + + internal function execute(store:AssetManager):void + { + _callback(store); + } + + public function get priority():int { return _priority; } +} \ No newline at end of file diff --git a/mobile_version/src/starling/assets/AssetReference.as b/mobile_version/src/starling/assets/AssetReference.as new file mode 100644 index 00000000..d978f100 --- /dev/null +++ b/mobile_version/src/starling/assets/AssetReference.as @@ -0,0 +1,56 @@ +package starling.assets +{ + import starling.textures.TextureOptions; + + /** The description of an asset to be created by an AssetFactory. */ + public class AssetReference + { + private var _name:String; + private var _url:String; + private var _data:Object; + private var _mimeType:String; + private var _extension:String; + private var _textureOptions:TextureOptions; + + /** Creates a new instance with the given data, which is typically some kind of file + * reference / URL or an instance of an asset class. If 'data' contains an URL, an + * equivalent value will be assigned to the 'url' property. */ + public function AssetReference(data:Object) + { + _data = data; + _textureOptions = new TextureOptions(); + + if (data is String) _url = data as String; + else if ("url" in data) _url = data["url"] as String; + } + + /** The name with which the asset should be added to the AssetManager. */ + public function get name():String { return _name; } + public function set name(value:String):void { _name = value; } + + /** The url from which the asset needs to be / has been loaded. */ + public function get url():String { return _url; } + public function set url(value:String):void { _url = value; } + + /** The raw data of the asset. This property often contains an URL; when it's passed + * to an AssetFactory, loading has already completed, and the property contains a + * ByteArray with the loaded data. */ + public function get data():Object { return _data; } + public function set data(value:Object):void { _data = value; } + + /** The mime type of the asset, if loaded from a server. */ + public function get mimeType():String { return _mimeType; } + public function set mimeType(value:String):void { _mimeType = value; } + + /** The file extension of the asset, if the filename or URL contains one. */ + public function get extension():String { return _extension; } + public function set extension(value:String):void { _extension = value; } + + /** The TextureOptions describing how to create a texture, if the asset references one. */ + public function get textureOptions():TextureOptions { return _textureOptions; } + public function set textureOptions(value:TextureOptions):void + { + _textureOptions.copyFrom(value); + } + } +} diff --git a/mobile_version/src/starling/assets/AssetType.as b/mobile_version/src/starling/assets/AssetType.as new file mode 100644 index 00000000..68631d28 --- /dev/null +++ b/mobile_version/src/starling/assets/AssetType.as @@ -0,0 +1,40 @@ +package starling.assets +{ + import flash.media.Sound; + import flash.media.Video; + import flash.utils.ByteArray; + + import starling.errors.AbstractClassError; + import starling.text.BitmapFont; + import starling.textures.Texture; + import starling.textures.TextureAtlas; + + /** An enumeration class containing all the asset types supported by the AssetManager. */ + public class AssetType + { + /** @private */ + public function AssetType() { throw new AbstractClassError(); } + + public static const TEXTURE:String = "texture"; + public static const TEXTURE_ATLAS:String = "textureAtlas"; + public static const SOUND:String = "sound"; + public static const XML_DOCUMENT:String = "xml"; + public static const OBJECT:String = "object"; + public static const BYTE_ARRAY:String = "byteArray"; + public static const BITMAP_FONT:String = "bitmapFont"; + public static const ASSET_MANAGER:String = "assetManager"; + + /** Figures out the asset type string from the type of the given instance. */ + public static function fromAsset(asset:Object):String + { + if (asset is Texture) return TEXTURE; + else if (asset is TextureAtlas) return TEXTURE_ATLAS; + else if (asset is Sound) return SOUND; + else if (asset is XML) return XML_DOCUMENT; + else if (asset is ByteArray) return BYTE_ARRAY; + else if (asset is BitmapFont) return BITMAP_FONT; + else if (asset is AssetManager) return ASSET_MANAGER; + else return OBJECT; + } + } +} diff --git a/mobile_version/src/starling/assets/AtfTextureFactory.as b/mobile_version/src/starling/assets/AtfTextureFactory.as new file mode 100644 index 00000000..9e587170 --- /dev/null +++ b/mobile_version/src/starling/assets/AtfTextureFactory.as @@ -0,0 +1,73 @@ +package starling.assets +{ + import flash.utils.ByteArray; + + import starling.textures.AtfData; + import starling.textures.Texture; + import starling.utils.execute; + + /** This AssetFactory creates texture assets from ATF files. */ + public class AtfTextureFactory extends AssetFactory + { + /** Creates a new instance. */ + public function AtfTextureFactory() + { + addExtensions("atf"); // not used, actually, since we can parse the ATF header, anyway. + } + + /** @inheritDoc */ + override public function canHandle(reference:AssetReference):Boolean + { + return (reference.data is ByteArray && AtfData.isAtfData(reference.data as ByteArray)); + } + + /** @inheritDoc */ + override public function create(reference:AssetReference, helper:AssetFactoryHelper, + onComplete:Function, onError:Function):void + { + helper.executeWhenContextReady(createTexture); + + function createTexture():void + { + var onReady:Function = reference.textureOptions.onReady as Function; + reference.textureOptions.onReady = function():void + { + execute(onReady, texture); + onComplete(reference.name, texture); + }; + + var texture:Texture = null; + var url:String = reference.url; + + try { texture = Texture.fromData(reference.data, reference.textureOptions); } + catch (e:Error) { onError(e.message); } + + if (url && texture) + { + texture.root.onRestore = function():void + { + helper.onBeginRestore(); + helper.loadDataFromUrl(url, function(data:ByteArray):void + { + helper.executeWhenContextReady(function():void + { + try { texture.root.uploadAtfData(data); } + catch (e:Error) { helper.log("Texture restoration failed: " + e.message); } + + helper.onEndRestore(); + }); + }, onReloadError); + }; + } + + reference.data = null; // prevent closures from keeping reference + } + + function onReloadError(error:String):void + { + helper.log("Texture restoration failed for " + reference.url + ". " + error); + helper.onEndRestore(); + } + } + } +} diff --git a/mobile_version/src/starling/assets/BitmapTextureFactory.as b/mobile_version/src/starling/assets/BitmapTextureFactory.as new file mode 100644 index 00000000..24915e4e --- /dev/null +++ b/mobile_version/src/starling/assets/BitmapTextureFactory.as @@ -0,0 +1,167 @@ +package starling.assets +{ + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.Loader; + import flash.display.LoaderInfo; + import flash.events.Event; + import flash.events.IOErrorEvent; + import flash.system.ImageDecodingPolicy; + import flash.system.LoaderContext; + import flash.utils.ByteArray; + + import starling.textures.Texture; + import starling.textures.TextureOptions; + import starling.utils.ByteArrayUtil; + import starling.utils.execute; + + /** This AssetFactory creates texture assets from bitmaps and image files. */ + public class BitmapTextureFactory extends AssetFactory + { + private static const MAGIC_NUMBERS_JPG:Array = [0xff, 0xd8]; + private static const MAGIC_NUMBERS_PNG:Array = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; + private static const MAGIC_NUMBERS_GIF:Array = [0x47, 0x49, 0x46, 0x38]; + + /** Creates a new instance. */ + public function BitmapTextureFactory() + { + addMimeTypes("image/png", "image/jpg", "image/jpeg", "image/gif"); + addExtensions("png", "jpg", "jpeg", "gif"); + } + + /** @inheritDoc */ + override public function canHandle(reference:AssetReference):Boolean + { + if (super.canHandle(reference) || + reference.data is Bitmap || reference.data is BitmapData) + { + return true; + } + else if (reference.data is ByteArray) + { + var byteData:ByteArray = reference.data as ByteArray; + return ByteArrayUtil.startsWithBytes(byteData, MAGIC_NUMBERS_PNG) || + ByteArrayUtil.startsWithBytes(byteData, MAGIC_NUMBERS_JPG) || + ByteArrayUtil.startsWithBytes(byteData, MAGIC_NUMBERS_GIF); + } + else return false; + } + + /** @inheritDoc */ + override public function create(reference:AssetReference, helper:AssetFactoryHelper, + onComplete:Function, onError:Function):void + { + var texture:Texture = null; + var url:String = reference.url; + var data:Object = reference.data; + var name:String = reference.name; + var options:TextureOptions = reference.textureOptions; + var onReady:Function = reference.textureOptions.onReady as Function; + + if (data is Bitmap) + onBitmapDataCreated((data as Bitmap).bitmapData); + else if (data is BitmapData) + onBitmapDataCreated(data as BitmapData); + else if (data is ByteArray) + createBitmapDataFromByteArray(data as ByteArray, onBitmapDataCreated, onError); + + // prevent closures from keeping references + reference.data = data = null; + + function onBitmapDataCreated(bitmapData:BitmapData):void + { + helper.executeWhenContextReady(createFromBitmapData, bitmapData); + } + + function createFromBitmapData(bitmapData:BitmapData):void + { + options.onReady = complete; + + try { texture = Texture.fromData(bitmapData, options); } + catch (e:Error) { onError(e.message); } + + if (texture && url) texture.root.onRestore = restoreTexture; + } + + function complete():void + { + execute(onReady, texture); + onComplete(name, texture); + } + + function restoreTexture():void + { + helper.onBeginRestore(); + + reload(url, function(bitmapData:BitmapData):void + { + helper.executeWhenContextReady(function():void + { + try { texture.root.uploadBitmapData(bitmapData); } + catch (e:Error) { helper.log("Texture restoration failed: " + e.message); } + + helper.onEndRestore(); + }); + }); + } + + function reload(url:String, onComplete:Function):void + { + helper.loadDataFromUrl(url, function(data:ByteArray):void + { + createBitmapDataFromByteArray(data, onComplete, onReloadError); + }, onReloadError); + } + + function onReloadError(error:String):void + { + helper.log("Texture restoration failed for " + url + ". " + error); + helper.onEndRestore(); + } + } + + /** Called by 'create' to convert a ByteArray to a BitmapData. + * + * @param data A ByteArray that contains image data + * (like the contents of a PNG or JPG file). + * @param onComplete Called with the BitmapData when successful. + *
function(bitmapData:BitmapData):void;
+ * @param onError To be called when creation fails for some reason. + *
function(error:String):void
+ */ + protected function createBitmapDataFromByteArray(data:ByteArray, + onComplete:Function, onError:Function):void + { + var loader:Loader = new Loader(); + var loaderInfo:LoaderInfo = loader.contentLoaderInfo; + loaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onIoError); + loaderInfo.addEventListener(Event.COMPLETE, onLoaderComplete); + var loaderContext:LoaderContext = new LoaderContext(); + loaderContext.imageDecodingPolicy = ImageDecodingPolicy.ON_LOAD; + loader.loadBytes(data, loaderContext); + + function onIoError(event:IOErrorEvent):void + { + cleanup(); + execute(onError, event.text); + } + + function onLoaderComplete(event:Object):void + { + complete(event.target.content.bitmapData); + } + + function complete(bitmapData:BitmapData):void + { + cleanup(); + execute(onComplete, bitmapData); + } + + function cleanup():void + { + loaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, onIoError); + loaderInfo.removeEventListener(Event.COMPLETE, onLoaderComplete); + } + } + } +} diff --git a/mobile_version/src/starling/assets/ByteArrayFactory.as b/mobile_version/src/starling/assets/ByteArrayFactory.as new file mode 100644 index 00000000..79d02371 --- /dev/null +++ b/mobile_version/src/starling/assets/ByteArrayFactory.as @@ -0,0 +1,30 @@ +package starling.assets +{ + import flash.utils.ByteArray; + + /** This AssetFactory forwards ByteArrays to the AssetManager. It's the fallback when + * no other factory can handle an asset reference (default priority: -100). */ + public class ByteArrayFactory extends AssetFactory + { + /** Creates a new instance. */ + public function ByteArrayFactory() + { + // not used, actually - this factory is used as a fallback with low priority + addExtensions("bin"); + addMimeTypes("application/octet-stream"); + } + + /** @inheritDoc */ + override public function canHandle(reference:AssetReference):Boolean + { + return reference.data is ByteArray; + } + + /** @inheritDoc */ + override public function create(reference:AssetReference, helper:AssetFactoryHelper, + onComplete:Function, onError:Function):void + { + onComplete(reference.name, reference.data as ByteArray); + } + } +} diff --git a/mobile_version/src/starling/assets/DataLoader.as b/mobile_version/src/starling/assets/DataLoader.as new file mode 100644 index 00000000..4fe81f7b --- /dev/null +++ b/mobile_version/src/starling/assets/DataLoader.as @@ -0,0 +1,138 @@ +package starling.assets +{ + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.HTTPStatusEvent; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.net.URLLoader; + import flash.net.URLLoaderDataFormat; + import flash.net.URLRequest; + import flash.utils.Dictionary; + + import starling.utils.execute; + + /** Loads binary data from a local or remote URL with a very simple callback system. + * + *

The DataLoader is used by the AssetManager to load any local or remote data. + * Its single purpose is to get the binary data that's stored at a specific URL.

+ * + *

You can use this class for your own purposes (as an easier to use 'URLLoader' + * alternative), or you can extend the class to modify the AssetManager's behavior. + * E.g. you could extend this class to add a caching mechanism for remote assets. + * Assign an instance of your extended class to the AssetManager's dataLoader + * property.

+ */ + public class DataLoader + { + // This HTTPStatusEvent is only available in AIR + private static const HTTP_RESPONSE_STATUS:String = "httpResponseStatus"; + + private var _urlLoaders:Dictionary; + + /** Creates a new DataLoader instance. */ + public function DataLoader() + { + _urlLoaders = new Dictionary(true); + } + + /** Loads the binary data from a specific URL. Always supply both 'onComplete' and + * 'onError' parameters; in case of an error, only the latter will be called. + * + *

The 'onComplete' callback may have up to four parameters, all of them being optional. + * If you pass a callback that just takes zero or one, it will work just as well. The + * additional parameters may be used to describe the name and type of the downloaded + * data. They are not provided by the base class, but the AssetManager will check + * if they are available.

+ * + * @param url a String containing an URL. + * @param onComplete will be called when the data has been loaded. + * function(data:ByteArray, mimeType:String, name:String, extension:String):void + * @param onError will be called in case of an error. The 2nd parameter is optional. + * function(error:String, httpStatus:int):void + * @param onProgress will be called multiple times with the current load ratio (0-1). + * function(ratio:Number):void + */ + public function load(url:String, onComplete:Function, + onError:Function, onProgress:Function=null):void + { + var message:String; + var mimeType:String = null; + var httpStatus:int = 0; + var request:URLRequest = new URLRequest(url); + var loader:URLLoader = new URLLoader(); + loader.dataFormat = URLLoaderDataFormat.BINARY; + loader.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); + loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onLoadError); + loader.addEventListener(HTTP_RESPONSE_STATUS, onHttpResponseStatus); + loader.addEventListener(ProgressEvent.PROGRESS, onLoadProgress); + loader.addEventListener(Event.COMPLETE, onLoadComplete); + loader.load(request); + + _urlLoaders[loader] = true; + + function onHttpResponseStatus(event:HTTPStatusEvent):void + { + httpStatus = event.status; + mimeType = getHttpHeader(event["responseHeaders"], "Content-Type"); + } + + function onLoadError(event:ErrorEvent):void + { + cleanup(); + message = event.type + " - " + event.text; + execute(onError, message); + } + + function onLoadProgress(event:ProgressEvent):void + { + if (onProgress != null && event.bytesTotal > 0) + onProgress(event.bytesLoaded / event.bytesTotal); + } + + function onLoadComplete(event:Object):void + { + cleanup(); + + if (httpStatus < 400) + execute(onComplete, loader.data, mimeType); + else + execute(onError, "Unexpected HTTP status '" + httpStatus + "'. URL: " + + request.url, httpStatus); + } + + function cleanup():void + { + loader.removeEventListener(IOErrorEvent.IO_ERROR, onLoadError); + loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onLoadError); + loader.removeEventListener(HTTP_RESPONSE_STATUS, onHttpResponseStatus); + loader.removeEventListener(ProgressEvent.PROGRESS, onLoadProgress); + loader.removeEventListener(Event.COMPLETE, onLoadComplete); + delete _urlLoaders[loader]; + } + } + + /** Aborts all current load operations. The loader can still be used, though. */ + public function close():void + { + for (var loader:* in _urlLoaders) + { + try { loader.close(); } + catch (e:Error) {} + } + + _urlLoaders = new Dictionary(true); + } + + private static function getHttpHeader(headers:Array, headerName:String):String + { + if (headers) + { + for each (var header:Object in headers) + if (header.name == headerName) return header.value; + } + return null; + } + } +} diff --git a/mobile_version/src/starling/assets/JsonFactory.as b/mobile_version/src/starling/assets/JsonFactory.as new file mode 100644 index 00000000..8f8494c9 --- /dev/null +++ b/mobile_version/src/starling/assets/JsonFactory.as @@ -0,0 +1,40 @@ +package starling.assets +{ + import flash.utils.ByteArray; + + import starling.utils.ByteArrayUtil; + + /** This AssetFactory creates objects from JSON data. */ + public class JsonFactory extends AssetFactory + { + /** Creates a new instance. */ + public function JsonFactory() + { + addExtensions("json"); + addMimeTypes("application/json", "text/json"); + } + + /** @inheritDoc */ + override public function canHandle(reference:AssetReference):Boolean + { + return super.canHandle(reference) || (reference.data is ByteArray && + ByteArrayUtil.startsWithString(reference.data as ByteArray, "{")); + } + + /** @inheritDoc */ + override public function create(reference:AssetReference, helper:AssetFactoryHelper, + onComplete:Function, onError:Function):void + { + try + { + var bytes:ByteArray = reference.data as ByteArray; + var object:Object = JSON.parse(bytes.readUTFBytes(bytes.length)); + onComplete(reference.name, object); + } + catch (e:Error) + { + onError("Could not parse JSON: " + e.message); + } + } + } +} diff --git a/mobile_version/src/starling/assets/SoundFactory.as b/mobile_version/src/starling/assets/SoundFactory.as new file mode 100644 index 00000000..84e097b6 --- /dev/null +++ b/mobile_version/src/starling/assets/SoundFactory.as @@ -0,0 +1,60 @@ +package starling.assets +{ + import flash.media.Sound; + import flash.utils.ByteArray; + + import starling.utils.ByteArrayUtil; + + /** This AssetFactory creates sound assets. */ + public class SoundFactory extends AssetFactory + { + private static const MAGIC_NUMBERS_A:Array = [0xFF, 0xFB]; + private static const MAGIC_NUMBERS_B:Array = [0x49, 0x44, 0x33]; + + /** Creates a new instance. */ + public function SoundFactory() + { + addMimeTypes("audio/mp3", "audio/mpeg3", "audio/mpeg"); + addExtensions("mp3"); + } + + /** @inheritDoc */ + override public function canHandle(reference:AssetReference):Boolean + { + if (reference.data is Sound || super.canHandle(reference)) + return true; + else if (reference.data is ByteArray) + { + var byteData:ByteArray = reference.data as ByteArray; + return ByteArrayUtil.startsWithBytes(byteData, MAGIC_NUMBERS_A) || + ByteArrayUtil.startsWithBytes(byteData, MAGIC_NUMBERS_B); + } + else return false; + } + + /** @inheritDoc */ + override public function create(reference:AssetReference, helper:AssetFactoryHelper, + onComplete:Function, onError:Function):void + { + var sound:Sound = reference.data as Sound; + var bytes:ByteArray = reference.data as ByteArray; + + if (bytes) + { + try + { + sound = new Sound(); + sound.loadCompressedDataFromByteArray(bytes, bytes.length); + } + catch (e:Error) + { + onError("Could not load sound data: " + e.message); + return; + } + + } + + onComplete(reference.name, sound); + } + } +} diff --git a/mobile_version/src/starling/assets/XmlFactory.as b/mobile_version/src/starling/assets/XmlFactory.as new file mode 100644 index 00000000..54af6099 --- /dev/null +++ b/mobile_version/src/starling/assets/XmlFactory.as @@ -0,0 +1,84 @@ +package starling.assets +{ + import flash.utils.ByteArray; + + import starling.text.BitmapFont; + import starling.text.TextField; + import starling.textures.Texture; + import starling.textures.TextureAtlas; + import starling.utils.ByteArrayUtil; + + /** This AssetFactory creates XML assets, texture atlases and bitmap fonts. */ + public class XmlFactory extends AssetFactory + { + /** Creates a new instance. */ + public function XmlFactory() + { + addMimeTypes("application/xml", "text/xml"); + addExtensions("xml", "fnt"); + } + + /** Returns true if mime type or extension fit for XML data, or if the data starts + * with a "<" character. */ + override public function canHandle(reference:AssetReference):Boolean + { + return super.canHandle(reference) || (reference.data is ByteArray && + ByteArrayUtil.startsWithString(reference.data as ByteArray, "<")); + } + + /** Creates the XML asset and passes it to 'onComplete'. If the XML contains a + * TextureAtlas or a BitmapFont, adds suitable post processors. */ + override public function create(reference:AssetReference, helper:AssetFactoryHelper, + onComplete:Function, onError:Function):void + { + var xml:XML = reference.data as XML; + var bytes:ByteArray = reference.data as ByteArray; + + if (bytes) + { + try { xml = new XML(bytes); } + catch (e:Error) + { + onError("Could not parse XML: " + e.message); + return; + } + } + + var rootNode:String = xml.localName(); + + if (rootNode == "TextureAtlas") + helper.addPostProcessor(textureAtlasPostProcessor, 100); + else if (rootNode == "font") + helper.addPostProcessor(bitmapFontPostProcessor); + + onComplete(reference.name, xml); + + // prevent closures from keeping references + reference.data = bytes = null; + + function textureAtlasPostProcessor(store:AssetManager):void + { + var name:String = helper.getNameFromUrl(xml.@imagePath.toString()); + var texture:Texture = store.getTexture(name); + if (texture) store.addAsset(name, new TextureAtlas(texture, xml)); + else helper.log("Cannot create atlas: texture '" + name + "' is missing."); + } + + function bitmapFontPostProcessor(store:AssetManager):void + { + var textureName:String = helper.getNameFromUrl(xml.pages.page.@file.toString()); + var texture:Texture = store.getTexture(textureName); + var fontName:String = store.registerBitmapFontsWithFontFace ? + xml.info.@face.toString() : textureName; + + if (texture) + { + var bitmapFont:BitmapFont = new BitmapFont(texture, xml); + store.addAsset(fontName, bitmapFont); + TextField.registerCompositor(bitmapFont, fontName); + } + else helper.log("Cannot create bitmap font: texture '" + textureName + "' is missing."); + } + } + } +} diff --git a/mobile_version/src/starling/core/Starling.as b/mobile_version/src/starling/core/Starling.as new file mode 100644 index 00000000..042eedc1 --- /dev/null +++ b/mobile_version/src/starling/core/Starling.as @@ -0,0 +1,1051 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.core +{ + import flash.display.Shape; + import flash.display.Sprite; + import flash.display.Stage3D; + import flash.display.StageAlign; + import flash.display.StageScaleMode; + import flash.display3D.Context3D; + import flash.display3D.Context3DProfile; + import flash.errors.IllegalOperationError; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.events.TouchEvent; + import flash.geom.Rectangle; + import flash.system.Capabilities; + import flash.text.TextField; + import flash.text.TextFieldAutoSize; + import flash.text.TextFormat; + import flash.text.TextFormatAlign; + import flash.ui.Mouse; + import flash.ui.Multitouch; + import flash.ui.MultitouchInputMode; + import flash.utils.getTimer; + import flash.utils.setTimeout; + + import starling.animation.Juggler; + import starling.display.DisplayObject; + import starling.display.Stage; + import starling.events.EventDispatcher; + import starling.events.ResizeEvent; + import starling.events.TouchPhase; + import starling.events.TouchProcessor; + import starling.rendering.Painter; + import starling.utils.Align; + import starling.utils.RectangleUtil; + import starling.utils.SystemUtil; + + /** Dispatched when a new render context is created. The 'data' property references the context. */ + [Event(name="context3DCreate", type="starling.events.Event")] + + /** Dispatched when the root class has been created. The 'data' property references that object. */ + [Event(name="rootCreated", type="starling.events.Event")] + + /** Dispatched when a fatal error is encountered. The 'data' property contains an error string. */ + [Event(name="fatalError", type="starling.events.Event")] + + /** Dispatched when the display list is about to be rendered. This event provides the last + * opportunity to make changes before the display list is rendered. */ + [Event(name="render", type="starling.events.Event")] + + /** The Starling class represents the core of the Starling framework. + * + *

The Starling framework makes it possible to create 2D applications and games that make + * use of the Stage3D architecture introduced in Flash Player 11. It implements a display tree + * system that is very similar to that of conventional Flash, while leveraging modern GPUs + * to speed up rendering.

+ * + *

The Starling class represents the link between the conventional Flash display tree and + * the Starling display tree. To create a Starling-powered application, you have to create + * an instance of the Starling class:

+ * + *
var starling:Starling = new Starling(Game, stage);
+ * + *

The first parameter has to be a Starling display object class, e.g. a subclass of + * starling.display.Sprite. In the sample above, the class "Game" is the + * application root. An instance of "Game" will be created as soon as Starling is initialized. + * The second parameter is the conventional (Flash) stage object. Per default, Starling will + * display its contents directly below the stage.

+ * + *

It is recommended to store the Starling instance as a member variable, to make sure + * that the Garbage Collector does not destroy it. After creating the Starling object, you + * have to start it up like this:

+ * + *
starling.start();
+ * + *

It will now render the contents of the "Game" class in the frame rate that is set up for + * the application (as defined in the Flash stage).

+ * + * Context3D Profiles + * + *

Stage3D supports different rendering profiles, and Starling works with all of them. The + * last parameter of the Starling constructor allows you to choose which profile you want. + * The following profiles are available:

+ * + *
    + *
  • BASELINE_CONSTRAINED: provides the broadest hardware reach. If you develop for the + * browser, this is the profile you should test with.
  • + *
  • BASELINE: recommend for any mobile application, as it allows Starling to use a more + * memory efficient texture type (RectangleTextures). It also supports more complex + * AGAL code.
  • + *
  • BASELINE_EXTENDED: adds support for textures up to 4096x4096 pixels. This is + * especially useful on mobile devices with very high resolutions.
  • + *
  • STANDARD_CONSTRAINED, STANDARD, STANDARD_EXTENDED: each provide more AGAL features, + * among other things. Most Starling games will not gain much from them.
  • + *
+ * + *

The recommendation is to deploy your app with the profile "auto" (which makes Starling + * pick the best available of those), but to test it in all available profiles.

+ * + * Accessing the Starling object + * + *

From within your application, you can access the current Starling object anytime + * through the static method Starling.current. It will return the active Starling + * instance (most applications will only have one Starling object, anyway).

+ * + * Viewport + * + *

The area the Starling content is rendered into is, per default, the complete size of the + * stage. You can, however, use the "viewPort" property to change it. This can be useful + * when you want to render only into a part of the screen, or if the player size changes. For + * the latter, you can listen to the RESIZE-event dispatched by the Starling + * stage.

+ * + * Native overlay + * + *

Sometimes you will want to display native Flash content on top of Starling. That's what the + * nativeOverlay property is for. It returns a Flash Sprite lying directly + * on top of the Starling content. You can add conventional Flash objects to that overlay.

+ * + *

Beware, though, that conventional Flash content on top of 3D content can lead to + * performance penalties on some (mobile) platforms. For that reason, always remove all child + * objects from the overlay when you don't need them any longer.

+ * + * Multitouch + * + *

Starling supports multitouch input on devices that provide it. During development, + * where most of us are working with a conventional mouse and keyboard, Starling can simulate + * multitouch events with the help of the "Shift" and "Ctrl" (Mac: "Cmd") keys. Activate + * this feature by enabling the simulateMultitouch property.

+ * + * Skipping Unchanged Frames + * + *

It happens surprisingly often in an app or game that a scene stays completely static for + * several frames. So why redraw the stage at all in those situations? That's exactly the + * point of the skipUnchangedFrames-property. If enabled, static scenes are + * recognized as such and the back buffer is simply left as it is. On a mobile device, the + * impact of this feature can't be overestimated! There's simply no better way to enhance + * battery life. Make it a habit to always activate it; look at the documentation of the + * corresponding property for details.

+ * + * Handling a lost render context + * + *

On some operating systems and under certain conditions (e.g. returning from system + * sleep), Starling's stage3D render context may be lost. Starling will try to recover + * from a lost context automatically; to be able to do this, it will cache textures in + * RAM. This will take up quite a bit of extra memory, though, which might be problematic + * especially on mobile platforms. To avoid the higher memory footprint, it's recommended + * to load your textures with Starling's "AssetManager"; it is smart enough to recreate a + * texture directly from its origin.

+ * + *

In case you want to react to a context loss manually, Starling dispatches an event with + * the type "Event.CONTEXT3D_CREATE" when the context is restored, and textures will execute + * their root.onRestore callback, to which you can attach your own logic. + * Refer to the "Texture" class for more information.

+ * + * Sharing a 3D Context + * + *

Per default, Starling handles the Stage3D context itself. If you want to combine + * Starling with another Stage3D engine, however, this may not be what you want. In this case, + * you can make use of the shareContext property:

+ * + *
    + *
  1. Manually create and configure a context3D object that both frameworks can work with + * (ideally through RenderUtil.requestContext3D and + * context.configureBackBuffer).
  2. + *
  3. Initialize Starling with the stage3D instance that contains that configured context. + * This will automatically enable shareContext.
  4. + *
  5. Call start() on your Starling instance (as usual). This will make + * Starling queue input events (keyboard/mouse/touch).
  6. + *
  7. Create a game loop (e.g. using the native ENTER_FRAME event) and let it + * call Starling's nextFrame as well as the equivalent method of the other + * Stage3D engine. Surround those calls with context.clear() and + * context.present().
  8. + *
+ * + *

The Starling wiki contains a tutorial with more + * information about this topic.

+ * + * @see starling.utils.AssetManager + * @see starling.textures.Texture + * + */ + public class Starling extends EventDispatcher + { + /** The version of the Starling framework. */ + public static const VERSION:String = "2.1"; + + // members + + private var _stage:Stage; // starling.display.stage! + private var _rootClass:Class; + private var _root:DisplayObject; + private var _juggler:Juggler; + private var _painter:Painter; + private var _touchProcessor:TouchProcessor; + private var _antiAliasing:int; + private var _frameTimestamp:Number; + private var _frameID:uint; + private var _leftMouseDown:Boolean; + private var _statsDisplay:StatsDisplay; + private var _started:Boolean; + private var _rendering:Boolean; + private var _supportHighResolutions:Boolean; + private var _skipUnchangedFrames:Boolean; + private var _showStats:Boolean; + + private var _viewPort:Rectangle; + private var _previousViewPort:Rectangle; + private var _clippedViewPort:Rectangle; + + private var _nativeStage:flash.display.Stage; + private var _nativeStageEmpty:Boolean; + private var _nativeOverlay:Sprite; + + private static var sCurrent:Starling; + private static var sAll:Vector. = new []; + + // construction + + /** Creates a new Starling instance. + * @param rootClass A subclass of 'starling.display.DisplayObject'. It will be created + * as soon as initialization is finished and will become the first child + * of the Starling stage. Pass null if you don't want to + * create a root object right away. (You can use the + * rootClass property later to make that happen.) + * @param stage The Flash (2D) stage. + * @param viewPort A rectangle describing the area into which the content will be + * rendered. Default: stage size + * @param stage3D The Stage3D object into which the content will be rendered. If it + * already contains a context, sharedContext will be set + * to true. Default: the first available Stage3D. + * @param renderMode The Context3D render mode that should be requested. + * Use this parameter if you want to force "software" rendering. + * @param profile The Context3D profile that should be requested. + * + *
    + *
  • If you pass a profile String, this profile is enforced.
  • + *
  • Pass an Array of profiles to make Starling pick the first + * one that works (starting with the first array element).
  • + *
  • Pass the String "auto" to make Starling pick the best available + * profile automatically.
  • + *
+ */ + public function Starling(rootClass:Class, stage:flash.display.Stage, + viewPort:Rectangle=null, stage3D:Stage3D=null, + renderMode:String="auto", profile:Object="auto") + { + if (stage == null) throw new ArgumentError("Stage must not be null"); + if (viewPort == null) viewPort = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight); + if (stage3D == null) stage3D = stage.stage3Ds[0]; + + // TODO it might make sense to exchange the 'renderMode' and 'profile' parameters. + + SystemUtil.initialize(); + sAll.push(this); + makeCurrent(); + + _rootClass = rootClass; + _viewPort = viewPort; + _previousViewPort = new Rectangle(); + _stage = new Stage(viewPort.width, viewPort.height, stage.color); + _nativeOverlay = new Sprite(); + _nativeStage = stage; + _nativeStage.addChild(_nativeOverlay); + _touchProcessor = new TouchProcessor(_stage); + _juggler = new Juggler(); + _antiAliasing = 0; + _supportHighResolutions = false; + _painter = new Painter(stage3D); + _frameTimestamp = getTimer() / 1000.0; + _frameID = 1; + + // all other modes are problematic in Starling, so we force those here + stage.scaleMode = StageScaleMode.NO_SCALE; + stage.align = StageAlign.TOP_LEFT; + + // register touch/mouse event handlers + for each (var touchEventType:String in touchEventTypes) + stage.addEventListener(touchEventType, onTouch, false, 0, true); + + // register other event handlers + stage.addEventListener(Event.ENTER_FRAME, onEnterFrame, false, 0, true); + stage.addEventListener(KeyboardEvent.KEY_DOWN, onKey, false, 0, true); + stage.addEventListener(KeyboardEvent.KEY_UP, onKey, false, 0, true); + stage.addEventListener(Event.RESIZE, onResize, false, 0, true); + stage.addEventListener(Event.MOUSE_LEAVE, onMouseLeave, false, 0, true); + + stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreated, false, 10, true); + stage3D.addEventListener(ErrorEvent.ERROR, onStage3DError, false, 10, true); + + if (_painter.shareContext) + { + setTimeout(initialize, 1); // we don't call it right away, because Starling should + // behave the same way with or without a shared context + } + else + { + if (!SystemUtil.supportsDepthAndStencil) + trace("[Starling] Mask support requires 'depthAndStencil' to be enabled" + + " in the application descriptor."); + + _painter.requestContext3D(renderMode, profile); + } + } + + /** Disposes all children of the stage and the render context; removes all registered + * event listeners. */ + public function dispose():void + { + stop(true); + + _nativeStage.removeEventListener(Event.ENTER_FRAME, onEnterFrame, false); + _nativeStage.removeEventListener(KeyboardEvent.KEY_DOWN, onKey, false); + _nativeStage.removeEventListener(KeyboardEvent.KEY_UP, onKey, false); + _nativeStage.removeEventListener(Event.RESIZE, onResize, false); + _nativeStage.removeEventListener(Event.MOUSE_LEAVE, onMouseLeave, false); + _nativeStage.removeChild(_nativeOverlay); + + stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated, false); + stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextRestored, false); + stage3D.removeEventListener(ErrorEvent.ERROR, onStage3DError, false); + + for each (var touchEventType:String in touchEventTypes) + _nativeStage.removeEventListener(touchEventType, onTouch, false); + + _touchProcessor.dispose(); + _painter.dispose(); + _stage.dispose(); + + var index:int = sAll.indexOf(this); + if (index != -1) sAll.removeAt(index); + if (sCurrent == this) sCurrent = null; + } + + // functions + + private function initialize():void + { + makeCurrent(); + updateViewPort(true); + + // ideal time: after viewPort setup, before root creation + dispatchEventWith(Event.CONTEXT3D_CREATE, false, context); + + initializeRoot(); + _frameTimestamp = getTimer() / 1000.0; + } + + private function initializeRoot():void + { + if (_root == null && _rootClass != null) + { + _root = new _rootClass() as DisplayObject; + if (_root == null) throw new Error("Invalid root class: " + _rootClass); + _stage.addChildAt(_root, 0); + + dispatchEventWith(starling.events.Event.ROOT_CREATED, false, _root); + } + } + + /** Calls advanceTime() (with the time that has passed since the last frame) + * and render(). */ + public function nextFrame():void + { + var now:Number = getTimer() / 1000.0; + var passedTime:Number = now - _frameTimestamp; + _frameTimestamp = now; + + // to avoid overloading time-based animations, the maximum delta is truncated. + if (passedTime > 1.0) passedTime = 1.0; + + // after about 25 days, 'getTimer()' will roll over. A rare event, but still ... + if (passedTime < 0.0) passedTime = 1.0 / _nativeStage.frameRate; + + advanceTime(passedTime); + render(); + } + + /** Dispatches ENTER_FRAME events on the display list, advances the Juggler + * and processes touches. */ + public function advanceTime(passedTime:Number):void + { + if (!contextValid) + return; + + makeCurrent(); + + _touchProcessor.advanceTime(passedTime); + _stage.advanceTime(passedTime); + _juggler.advanceTime(passedTime); + } + + /** Renders the complete display list. Before rendering, the context is cleared; afterwards, + * it is presented (to avoid this, enable shareContext). + * + *

This method also dispatches an Event.RENDER-event on the Starling + * instance. That's the last opportunity to make changes before the display list is + * rendered.

*/ + public function render():void + { + if (!contextValid) + return; + + makeCurrent(); + updateViewPort(); + + var doRedraw:Boolean = _stage.requiresRedraw || mustAlwaysRender; + if (doRedraw) + { + dispatchEventWith(starling.events.Event.RENDER); + + var shareContext:Boolean = _painter.shareContext; + var scaleX:Number = _viewPort.width / _stage.stageWidth; + var scaleY:Number = _viewPort.height / _stage.stageHeight; + + _painter.nextFrame(); + _painter.pixelSize = 1.0 / contentScaleFactor; + _painter.state.setProjectionMatrix( + _viewPort.x < 0 ? -_viewPort.x / scaleX : 0.0, + _viewPort.y < 0 ? -_viewPort.y / scaleY : 0.0, + _clippedViewPort.width / scaleX, + _clippedViewPort.height / scaleY, + _stage.stageWidth, _stage.stageHeight, _stage.cameraPosition); + + if (!shareContext) + _painter.clear(_stage.color, 0.0); + + _stage.render(_painter); + _painter.finishFrame(); + _painter.frameID = ++_frameID; + + if (!shareContext) + _painter.present(); + } + + if (_statsDisplay) + { + _statsDisplay.drawCount = _painter.drawCount; + if (!doRedraw) _statsDisplay.markFrameAsSkipped(); + } + } + + private function updateViewPort(forceUpdate:Boolean=false):void + { + // the last set viewport is stored in a variable; that way, people can modify the + // viewPort directly (without a copy) and we still know if it has changed. + + if (forceUpdate || !RectangleUtil.compare(_viewPort, _previousViewPort)) + { + _previousViewPort.setTo(_viewPort.x, _viewPort.y, _viewPort.width, _viewPort.height); + + // Constrained mode requires that the viewport is within the native stage bounds; + // thus, we use a clipped viewport when configuring the back buffer. (In baseline + // mode, that's not necessary, but it does not hurt either.) + + _clippedViewPort = _viewPort.intersection( + new Rectangle(0, 0, _nativeStage.stageWidth, _nativeStage.stageHeight)); + + var contentScaleFactor:Number = + _supportHighResolutions ? _nativeStage.contentsScaleFactor : 1.0; + + _painter.configureBackBuffer(_clippedViewPort, contentScaleFactor, + _antiAliasing, true); + } + } + + private function updateNativeOverlay():void + { + _nativeOverlay.x = _viewPort.x; + _nativeOverlay.y = _viewPort.y; + _nativeOverlay.scaleX = _viewPort.width / _stage.stageWidth; + _nativeOverlay.scaleY = _viewPort.height / _stage.stageHeight; + } + + /** Stops Starling right away and displays an error message on the native overlay. + * This method will also cause Starling to dispatch a FATAL_ERROR event. */ + public function stopWithFatalError(message:String):void + { + var background:Shape = new Shape(); + background.graphics.beginFill(0x0, 0.8); + background.graphics.drawRect(0, 0, _stage.stageWidth, _stage.stageHeight); + background.graphics.endFill(); + + var textField:TextField = new TextField(); + var textFormat:TextFormat = new TextFormat("Verdana", 14, 0xFFFFFF); + textFormat.align = TextFormatAlign.CENTER; + textField.defaultTextFormat = textFormat; + textField.wordWrap = true; + textField.width = _stage.stageWidth * 0.75; + textField.autoSize = TextFieldAutoSize.CENTER; + textField.text = message; + textField.x = (_stage.stageWidth - textField.width) / 2; + textField.y = (_stage.stageHeight - textField.height) / 2; + textField.background = true; + textField.backgroundColor = 0x550000; + + updateNativeOverlay(); + nativeOverlay.addChild(background); + nativeOverlay.addChild(textField); + stop(true); + + trace("[Starling]", message); + dispatchEventWith(starling.events.Event.FATAL_ERROR, false, message); + } + + /** Make this Starling instance the current one. */ + public function makeCurrent():void + { + sCurrent = this; + } + + /** As soon as Starling is started, it will queue input events (keyboard/mouse/touch); + * furthermore, the method nextFrame will be called once per Flash Player + * frame. (Except when shareContext is enabled: in that case, you have to + * call that method manually.) */ + public function start():void + { + _started = _rendering = true; + _frameTimestamp = getTimer() / 1000.0; + + // mainly for Android: force redraw when app moves into foreground + setTimeout(setRequiresRedraw, 100); + } + + /** Stops all logic and input processing, effectively freezing the app in its current state. + * Per default, rendering will continue: that's because the classic display list + * is only updated when stage3D is. (If Starling stopped rendering, conventional Flash + * contents would freeze, as well.) + * + *

However, if you don't need classic Flash contents, you can stop rendering, too. + * On some mobile systems (e.g. iOS), you are even required to do so if you have + * activated background code execution.

+ */ + public function stop(suspendRendering:Boolean=false):void + { + _started = false; + _rendering = !suspendRendering; + } + + /** Makes sure that the next frame is actually rendered. + * + *

When skipUnchangedFrames is enabled, some situations require that you + * manually force a redraw, e.g. when a RenderTexture is changed. This method is the + * easiest way to do so; it's just a shortcut to stage.setRequiresRedraw(). + *

+ */ + public function setRequiresRedraw():void + { + _stage.setRequiresRedraw(); + } + + // event handlers + + private function onStage3DError(event:ErrorEvent):void + { + if (event.errorID == 3702) + { + var mode:String = Capabilities.playerType == "Desktop" ? "renderMode" : "wmode"; + stopWithFatalError("Context3D not available! Possible reasons: wrong " + mode + + " or missing device support."); + } + else + stopWithFatalError("Stage3D error: " + event.text); + } + + private function onContextCreated(event:Event):void + { + stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated); + stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextRestored, false, 10, true); + + trace("[Starling] Context ready. Display Driver:", context.driverInfo); + initialize(); + } + + private function onContextRestored(event:Event):void + { + trace("[Starling] Context restored."); + updateViewPort(true); + dispatchEventWith(Event.CONTEXT3D_CREATE, false, context); + } + + private function onEnterFrame(event:Event):void + { + // On mobile, the native display list is only updated on stage3D draw calls. + // Thus, we render even when Starling is paused. + + if (!shareContext) + { + if (_started) nextFrame(); + else if (_rendering) render(); + } + + updateNativeOverlay(); + } + + private function onKey(event:KeyboardEvent):void + { + if (!_started) return; + + var keyEvent:starling.events.KeyboardEvent = new starling.events.KeyboardEvent( + event.type, event.charCode, event.keyCode, event.keyLocation, + event.ctrlKey, event.altKey, event.shiftKey); + + makeCurrent(); + _stage.dispatchEvent(keyEvent); + + if (keyEvent.isDefaultPrevented()) + event.preventDefault(); + } + + private function onResize(event:Event):void + { + var stageWidth:int = event.target.stageWidth; + var stageHeight:int = event.target.stageHeight; + + if (contextValid) + dispatchResizeEvent(); + else + addEventListener(Event.CONTEXT3D_CREATE, dispatchResizeEvent); + + function dispatchResizeEvent():void + { + // on Android, the context is not valid while we're resizing. To avoid problems + // with user code, we delay the event dispatching until it becomes valid again. + + makeCurrent(); + removeEventListener(Event.CONTEXT3D_CREATE, dispatchResizeEvent); + _stage.dispatchEvent(new ResizeEvent(Event.RESIZE, stageWidth, stageHeight)); + } + } + + private function onMouseLeave(event:Event):void + { + _touchProcessor.enqueueMouseLeftStage(); + } + + private function onTouch(event:Event):void + { + if (!_started) return; + + var globalX:Number; + var globalY:Number; + var touchID:int; + var phase:String; + var pressure:Number = 1.0; + var width:Number = 1.0; + var height:Number = 1.0; + + // figure out general touch properties + if (event is MouseEvent) + { + var mouseEvent:MouseEvent = event as MouseEvent; + globalX = mouseEvent.stageX; + globalY = mouseEvent.stageY; + touchID = 0; + + // MouseEvent.buttonDown returns true for both left and right button (AIR supports + // the right mouse button). We only want to react on the left button for now, + // so we have to save the state for the left button manually. + if (event.type == MouseEvent.MOUSE_DOWN) _leftMouseDown = true; + else if (event.type == MouseEvent.MOUSE_UP) _leftMouseDown = false; + } + else + { + var touchEvent:TouchEvent = event as TouchEvent; + + // On a system that supports both mouse and touch input, the primary touch point + // is dispatched as mouse event as well. Since we don't want to listen to that + // event twice, we ignore the primary touch in that case. + + if (Mouse.supportsCursor && touchEvent.isPrimaryTouchPoint) return; + else + { + globalX = touchEvent.stageX; + globalY = touchEvent.stageY; + touchID = touchEvent.touchPointID; + pressure = touchEvent.pressure; + width = touchEvent.sizeX; + height = touchEvent.sizeY; + } + } + + // figure out touch phase + switch (event.type) + { + case TouchEvent.TOUCH_BEGIN: phase = TouchPhase.BEGAN; break; + case TouchEvent.TOUCH_MOVE: phase = TouchPhase.MOVED; break; + case TouchEvent.TOUCH_END: phase = TouchPhase.ENDED; break; + case MouseEvent.MOUSE_DOWN: phase = TouchPhase.BEGAN; break; + case MouseEvent.MOUSE_UP: phase = TouchPhase.ENDED; break; + case MouseEvent.MOUSE_MOVE: + phase = (_leftMouseDown ? TouchPhase.MOVED : TouchPhase.HOVER); break; + } + + // move position into viewport bounds + globalX = _stage.stageWidth * (globalX - _viewPort.x) / _viewPort.width; + globalY = _stage.stageHeight * (globalY - _viewPort.y) / _viewPort.height; + + // enqueue touch in touch processor + _touchProcessor.enqueue(touchID, phase, globalX, globalY, pressure, width, height); + + // allow objects that depend on mouse-over state to be updated immediately + if (event.type == MouseEvent.MOUSE_UP && Mouse.supportsCursor) + _touchProcessor.enqueue(touchID, TouchPhase.HOVER, globalX, globalY); + } + + private function get touchEventTypes():Array + { + var types:Array = []; + + if (multitouchEnabled) + types.push(TouchEvent.TOUCH_BEGIN, TouchEvent.TOUCH_MOVE, TouchEvent.TOUCH_END); + + if (!multitouchEnabled || Mouse.supportsCursor) + types.push(MouseEvent.MOUSE_DOWN, MouseEvent.MOUSE_MOVE, MouseEvent.MOUSE_UP); + + return types; + } + + private function get mustAlwaysRender():Boolean + { + // On mobile, and in some browsers with the "baselineConstrained" profile, the + // standard display list is only rendered after calling "context.present()". + // In such a case, we cannot omit frames if there is any content on the stage. + + if (!_skipUnchangedFrames || _painter.shareContext) + return true; + else if (SystemUtil.isDesktop && profile != Context3DProfile.BASELINE_CONSTRAINED) + return false; + else + { + // Rendering can be skipped when both this and previous frame are empty. + var nativeStageEmpty:Boolean = isNativeDisplayObjectEmpty(_nativeStage); + var mustAlwaysRender:Boolean = !nativeStageEmpty || !_nativeStageEmpty; + _nativeStageEmpty = nativeStageEmpty; + + return mustAlwaysRender; + } + } + + // properties + + /** Indicates if this Starling instance is started. */ + public function get isStarted():Boolean { return _started; } + + /** The default juggler of this instance. Will be advanced once per frame. */ + public function get juggler():Juggler { return _juggler; } + + /** The painter, which is used for all rendering. The same instance is passed to all + * rendermethods each frame. */ + public function get painter():Painter { return _painter; } + + /** The render context of this instance. */ + public function get context():Context3D { return _painter.context; } + + /** Indicates if multitouch simulation with "Shift" and "Ctrl"/"Cmd"-keys is enabled. + * @default false */ + public function get simulateMultitouch():Boolean { return _touchProcessor.simulateMultitouch; } + public function set simulateMultitouch(value:Boolean):void + { + _touchProcessor.simulateMultitouch = value; + } + + /** Indicates if Stage3D render methods will report errors. It's recommended to activate + * this when writing custom rendering code (shaders, etc.), since you'll get more detailed + * error messages. However, it has a very negative impact on performance, and it prevents + * ATF textures from being restored on a context loss. Never activate for release builds! + * + * @default false */ + public function get enableErrorChecking():Boolean { return _painter.enableErrorChecking; } + public function set enableErrorChecking(value:Boolean):void + { + _painter.enableErrorChecking = value; + } + + /** The anti-aliasing level. 0 - none, 16 - maximum. @default 0 */ + public function get antiAliasing():int { return _antiAliasing; } + public function set antiAliasing(value:int):void + { + if (_antiAliasing != value) + { + _antiAliasing = value; + if (contextValid) updateViewPort(true); + } + } + + /** The viewport into which Starling contents will be rendered. */ + public function get viewPort():Rectangle { return _viewPort; } + public function set viewPort(value:Rectangle):void { _viewPort = value.clone(); } + + /** The ratio between viewPort width and stage width. Useful for choosing a different + * set of textures depending on the display resolution. */ + public function get contentScaleFactor():Number + { + return (_viewPort.width * _painter.backBufferScaleFactor) / _stage.stageWidth; + } + + /** A Flash Sprite placed directly on top of the Starling content. Use it to display native + * Flash components. */ + public function get nativeOverlay():Sprite { return _nativeOverlay; } + + /** Indicates if a small statistics box (with FPS, memory usage and draw count) is + * displayed. + * + *

When the box turns dark green, more than 50% of the frames since the box' last + * update could skip rendering altogether. This will only happen if the property + * skipUnchangedFrames is enabled.

+ * + *

Beware that the memory usage should be taken with a grain of salt. The value is + * determined via System.totalMemory and does not take texture memory + * into account. It is recommended to use Adobe Scout for reliable and comprehensive + * memory analysis.

+ */ + public function get showStats():Boolean { return _showStats; } + public function set showStats(value:Boolean):void + { + _showStats = value; + + if (value) + { + if (_statsDisplay) _stage.addChild(_statsDisplay); + else showStatsAt(); + } + else if (_statsDisplay) + { + _statsDisplay.removeFromParent(); + } + } + + /** Displays the statistics box at a certain position. */ + public function showStatsAt(horizontalAlign:String="left", + verticalAlign:String="top", scale:Number=1):void + { + _showStats = true; + + if (context == null) + { + // Starling is not yet ready - we postpone this until it's initialized. + addEventListener(starling.events.Event.ROOT_CREATED, onRootCreated); + } + else + { + var stageWidth:int = _stage.stageWidth; + var stageHeight:int = _stage.stageHeight; + + if (_statsDisplay == null) + { + _statsDisplay = new StatsDisplay(); + _statsDisplay.touchable = false; + } + + _stage.addChild(_statsDisplay); + _statsDisplay.scaleX = _statsDisplay.scaleY = scale; + + if (horizontalAlign == Align.LEFT) _statsDisplay.x = 0; + else if (horizontalAlign == Align.RIGHT) _statsDisplay.x = stageWidth - _statsDisplay.width; + else if (horizontalAlign == Align.CENTER) _statsDisplay.x = (stageWidth - _statsDisplay.width) / 2; + else throw new ArgumentError("Invalid horizontal alignment: " + horizontalAlign); + + if (verticalAlign == Align.TOP) _statsDisplay.y = 0; + else if (verticalAlign == Align.BOTTOM) _statsDisplay.y = stageHeight - _statsDisplay.height; + else if (verticalAlign == Align.CENTER) _statsDisplay.y = (stageHeight - _statsDisplay.height) / 2; + else throw new ArgumentError("Invalid vertical alignment: " + verticalAlign); + } + + function onRootCreated():void + { + if (_showStats) showStatsAt(horizontalAlign, verticalAlign, scale); + removeEventListener(starling.events.Event.ROOT_CREATED, onRootCreated); + } + } + + /** The Starling stage object, which is the root of the display tree that is rendered. */ + public function get stage():Stage { return _stage; } + + /** The Flash Stage3D object Starling renders into. */ + public function get stage3D():Stage3D { return _painter.stage3D; } + + /** The Flash (2D) stage object Starling renders beneath. */ + public function get nativeStage():flash.display.Stage { return _nativeStage; } + + /** The instance of the root class provided in the constructor. Available as soon as + * the event 'ROOT_CREATED' has been dispatched. */ + public function get root():DisplayObject { return _root; } + + /** The class that will be instantiated by Starling as the 'root' display object. + * Must be a subclass of 'starling.display.DisplayObject'. + * + *

If you passed null as first parameter to the Starling constructor, + * you can use this property to set the root class at a later time. As soon as the class + * is instantiated, Starling will dispatch a ROOT_CREATED event.

+ * + *

Beware: you cannot change the root class once the root object has been + * instantiated.

+ */ + public function get rootClass():Class { return _rootClass; } + public function set rootClass(value:Class):void + { + if (_rootClass != null && _root != null) + throw new Error("Root class may not change after root has been instantiated"); + else if (_rootClass == null) + { + _rootClass = value; + if (context) initializeRoot(); + } + } + + /** Indicates if another Starling instance (or another Stage3D framework altogether) + * uses the same render context. If enabled, Starling will not execute any destructive + * context operations (e.g. not call 'configureBackBuffer', 'clear', 'present', etc. + * This has to be done manually, then. @default false */ + public function get shareContext() : Boolean { return _painter.shareContext; } + public function set shareContext(value : Boolean) : void { _painter.shareContext = value; } + + /** The Context3D profile of the current render context, or null + * if the context has not been created yet. */ + public function get profile():String { return _painter.profile; } + + /** Indicates that if the device supports HiDPI screens Starling will attempt to allocate + * a larger back buffer than indicated via the viewPort size. Note that this is used + * on Desktop only; mobile AIR apps still use the "requestedDisplayResolution" parameter + * the application descriptor XML. @default false */ + public function get supportHighResolutions():Boolean { return _supportHighResolutions; } + public function set supportHighResolutions(value:Boolean):void + { + if (_supportHighResolutions != value) + { + _supportHighResolutions = value; + if (contextValid) updateViewPort(true); + } + } + + /** When enabled, Starling will skip rendering the stage if it hasn't changed since the + * last frame. This is great for apps that remain static from time to time, since it will + * greatly reduce power consumption. You should activate this whenever possible! + * + *

The reason why it's disabled by default is just that it causes problems with Render- + * and VideoTextures. When you use those, you either have to disable this property + * temporarily, or call setRequiresRedraw() (ideally on the stage) whenever + * those textures are changing. Otherwise, the changes won't show up.

+ * + * @default false + */ + public function get skipUnchangedFrames():Boolean { return _skipUnchangedFrames; } + public function set skipUnchangedFrames(value:Boolean):void + { + _skipUnchangedFrames = value; + _nativeStageEmpty = false; // required by 'mustAlwaysRender' + } + + /** The TouchProcessor is passed all mouse and touch input and is responsible for + * dispatching TouchEvents to the Starling display tree. If you want to handle these + * types of input manually, pass your own custom subclass to this property. */ + public function get touchProcessor():TouchProcessor { return _touchProcessor; } + public function set touchProcessor(value:TouchProcessor):void + { + if (value == null) throw new ArgumentError("TouchProcessor must not be null"); + else if (value != _touchProcessor) + { + _touchProcessor.dispose(); + _touchProcessor = value; + } + } + + /** The number of frames that have been rendered since this instance was created. */ + public function get frameID():uint { return _frameID; } + + /** Indicates if the Context3D object is currently valid (i.e. it hasn't been lost or + * disposed). */ + public function get contextValid():Boolean { return _painter.contextValid; } + + // static properties + + /** The currently active Starling instance. */ + public static function get current():Starling { return sCurrent; } + + /** All Starling instances.

CAUTION: not a copy, but the actual object! Do not modify!

*/ + public static function get all():Vector. { return sAll; } + + /** The render context of the currently active Starling instance. */ + public static function get context():Context3D { return sCurrent ? sCurrent.context : null; } + + /** The default juggler of the currently active Starling instance. */ + public static function get juggler():Juggler { return sCurrent ? sCurrent._juggler : null; } + + /** The painter used for all rendering of the currently active Starling instance. */ + public static function get painter():Painter { return sCurrent ? sCurrent._painter : null; } + + /** The contentScaleFactor of the currently active Starling instance. */ + public static function get contentScaleFactor():Number + { + return sCurrent ? sCurrent.contentScaleFactor : 1.0; + } + + /** Indicates if multitouch input should be supported. */ + public static function get multitouchEnabled():Boolean + { + return Multitouch.inputMode == MultitouchInputMode.TOUCH_POINT; + } + + public static function set multitouchEnabled(value:Boolean):void + { + if (sCurrent) throw new IllegalOperationError( + "'multitouchEnabled' must be set before Starling instance is created"); + else + Multitouch.inputMode = value ? MultitouchInputMode.TOUCH_POINT : + MultitouchInputMode.NONE; + } + + /** The number of frames that have been rendered since the current instance was created. */ + public static function get frameID():uint + { + return sCurrent ? sCurrent._frameID : 0; + } + } +} + +import flash.display.DisplayObject; +import flash.display.DisplayObjectContainer; + +// put here to avoid naming conflicts +function isNativeDisplayObjectEmpty(object:DisplayObject):Boolean +{ + if (object == null) return true; + else if (object is DisplayObjectContainer) + { + var container:DisplayObjectContainer = object as DisplayObjectContainer; + var numChildren:int = container.numChildren; + + for (var i:int=0; i UPDATE_INTERVAL) + { + update(); + _frameCount = _skipCount = _totalTime = 0; + } + } + + /** Updates the displayed values. */ + public function update():void + { + _background.color = _skipCount > _frameCount / 2 ? 0x003F00 : 0x0; + _fps = _totalTime > 0 ? _frameCount / _totalTime : 0; + _memory = System.totalMemory * B_TO_MB; + _gpuMemory = supportsGpuMem ? Starling.context['totalGPUMemory'] * B_TO_MB : -1; + + var fpsText:String = _fps.toFixed(_fps < 100 ? 1 : 0); + var memText:String = _memory.toFixed(_memory < 100 ? 1 : 0); + var gpuMemText:String = _gpuMemory.toFixed(_gpuMemory < 100 ? 1 : 0); + var drwText:String = (_totalTime > 0 ? _drawCount-2 : _drawCount).toString(); // ignore self + + _values.text = fpsText + "\n" + memText + "\n" + + (_gpuMemory >= 0 ? gpuMemText + "\n" : "") + drwText; + } + + /** Call this once in every frame that can skip rendering because nothing changed. */ + public function markFrameAsSkipped():void + { + _skipCount += 1; + } + + public override function render(painter:Painter):void + { + // By calling 'finishQuadBatch' and 'excludeFromCache', we can make sure that the stats + // display is always rendered with exactly two draw calls. That is taken into account + // when showing the drawCount value (see 'ignore self' comment above) + + painter.excludeFromCache(this); + painter.finishMeshBatch(); + super.render(painter); + } + + /** Indicates if the current runtime supports the 'totalGPUMemory' API. */ + private function get supportsGpuMem():Boolean + { + return "totalGPUMemory" in Starling.context; + } + + /** The number of Stage3D draw calls per second. */ + public function get drawCount():int { return _drawCount; } + public function set drawCount(value:int):void { _drawCount = value; } + + /** The current frames per second (updated twice per second). */ + public function get fps():Number { return _fps; } + public function set fps(value:Number):void { _fps = value; } + + /** The currently used system memory in MB. */ + public function get memory():Number { return _memory; } + public function set memory(value:Number):void { _memory = value; } + + /** The currently used graphics memory in MB. */ + public function get gpuMemory():Number { return _gpuMemory; } + public function set gpuMemory(value:Number):void { _gpuMemory = value; } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/core/starling_internal.as b/mobile_version/src/starling/core/starling_internal.as new file mode 100644 index 00000000..2fa3be73 --- /dev/null +++ b/mobile_version/src/starling/core/starling_internal.as @@ -0,0 +1,22 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.core +{ + /** + * This namespace is used for undocumented APIs -- usually implementation + * details -- which can't be private because they need to visible + * to other classes. + * + * APIs in this namespace are completely unsupported and are likely to + * change in future versions of Starling. + */ + public namespace starling_internal; +} \ No newline at end of file diff --git a/mobile_version/src/starling/display/BlendMode.as b/mobile_version/src/starling/display/BlendMode.as new file mode 100644 index 00000000..3794e2df --- /dev/null +++ b/mobile_version/src/starling/display/BlendMode.as @@ -0,0 +1,136 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.display +{ + import flash.display3D.Context3DBlendFactor; + + import starling.core.Starling; + + /** A class that provides constant values for visual blend mode effects. + * + *

A blend mode is always defined by two 'Context3DBlendFactor' values. A blend factor + * represents a particular four-value vector that is multiplied with the source or destination + * color in the blending formula. The blending formula is:

+ * + *
result = source × sourceFactor + destination × destinationFactor
+ * + *

In the formula, the source color is the output color of the pixel shader program. The + * destination color is the color that currently exists in the color buffer, as set by + * previous clear and draw operations.

+ * + *

You can add your own blend modes via BlendMode.register. + * To get the math right, remember that all colors in Starling use premultiplied alpha (PMA), + * which means that their RGB values were multiplied with the alpha value.

+ * + * @see flash.display3D.Context3DBlendFactor + */ + public class BlendMode + { + private var _name:String; + private var _sourceFactor:String; + private var _destinationFactor:String; + + private static var sBlendModes:Object; + + /** Creates a new BlendMode instance. Don't call this method directly; instead, + * register a new blend mode using BlendMode.register. */ + public function BlendMode(name:String, sourceFactor:String, destinationFactor:String) + { + _name = name; + _sourceFactor = sourceFactor; + _destinationFactor = destinationFactor; + } + + /** Inherits the blend mode from this display object's parent. */ + public static const AUTO:String = "auto"; + + /** Deactivates blending, i.e. disabling any transparency. */ + public static const NONE:String = "none"; + + /** The display object appears in front of the background. */ + public static const NORMAL:String = "normal"; + + /** Adds the values of the colors of the display object to the colors of its background. */ + public static const ADD:String = "add"; + + /** Multiplies the values of the display object colors with the the background color. */ + public static const MULTIPLY:String = "multiply"; + + /** Multiplies the complement (inverse) of the display object color with the complement of + * the background color, resulting in a bleaching effect. */ + public static const SCREEN:String = "screen"; + + /** Erases the background when drawn on a RenderTexture. */ + public static const ERASE:String = "erase"; + + /** When used on a RenderTexture, the drawn object will act as a mask for the current + * content, i.e. the source alpha overwrites the destination alpha. */ + public static const MASK:String = "mask"; + + /** Draws under/below existing objects; useful especially on RenderTextures. */ + public static const BELOW:String = "below"; + + // static access methods + + /** Returns the blend mode with the given name. + * Throws an ArgumentError if the mode does not exist. */ + public static function get(modeName:String):BlendMode + { + if (sBlendModes == null) registerDefaults(); + if (modeName in sBlendModes) return sBlendModes[modeName]; + else throw new ArgumentError("Blend mode not found: " + modeName); + } + + /** Registers a blending mode under a certain name. */ + public static function register(name:String, srcFactor:String, dstFactor:String):BlendMode + { + if (sBlendModes == null) registerDefaults(); + var blendMode:BlendMode = new BlendMode(name, srcFactor, dstFactor); + sBlendModes[name] = blendMode; + return blendMode; + } + + private static function registerDefaults():void + { + if (sBlendModes) return; + + sBlendModes = {}; + register("none" , Context3DBlendFactor.ONE, Context3DBlendFactor.ZERO); + register("normal", Context3DBlendFactor.ONE, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA); + register("add", Context3DBlendFactor.ONE, Context3DBlendFactor.ONE); + register("multiply", Context3DBlendFactor.DESTINATION_COLOR, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA); + register("screen", Context3DBlendFactor.ONE, Context3DBlendFactor.ONE_MINUS_SOURCE_COLOR); + register("erase", Context3DBlendFactor.ZERO, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA); + register("mask", Context3DBlendFactor.ZERO, Context3DBlendFactor.SOURCE_ALPHA); + register("below", Context3DBlendFactor.ONE_MINUS_DESTINATION_ALPHA, Context3DBlendFactor.DESTINATION_ALPHA); + } + + // instance methods / properties + + /** Sets the appropriate blend factors for source and destination on the current context. */ + public function activate():void + { + Starling.context.setBlendFactors(_sourceFactor, _destinationFactor); + } + + /** Returns the name of the blend mode. */ + public function toString():String { return _name; } + + /** The source blend factor of this blend mode. */ + public function get sourceFactor():String { return _sourceFactor; } + + /** The destination blend factor of this blend mode. */ + public function get destinationFactor():String { return _destinationFactor; } + + /** Returns the name of the blend mode. */ + public function get name():String { return _name; } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/display/Button.as b/mobile_version/src/starling/display/Button.as new file mode 100644 index 00000000..54c84330 --- /dev/null +++ b/mobile_version/src/starling/display/Button.as @@ -0,0 +1,455 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.display +{ + import flash.geom.Rectangle; + import flash.ui.Mouse; + import flash.ui.MouseCursor; + + import starling.events.Event; + import starling.events.Touch; + import starling.events.TouchEvent; + import starling.events.TouchPhase; + import starling.styles.MeshStyle; + import starling.text.TextField; + import starling.text.TextFormat; + import starling.textures.Texture; + + /** Dispatched when the user triggers the button. Bubbles. */ + [Event(name="triggered", type="starling.events.Event")] + + /** A simple button composed of an image and, optionally, text. + * + *

You can use different textures for various states of the button. If you're providing + * only an up state, the button is simply scaled a little when it is touched.

+ * + *

In addition, you can overlay text on the button. To customize the text, you can use + * properties equivalent to those of the TextField class. Move the text to a certain position + * by updating the textBounds property.

+ * + *

To react on touches on a button, there is special Event.TRIGGERED event. + * Use this event instead of normal touch events. That way, users can cancel button + * activation by moving the mouse/finger away from the button before releasing.

+ */ + public class Button extends DisplayObjectContainer + { + private static const MAX_DRAG_DIST:Number = 50; + + private var _upState:Texture; + private var _downState:Texture; + private var _overState:Texture; + private var _disabledState:Texture; + + private var _contents:Sprite; + private var _body:Image; + private var _textField:TextField; + private var _textBounds:Rectangle; + private var _overlay:Sprite; + + private var _scaleWhenDown:Number; + private var _scaleWhenOver:Number; + private var _alphaWhenDown:Number; + private var _alphaWhenDisabled:Number; + private var _useHandCursor:Boolean; + private var _enabled:Boolean; + private var _state:String; + private var _triggerBounds:Rectangle; + + /** Creates a button with a set of state-textures and (optionally) some text. + * Any state that is left 'null' will display the up-state texture. Beware that all + * state textures should have the same dimensions. */ + public function Button(upState:Texture, text:String="", downState:Texture=null, + overState:Texture=null, disabledState:Texture=null) + { + if (upState == null) throw new ArgumentError("Texture 'upState' cannot be null"); + + _upState = upState; + _downState = downState; + _overState = overState; + _disabledState = disabledState; + + _state = ButtonState.UP; + _body = new Image(upState); + _body.pixelSnapping = true; + _scaleWhenDown = downState ? 1.0 : 0.9; + _scaleWhenOver = _alphaWhenDown = 1.0; + _alphaWhenDisabled = disabledState ? 1.0: 0.5; + _enabled = true; + _useHandCursor = true; + _textBounds = new Rectangle(0, 0, _body.width, _body.height); + _triggerBounds = new Rectangle(); + + _contents = new Sprite(); + _contents.addChild(_body); + addChild(_contents); + addEventListener(TouchEvent.TOUCH, onTouch); + + this.touchGroup = true; + this.text = text; + } + + /** @inheritDoc */ + public override function dispose():void + { + // text field might be disconnected from parent, so we have to dispose it manually + if (_textField) + _textField.dispose(); + + super.dispose(); + } + + /** Readjusts the dimensions of the button according to its current state texture. + * Call this method to synchronize button and texture size after assigning a texture + * with a different size. */ + public function readjustSize():void + { + var prevWidth:Number = _body.width; + var prevHeight:Number = _body.height; + + _body.readjustSize(); + + var scaleX:Number = _body.width / prevWidth; + var scaleY:Number = _body.height / prevHeight; + + _textBounds.x *= scaleX; + _textBounds.y *= scaleY; + _textBounds.width *= scaleX; + _textBounds.height *= scaleY; + + if (_textField) createTextField(); + } + + private function createTextField():void + { + if (_textField == null) + { + _textField = new TextField(_textBounds.width, _textBounds.height); + _textField.pixelSnapping = _body.pixelSnapping; + _textField.touchable = false; + _textField.autoScale = true; + _textField.batchable = true; + } + + _textField.width = _textBounds.width; + _textField.height = _textBounds.height; + _textField.x = _textBounds.x; + _textField.y = _textBounds.y; + } + + private function onTouch(event:TouchEvent):void + { + Mouse.cursor = (_useHandCursor && _enabled && event.interactsWith(this)) ? + MouseCursor.BUTTON : MouseCursor.AUTO; + + var touch:Touch = event.getTouch(this); + var isWithinBounds:Boolean; + + if (!_enabled) + { + return; + } + else if (touch == null) + { + state = ButtonState.UP; + } + else if (touch.phase == TouchPhase.HOVER) + { + state = ButtonState.OVER; + } + else if (touch.phase == TouchPhase.BEGAN && _state != ButtonState.DOWN) + { + _triggerBounds = getBounds(stage, _triggerBounds); + _triggerBounds.inflate(MAX_DRAG_DIST, MAX_DRAG_DIST); + + state = ButtonState.DOWN; + } + else if (touch.phase == TouchPhase.MOVED) + { + isWithinBounds = _triggerBounds.contains(touch.globalX, touch.globalY); + + if (_state == ButtonState.DOWN && !isWithinBounds) + { + // reset button when finger is moved too far away ... + state = ButtonState.UP; + } + else if (_state == ButtonState.UP && isWithinBounds) + { + // ... and reactivate when the finger moves back into the bounds. + state = ButtonState.DOWN; + } + } + else if (touch.phase == TouchPhase.ENDED && _state == ButtonState.DOWN) + { + state = ButtonState.UP; + if (!touch.cancelled) dispatchEventWith(Event.TRIGGERED, true); + } + } + + /** The current state of the button. The corresponding strings are found + * in the ButtonState class. */ + public function get state():String { return _state; } + public function set state(value:String):void + { + _state = value; + _contents.x = _contents.y = 0; + _contents.scaleX = _contents.scaleY = _contents.alpha = 1.0; + + switch (_state) + { + case ButtonState.DOWN: + setStateTexture(_downState); + _contents.alpha = _alphaWhenDown; + _contents.scaleX = _contents.scaleY = _scaleWhenDown; + _contents.x = (1.0 - _scaleWhenDown) / 2.0 * _body.width; + _contents.y = (1.0 - _scaleWhenDown) / 2.0 * _body.height; + break; + case ButtonState.UP: + setStateTexture(_upState); + break; + case ButtonState.OVER: + setStateTexture(_overState); + _contents.scaleX = _contents.scaleY = _scaleWhenOver; + _contents.x = (1.0 - _scaleWhenOver) / 2.0 * _body.width; + _contents.y = (1.0 - _scaleWhenOver) / 2.0 * _body.height; + break; + case ButtonState.DISABLED: + setStateTexture(_disabledState); + _contents.alpha = _alphaWhenDisabled; + break; + default: + throw new ArgumentError("Invalid button state: " + _state); + } + } + + private function setStateTexture(texture:Texture):void + { + _body.texture = texture ? texture : _upState; + } + + /** The scale factor of the button on touch. Per default, a button without a down state + * texture will be made slightly smaller, while a button with a down state texture + * remains unscaled. */ + public function get scaleWhenDown():Number { return _scaleWhenDown; } + public function set scaleWhenDown(value:Number):void { _scaleWhenDown = value; } + + /** The scale factor of the button while the mouse cursor hovers over it. @default 1.0 */ + public function get scaleWhenOver():Number { return _scaleWhenOver; } + public function set scaleWhenOver(value:Number):void { _scaleWhenOver = value; } + + /** The alpha value of the button on touch. @default 1.0 */ + public function get alphaWhenDown():Number { return _alphaWhenDown; } + public function set alphaWhenDown(value:Number):void { _alphaWhenDown = value; } + + /** The alpha value of the button when it is disabled. @default 0.5 */ + public function get alphaWhenDisabled():Number { return _alphaWhenDisabled; } + public function set alphaWhenDisabled(value:Number):void { _alphaWhenDisabled = value; } + + /** Indicates if the button can be triggered. */ + public function get enabled():Boolean { return _enabled; } + public function set enabled(value:Boolean):void + { + if (_enabled != value) + { + _enabled = value; + state = value ? ButtonState.UP : ButtonState.DISABLED; + } + } + + /** The text that is displayed on the button. */ + public function get text():String { return _textField ? _textField.text : ""; } + public function set text(value:String):void + { + if (value.length == 0) + { + if (_textField) + { + _textField.text = value; + _textField.removeFromParent(); + } + } + else + { + createTextField(); + _textField.text = value; + + if (_textField.parent == null) + _contents.addChild(_textField); + } + } + + /** The format of the button's TextField. */ + public function get textFormat():TextFormat + { + if (_textField == null) createTextField(); + return _textField.format; + } + + public function set textFormat(value:TextFormat):void + { + if (_textField == null) createTextField(); + _textField.format = value; + } + + /** The style that is used to render the button's TextField. */ + public function get textStyle():MeshStyle + { + if (_textField == null) createTextField(); + return _textField.style; + } + + public function set textStyle(value:MeshStyle):void + { + if (_textField == null) createTextField(); + _textField.style = value; + } + + /** The style that is used to render the Button. */ + public function get style():MeshStyle { return _body.style; } + public function set style(value:MeshStyle):void { _body.style = value; } + + /** The texture that is displayed when the button is not being touched. */ + public function get upState():Texture { return _upState; } + public function set upState(value:Texture):void + { + if (value == null) + throw new ArgumentError("Texture 'upState' cannot be null"); + + if (_upState != value) + { + _upState = value; + if ( _state == ButtonState.UP || + (_state == ButtonState.DISABLED && _disabledState == null) || + (_state == ButtonState.DOWN && _downState == null) || + (_state == ButtonState.OVER && _overState == null)) + { + setStateTexture(value); + } + } + } + + /** The texture that is displayed while the button is touched. */ + public function get downState():Texture { return _downState; } + public function set downState(value:Texture):void + { + if (_downState != value) + { + _downState = value; + if (_state == ButtonState.DOWN) setStateTexture(value); + } + } + + /** The texture that is displayed while mouse hovers over the button. */ + public function get overState():Texture { return _overState; } + public function set overState(value:Texture):void + { + if (_overState != value) + { + _overState = value; + if (_state == ButtonState.OVER) setStateTexture(value); + } + } + + /** The texture that is displayed when the button is disabled. */ + public function get disabledState():Texture { return _disabledState; } + public function set disabledState(value:Texture):void + { + if (_disabledState != value) + { + _disabledState = value; + if (_state == ButtonState.DISABLED) setStateTexture(value); + } + } + + /** The bounds of the button's TextField. Allows moving the text to a custom position. + * CAUTION: not a copy, but the actual object! Text will only update on re-assignment. + */ + public function get textBounds():Rectangle { return _textBounds; } + public function set textBounds(value:Rectangle):void + { + _textBounds.copyFrom(value); + createTextField(); + } + + /** The color of the button's state image. Just like every image object, each pixel's + * color is multiplied with this value. @default white */ + public function get color():uint { return _body.color; } + public function set color(value:uint):void { _body.color = value; } + + /** The smoothing type used for the button's state image. */ + public function get textureSmoothing():String { return _body.textureSmoothing; } + public function set textureSmoothing(value:String):void { _body.textureSmoothing = value; } + + /** The overlay sprite is displayed on top of the button contents. It scales with the + * button when pressed. Use it to add additional objects to the button (e.g. an icon). */ + public function get overlay():Sprite + { + if (_overlay == null) + _overlay = new Sprite(); + + _contents.addChild(_overlay); // make sure it's always on top + return _overlay; + } + + /** Indicates if the mouse cursor should transform into a hand while it's over the button. + * @default true */ + public override function get useHandCursor():Boolean { return _useHandCursor; } + public override function set useHandCursor(value:Boolean):void { _useHandCursor = value; } + + /** Controls whether or not the instance snaps to the nearest pixel. This can prevent the + * object from looking blurry when it's not exactly aligned with the pixels of the screen. + * @default true */ + public function get pixelSnapping():Boolean { return _body.pixelSnapping; } + public function set pixelSnapping(value:Boolean):void + { + _body.pixelSnapping = value; + if (_textField) _textField.pixelSnapping = value; + } + + /** @private */ + override public function set width(value:Number):void + { + // The Button might use a Scale9Grid -> + // we must update the body width/height manually for the grid to scale properly. + + var newWidth:Number = value / (this.scaleX || 1.0); + var scale:Number = newWidth / (_body.width || 1.0); + + _body.width = newWidth; + _textBounds.x *= scale; + _textBounds.width *= scale; + + if (_textField) _textField.width = newWidth; + } + + /** @private */ + override public function set height(value:Number):void + { + var newHeight:Number = value / (this.scaleY || 1.0); + var scale:Number = newHeight / (_body.height || 1.0); + + _body.height = newHeight; + _textBounds.y *= scale; + _textBounds.height *= scale; + + if (_textField) _textField.height = newHeight; + } + + /** The current scaling grid used for the button's state image. Use this property to create + * buttons that resize in a smart way, i.e. with the four corners keeping the same size + * and only stretching the center area. + * + * @see Image#scale9Grid + * @default null + */ + public function get scale9Grid():Rectangle { return _body.scale9Grid; } + public function set scale9Grid(value:Rectangle):void { _body.scale9Grid = value; } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/display/ButtonState.as b/mobile_version/src/starling/display/ButtonState.as new file mode 100644 index 00000000..151b8f62 --- /dev/null +++ b/mobile_version/src/starling/display/ButtonState.as @@ -0,0 +1,33 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.display +{ + import starling.errors.AbstractClassError; + + /** A class that provides constant values for the states of the Button class. */ + public class ButtonState + { + /** @private */ + public function ButtonState() { throw new AbstractClassError(); } + + /** The button's default state. */ + public static const UP:String = "up"; + + /** The button is pressed. */ + public static const DOWN:String = "down"; + + /** The mouse hovers over the button. */ + public static const OVER:String = "over"; + + /** The button was disabled altogether. */ + public static const DISABLED:String = "disabled"; + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/display/Canvas.as b/mobile_version/src/starling/display/Canvas.as new file mode 100644 index 00000000..9e5cf299 --- /dev/null +++ b/mobile_version/src/starling/display/Canvas.as @@ -0,0 +1,121 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.display +{ + import flash.geom.Point; + + import starling.geom.Polygon; + import starling.rendering.IndexData; + import starling.rendering.VertexData; + + /** A display object supporting basic vector drawing functionality. In its current state, + * the main use of this class is to provide a range of forms that can be used as masks. + */ + public class Canvas extends DisplayObjectContainer + { + private var _polygons:Vector.; + private var _fillColor:uint; + private var _fillAlpha:Number; + + /** Creates a new (empty) Canvas. Call one or more of the 'draw' methods to add content. */ + public function Canvas() + { + _polygons = new []; + _fillColor = 0xffffff; + _fillAlpha = 1.0; + touchGroup = true; + } + + /** @inheritDoc */ + public override function dispose():void + { + _polygons.length = 0; + super.dispose(); + } + + /** @inheritDoc */ + public override function hitTest(localPoint:Point):DisplayObject + { + if (!visible || !touchable || !hitTestMask(localPoint)) return null; + + // we could also use the standard hit test implementation, but the polygon class can + // do that much more efficiently (it contains custom implementations for circles, etc). + + for (var i:int = 0, len:int = _polygons.length; i < len; ++i) + if (_polygons[i].containsPoint(localPoint)) return this; + + return null; + } + + /** Draws a circle. */ + public function drawCircle(x:Number, y:Number, radius:Number):void + { + appendPolygon(Polygon.createCircle(x, y, radius)); + } + + /** Draws an ellipse. */ + public function drawEllipse(x:Number, y:Number, width:Number, height:Number):void + { + var radiusX:Number = width / 2.0; + var radiusY:Number = height / 2.0; + + appendPolygon(Polygon.createEllipse(x + radiusX, y + radiusY, radiusX, radiusY)); + } + + /** Draws a rectangle. */ + public function drawRectangle(x:Number, y:Number, width:Number, height:Number):void + { + appendPolygon(Polygon.createRectangle(x, y, width, height)); + } + + /** Draws an arbitrary polygon. */ + public function drawPolygon(polygon:Polygon):void + { + appendPolygon(polygon); + } + + /** Specifies a simple one-color fill that subsequent calls to drawing methods + * (such as drawCircle()) will use. */ + public function beginFill(color:uint=0xffffff, alpha:Number=1.0):void + { + _fillColor = color; + _fillAlpha = alpha; + } + + /** Resets the color to 'white' and alpha to '1'. */ + public function endFill():void + { + _fillColor = 0xffffff; + _fillAlpha = 1.0; + } + + /** Removes all existing vertices. */ + public function clear():void + { + removeChildren(0, -1, true); + _polygons.length = 0; + } + + private function appendPolygon(polygon:Polygon):void + { + var vertexData:VertexData = new VertexData(); + var indexData:IndexData = new IndexData(polygon.numTriangles * 3); + + polygon.triangulate(indexData); + polygon.copyToVertexData(vertexData); + + vertexData.colorize("color", _fillColor, _fillAlpha); + + addChild(new Mesh(vertexData, indexData)); + _polygons[_polygons.length] = polygon; + } + } +} diff --git a/mobile_version/src/starling/display/DisplayObject.as b/mobile_version/src/starling/display/DisplayObject.as new file mode 100644 index 00000000..34b1d179 --- /dev/null +++ b/mobile_version/src/starling/display/DisplayObject.as @@ -0,0 +1,1111 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.display +{ + import flash.errors.IllegalOperationError; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.geom.Vector3D; + import flash.system.Capabilities; + import flash.ui.Mouse; + import flash.ui.MouseCursor; + import flash.utils.getQualifiedClassName; + + import starling.core.Starling; + import starling.core.starling_internal; + import starling.errors.AbstractClassError; + import starling.errors.AbstractMethodError; + import starling.events.Event; + import starling.events.EventDispatcher; + import starling.events.TouchEvent; + import starling.filters.FragmentFilter; + import starling.rendering.BatchToken; + import starling.rendering.Painter; + import starling.utils.Align; + import starling.utils.MathUtil; + import starling.utils.MatrixUtil; + + use namespace starling_internal; + + /** Dispatched when an object is added to a parent. */ + [Event(name="added", type="starling.events.Event")] + + /** Dispatched when an object is connected to the stage (directly or indirectly). */ + [Event(name="addedToStage", type="starling.events.Event")] + + /** Dispatched when an object is removed from its parent. */ + [Event(name="removed", type="starling.events.Event")] + + /** Dispatched when an object is removed from the stage and won't be rendered any longer. */ + [Event(name="removedFromStage", type="starling.events.Event")] + + /** Dispatched once every frame on every object that is connected to the stage. */ + [Event(name="enterFrame", type="starling.events.EnterFrameEvent")] + + /** Dispatched when an object is touched. Bubbles. */ + [Event(name="touch", type="starling.events.TouchEvent")] + + /** Dispatched when a key on the keyboard is released. */ + [Event(name="keyUp", type="starling.events.KeyboardEvent")] + + /** Dispatched when a key on the keyboard is pressed. */ + [Event(name="keyDown", type="starling.events.KeyboardEvent")] + + /** + * The DisplayObject class is the base class for all objects that are rendered on the + * screen. + * + *

The Display Tree

+ * + *

In Starling, 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 (Image, Quad) that will be rendered directly to + * the screen, and of container nodes (subclasses of "DisplayObjectContainer", like "Sprite"). + * A container is simply a display object that has child nodes - which can, again, be either + * leaf nodes or other containers.

+ * + *

At the base of the display tree, there is the Stage, which is a container, too. To create + * a Starling application, you create a custom Sprite subclass, and Starling will add an + * instance of this class to the stage.

+ * + *

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 children will receive any more touch events.

+ * + * 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 getTransformationMatrix. It will + * create a matrix that represents the transformation of a point in one coordinate system to + * another.

+ * + * Customization + * + *

DisplayObject is an abstract class, which means you cannot instantiate it directly, + * but have to use one of its many subclasses instead. For leaf nodes, this is typically + * 'Mesh' or its subclasses 'Quad' and 'Image'. To customize rendering of these objects, + * you can use fragment filters (via the filter-property on 'DisplayObject') + * or mesh styles (via the style-property on 'Mesh'). Look at the respective + * class documentation for more information.

+ * + * @see DisplayObjectContainer + * @see Sprite + * @see Stage + * @see Mesh + * @see starling.filters.FragmentFilter + * @see starling.styles.MeshStyle + */ + public class DisplayObject extends EventDispatcher + { + // private members + + private var _x:Number; + private var _y:Number; + private var _pivotX:Number; + private var _pivotY:Number; + private var _scaleX:Number; + private var _scaleY:Number; + private var _skewX:Number; + private var _skewY:Number; + private var _rotation:Number; + private var _alpha:Number; + private var _visible:Boolean; + private var _touchable:Boolean; + private var _blendMode:String; + private var _name:String; + private var _useHandCursor:Boolean; + private var _transformationMatrix:Matrix; + private var _transformationMatrix3D:Matrix3D; + private var _orientationChanged:Boolean; + private var _is3D:Boolean; + private var _maskee:DisplayObject; + + // internal members (for fast access on rendering) + + /** @private */ internal var _parent:DisplayObjectContainer; + /** @private */ internal var _lastParentOrSelfChangeFrameID:uint; + /** @private */ internal var _lastChildChangeFrameID:uint; + /** @private */ internal var _tokenFrameID:uint; + /** @private */ internal var _pushToken:BatchToken = new BatchToken(); + /** @private */ internal var _popToken:BatchToken = new BatchToken(); + /** @private */ internal var _hasVisibleArea:Boolean; + /** @private */ internal var _filter:FragmentFilter; + /** @private */ internal var _mask:DisplayObject; + + // helper objects + + private static var sAncestors:Vector. = new []; + private static var sHelperPoint:Point = new Point(); + private static var sHelperPoint3D:Vector3D = new Vector3D(); + private static var sHelperPointAlt3D:Vector3D = new Vector3D(); + private static var sHelperRect:Rectangle = new Rectangle(); + private static var sHelperMatrix:Matrix = new Matrix(); + private static var sHelperMatrixAlt:Matrix = new Matrix(); + private static var sHelperMatrix3D:Matrix3D = new Matrix3D(); + private static var sHelperMatrixAlt3D:Matrix3D = new Matrix3D(); + + /** @private */ + public function DisplayObject() + { + if (Capabilities.isDebugger && + getQualifiedClassName(this) == "starling.display::DisplayObject") + { + throw new AbstractClassError(); + } + + _x = _y = _pivotX = _pivotY = _rotation = _skewX = _skewY = 0.0; + _scaleX = _scaleY = _alpha = 1.0; + _visible = _touchable = _hasVisibleArea = true; + _blendMode = BlendMode.AUTO; + _transformationMatrix = new Matrix(); + } + + /** Disposes all resources of the display object. + * GPU buffers are released, event listeners are removed, filters and masks are disposed. */ + public function dispose():void + { + if (_filter) _filter.dispose(); + if (_mask) _mask.dispose(); + removeEventListeners(); + mask = null; // clear 'mask._maskee', just to be sure. + } + + /** Removes the object from its parent, if it has one, and optionally disposes it. */ + public function removeFromParent(dispose:Boolean=false):void + { + if (_parent) _parent.removeChild(this, dispose); + else if (dispose) this.dispose(); + } + + /** Creates a matrix that represents the transformation from the local coordinate system + * to another. If you pass an out-matrix, the result will be stored in this + * matrix instead of creating a new object. */ + public function getTransformationMatrix(targetSpace:DisplayObject, + out:Matrix=null):Matrix + { + var commonParent:DisplayObject; + var currentObject:DisplayObject; + + if (out) out.identity(); + else out = new Matrix(); + + if (targetSpace == this) + { + return out; + } + else if (targetSpace == _parent || (targetSpace == null && _parent == null)) + { + out.copyFrom(transformationMatrix); + return out; + } + else if (targetSpace == null || targetSpace == base) + { + // targetCoordinateSpace 'null' represents the target space of the base object. + // -> move up from this to base + + currentObject = this; + while (currentObject != targetSpace) + { + out.concat(currentObject.transformationMatrix); + currentObject = currentObject._parent; + } + + return out; + } + else if (targetSpace._parent == this) // optimization + { + targetSpace.getTransformationMatrix(this, out); + out.invert(); + + return out; + } + + // 1. find a common parent of this and the target space + + commonParent = findCommonParent(this, targetSpace); + + // 2. move up from this to common parent + + currentObject = this; + while (currentObject != commonParent) + { + out.concat(currentObject.transformationMatrix); + currentObject = currentObject._parent; + } + + if (commonParent == targetSpace) + return out; + + // 3. now move up from target until we reach the common parent + + sHelperMatrix.identity(); + currentObject = targetSpace; + while (currentObject != commonParent) + { + sHelperMatrix.concat(currentObject.transformationMatrix); + currentObject = currentObject._parent; + } + + // 4. now combine the two matrices + + sHelperMatrix.invert(); + out.concat(sHelperMatrix); + + return out; + } + + /** Returns a rectangle that completely encloses the object as it appears in another + * coordinate system. If you pass an out-rectangle, the result will be + * stored in this rectangle instead of creating a new object. */ + public function getBounds(targetSpace:DisplayObject, out:Rectangle=null):Rectangle + { + throw new AbstractMethodError(); + } + + /** Returns the object that is found topmost beneath a point in local coordinates, or nil + * if the test fails. Untouchable and invisible objects will cause the test to fail. */ + public function hitTest(localPoint:Point):DisplayObject + { + // on a touch test, invisible or untouchable objects cause the test to fail + if (!_visible || !_touchable) return null; + + // if we've got a mask and the hit occurs outside, fail + if (_mask && !hitTestMask(localPoint)) return null; + + // otherwise, check bounding box + if (getBounds(this, sHelperRect).containsPoint(localPoint)) return this; + else return null; + } + + /** Checks if a certain point is inside the display object's mask. If there is no mask, + * this method always returns true (because having no mask is equivalent + * to having one that's infinitely big). */ + public function hitTestMask(localPoint:Point):Boolean + { + if (_mask) + { + if (_mask.stage) getTransformationMatrix(_mask, sHelperMatrixAlt); + else + { + sHelperMatrixAlt.copyFrom(_mask.transformationMatrix); + sHelperMatrixAlt.invert(); + } + + var helperPoint:Point = localPoint == sHelperPoint ? new Point() : sHelperPoint; + MatrixUtil.transformPoint(sHelperMatrixAlt, localPoint, helperPoint); + return _mask.hitTest(helperPoint) != null; + } + else return true; + } + + /** Transforms a point from the local coordinate system to global (stage) coordinates. + * If you pass an out-point, the result will be stored in this point instead + * of creating a new object. */ + public function localToGlobal(localPoint:Point, out:Point=null):Point + { + if (is3D) + { + sHelperPoint3D.setTo(localPoint.x, localPoint.y, 0); + return local3DToGlobal(sHelperPoint3D, out); + } + else + { + getTransformationMatrix(base, sHelperMatrixAlt); + return MatrixUtil.transformPoint(sHelperMatrixAlt, localPoint, out); + } + } + + /** Transforms a point from global (stage) coordinates to the local coordinate system. + * If you pass an out-point, the result will be stored in this point instead + * of creating a new object. */ + public function globalToLocal(globalPoint:Point, out:Point=null):Point + { + if (is3D) + { + globalToLocal3D(globalPoint, sHelperPoint3D); + stage.getCameraPosition(this, sHelperPointAlt3D); + return MathUtil.intersectLineWithXYPlane(sHelperPointAlt3D, sHelperPoint3D, out); + } + else + { + getTransformationMatrix(base, sHelperMatrixAlt); + sHelperMatrixAlt.invert(); + return MatrixUtil.transformPoint(sHelperMatrixAlt, globalPoint, out); + } + } + + /** Renders the display object with the help of a painter object. Never call this method + * directly, except from within another render method. + * + * @param painter Captures the current render state and provides utility functions + * for rendering. + */ + public function render(painter:Painter):void + { + throw new AbstractMethodError(); + } + + /** Moves the pivot point to a certain position within the local coordinate system + * of the object. If you pass no arguments, it will be centered. */ + public function alignPivot(horizontalAlign:String="center", + verticalAlign:String="center"):void + { + var bounds:Rectangle = getBounds(this, sHelperRect); + setOrientationChanged(); + + if (horizontalAlign == Align.LEFT) _pivotX = bounds.x; + else if (horizontalAlign == Align.CENTER) _pivotX = bounds.x + bounds.width / 2.0; + else if (horizontalAlign == Align.RIGHT) _pivotX = bounds.x + bounds.width; + else throw new ArgumentError("Invalid horizontal alignment: " + horizontalAlign); + + if (verticalAlign == Align.TOP) _pivotY = bounds.y; + else if (verticalAlign == Align.CENTER) _pivotY = bounds.y + bounds.height / 2.0; + else if (verticalAlign == Align.BOTTOM) _pivotY = bounds.y + bounds.height; + else throw new ArgumentError("Invalid vertical alignment: " + verticalAlign); + } + + // 3D transformation + + /** Creates a matrix that represents the transformation from the local coordinate system + * to another. This method supports three dimensional objects created via 'Sprite3D'. + * If you pass an out-matrix, the result will be stored in this matrix + * instead of creating a new object. */ + public function getTransformationMatrix3D(targetSpace:DisplayObject, + out:Matrix3D=null):Matrix3D + { + var commonParent:DisplayObject; + var currentObject:DisplayObject; + + if (out) out.identity(); + else out = new Matrix3D(); + + if (targetSpace == this) + { + return out; + } + else if (targetSpace == _parent || (targetSpace == null && _parent == null)) + { + out.copyFrom(transformationMatrix3D); + return out; + } + else if (targetSpace == null || targetSpace == base) + { + // targetCoordinateSpace 'null' represents the target space of the base object. + // -> move up from this to base + + currentObject = this; + while (currentObject != targetSpace) + { + out.append(currentObject.transformationMatrix3D); + currentObject = currentObject._parent; + } + + return out; + } + else if (targetSpace._parent == this) // optimization + { + targetSpace.getTransformationMatrix3D(this, out); + out.invert(); + + return out; + } + + // 1. find a common parent of this and the target space + + commonParent = findCommonParent(this, targetSpace); + + // 2. move up from this to common parent + + currentObject = this; + while (currentObject != commonParent) + { + out.append(currentObject.transformationMatrix3D); + currentObject = currentObject._parent; + } + + if (commonParent == targetSpace) + return out; + + // 3. now move up from target until we reach the common parent + + sHelperMatrix3D.identity(); + currentObject = targetSpace; + while (currentObject != commonParent) + { + sHelperMatrix3D.append(currentObject.transformationMatrix3D); + currentObject = currentObject._parent; + } + + // 4. now combine the two matrices + + sHelperMatrix3D.invert(); + out.append(sHelperMatrix3D); + + return out; + } + + /** Transforms a 3D point from the local coordinate system to global (stage) coordinates. + * This is achieved by projecting the 3D point onto the (2D) view plane. + * + *

If you pass an out-point, the result will be stored in this point + * instead of creating a new object.

*/ + public function local3DToGlobal(localPoint:Vector3D, out:Point=null):Point + { + var stage:Stage = this.stage; + if (stage == null) throw new IllegalOperationError("Object not connected to stage"); + + getTransformationMatrix3D(stage, sHelperMatrixAlt3D); + MatrixUtil.transformPoint3D(sHelperMatrixAlt3D, localPoint, sHelperPoint3D); + return MathUtil.intersectLineWithXYPlane(stage.cameraPosition, sHelperPoint3D, out); + } + + /** Transforms a point from global (stage) coordinates to the 3D local coordinate system. + * If you pass an out-vector, the result will be stored in this vector + * instead of creating a new object. */ + public function globalToLocal3D(globalPoint:Point, out:Vector3D=null):Vector3D + { + var stage:Stage = this.stage; + if (stage == null) throw new IllegalOperationError("Object not connected to stage"); + + getTransformationMatrix3D(stage, sHelperMatrixAlt3D); + sHelperMatrixAlt3D.invert(); + return MatrixUtil.transformCoords3D( + sHelperMatrixAlt3D, globalPoint.x, globalPoint.y, 0, out); + } + + // internal methods + + /** @private */ + starling_internal function setParent(value:DisplayObjectContainer):void + { + // check for a recursion + var ancestor:DisplayObject = value; + while (ancestor != this && ancestor != null) + ancestor = ancestor._parent; + + if (ancestor == this) + throw new ArgumentError("An object cannot be added as a child to itself or one " + + "of its children (or children's children, etc.)"); + else + _parent = value; + } + + /** @private */ + internal function setIs3D(value:Boolean):void + { + _is3D = value; + } + + /** @private */ + internal function get isMask():Boolean + { + return _maskee != null; + } + + // render cache + + /** Forces the object to be redrawn in the next frame. + * This will prevent the object to be drawn from the render cache. + * + *

This method is called every time the object changes in any way. When creating + * custom mesh styles or any other custom rendering code, call this method if the object + * needs to be redrawn.

+ * + *

If the object needs to be redrawn just because it does not support the render cache, + * call painter.excludeFromCache() in the object's render method instead. + * That way, Starling's skipUnchangedFrames policy won't be disrupted.

+ */ + public function setRequiresRedraw():void + { + var parent:DisplayObject = _parent || _maskee; + var frameID:int = Starling.frameID; + + _lastParentOrSelfChangeFrameID = frameID; + _hasVisibleArea = _alpha != 0.0 && _visible && _maskee == null && + _scaleX != 0.0 && _scaleY != 0.0; + + while (parent && parent._lastChildChangeFrameID != frameID) + { + parent._lastChildChangeFrameID = frameID; + parent = parent._parent || parent._maskee; + } + } + + /** Indicates if the object needs to be redrawn in the upcoming frame, i.e. if it has + * changed its location relative to the stage or some other aspect of its appearance + * since it was last rendered. */ + public function get requiresRedraw():Boolean + { + var frameID:uint = Starling.frameID; + + return _lastParentOrSelfChangeFrameID == frameID || + _lastChildChangeFrameID == frameID; + } + + /** @private Makes sure the object is not drawn from cache in the next frame. + * This method is meant to be called only from Painter.finishFrame(), + * since it requires rendering to be concluded. */ + starling_internal function excludeFromCache():void + { + var object:DisplayObject = this; + var max:uint = 0xffffffff; + + while (object && object._tokenFrameID != max) + { + object._tokenFrameID = max; + object = object._parent; + } + } + + // helpers + + private function setOrientationChanged():void + { + _orientationChanged = true; + setRequiresRedraw(); + } + + private static function findCommonParent(object1:DisplayObject, + object2:DisplayObject):DisplayObject + { + var currentObject:DisplayObject = object1; + + while (currentObject) + { + sAncestors[sAncestors.length] = currentObject; // avoiding 'push' + currentObject = currentObject._parent; + } + + currentObject = object2; + while (currentObject && sAncestors.indexOf(currentObject) == -1) + currentObject = currentObject._parent; + + sAncestors.length = 0; + + if (currentObject) return currentObject; + else throw new ArgumentError("Object not connected to target"); + } + + // stage event handling + + /** @private */ + public override function dispatchEvent(event:Event):void + { + if (event.type == Event.REMOVED_FROM_STAGE && stage == null) + return; // special check to avoid double-dispatch of RfS-event. + else + super.dispatchEvent(event); + } + + // enter frame event optimization + + // To avoid looping through the complete display tree each frame to find out who's + // listening to ENTER_FRAME events, we manage a list of them manually in the Stage class. + // We need to take care that (a) it must be dispatched only when the object is + // part of the stage, (b) it must not cause memory leaks when the user forgets to call + // dispose and (c) there might be multiple listeners for this event. + + /** @inheritDoc */ + public override function addEventListener(type:String, listener:Function):void + { + if (type == Event.ENTER_FRAME && !hasEventListener(type)) + { + addEventListener(Event.ADDED_TO_STAGE, addEnterFrameListenerToStage); + addEventListener(Event.REMOVED_FROM_STAGE, removeEnterFrameListenerFromStage); + if (this.stage) addEnterFrameListenerToStage(); + } + + super.addEventListener(type, listener); + } + + /** @inheritDoc */ + public override function removeEventListener(type:String, listener:Function):void + { + super.removeEventListener(type, listener); + + if (type == Event.ENTER_FRAME && !hasEventListener(type)) + { + removeEventListener(Event.ADDED_TO_STAGE, addEnterFrameListenerToStage); + removeEventListener(Event.REMOVED_FROM_STAGE, removeEnterFrameListenerFromStage); + removeEnterFrameListenerFromStage(); + } + } + + /** @inheritDoc */ + public override function removeEventListeners(type:String=null):void + { + if ((type == null || type == Event.ENTER_FRAME) && hasEventListener(Event.ENTER_FRAME)) + { + removeEventListener(Event.ADDED_TO_STAGE, addEnterFrameListenerToStage); + removeEventListener(Event.REMOVED_FROM_STAGE, removeEnterFrameListenerFromStage); + removeEnterFrameListenerFromStage(); + } + + super.removeEventListeners(type); + } + + private function addEnterFrameListenerToStage():void + { + Starling.current.stage.addEnterFrameListener(this); + } + + private function removeEnterFrameListenerFromStage():void + { + Starling.current.stage.removeEnterFrameListener(this); + } + + // properties + + /** The transformation matrix of the object relative to its parent. + * + *

If you assign a custom transformation matrix, Starling will try to figure out + * suitable values for x, y, scaleX, scaleY, and rotation. + * However, if the matrix was created in a different way, this might not be possible. + * In that case, Starling will apply the matrix, but not update the corresponding + * properties.

+ * + *

CAUTION: not a copy, but the actual object!

*/ + public function get transformationMatrix():Matrix + { + if (_orientationChanged) + { + _orientationChanged = false; + + if (_skewX == 0.0 && _skewY == 0.0) + { + // optimization: no skewing / rotation simplifies the matrix math + + if (_rotation == 0.0) + { + _transformationMatrix.setTo(_scaleX, 0.0, 0.0, _scaleY, + _x - _pivotX * _scaleX, _y - _pivotY * _scaleY); + } + else + { + var cos:Number = Math.cos(_rotation); + var sin:Number = Math.sin(_rotation); + var a:Number = _scaleX * cos; + var b:Number = _scaleX * sin; + var c:Number = _scaleY * -sin; + var d:Number = _scaleY * cos; + var tx:Number = _x - _pivotX * a - _pivotY * c; + var ty:Number = _y - _pivotX * b - _pivotY * d; + + _transformationMatrix.setTo(a, b, c, d, tx, ty); + } + } + else + { + _transformationMatrix.identity(); + _transformationMatrix.scale(_scaleX, _scaleY); + MatrixUtil.skew(_transformationMatrix, _skewX, _skewY); + _transformationMatrix.rotate(_rotation); + _transformationMatrix.translate(_x, _y); + + if (_pivotX != 0.0 || _pivotY != 0.0) + { + // prepend pivot transformation + _transformationMatrix.tx = _x - _transformationMatrix.a * _pivotX + - _transformationMatrix.c * _pivotY; + _transformationMatrix.ty = _y - _transformationMatrix.b * _pivotX + - _transformationMatrix.d * _pivotY; + } + } + } + + return _transformationMatrix; + } + + public function set transformationMatrix(matrix:Matrix):void + { + const PI_Q:Number = Math.PI / 4.0; + + setRequiresRedraw(); + _orientationChanged = false; + _transformationMatrix.copyFrom(matrix); + _pivotX = _pivotY = 0; + + _x = matrix.tx; + _y = matrix.ty; + + _skewX = Math.atan(-matrix.c / matrix.d); + _skewY = Math.atan( matrix.b / matrix.a); + + // NaN check ("isNaN" causes allocation) + if (_skewX != _skewX) _skewX = 0.0; + if (_skewY != _skewY) _skewY = 0.0; + + _scaleY = (_skewX > -PI_Q && _skewX < PI_Q) ? matrix.d / Math.cos(_skewX) + : -matrix.c / Math.sin(_skewX); + _scaleX = (_skewY > -PI_Q && _skewY < PI_Q) ? matrix.a / Math.cos(_skewY) + : matrix.b / Math.sin(_skewY); + + if (MathUtil.isEquivalent(_skewX, _skewY)) + { + _rotation = _skewX; + _skewX = _skewY = 0; + } + else + { + _rotation = 0; + } + } + + /** The 3D transformation matrix of the object relative to its parent. + * + *

For 2D objects, this property returns just a 3D version of the 2D transformation + * matrix. Only the 'Sprite3D' class supports real 3D transformations.

+ * + *

CAUTION: not a copy, but the actual object!

*/ + public function get transformationMatrix3D():Matrix3D + { + // this method needs to be overridden in 3D-supporting subclasses (like Sprite3D). + + if (_transformationMatrix3D == null) + _transformationMatrix3D = new Matrix3D(); + + return MatrixUtil.convertTo3D(transformationMatrix, _transformationMatrix3D); + } + + /** Indicates if this object or any of its parents is a 'Sprite3D' object. */ + public function get is3D():Boolean { return _is3D; } + + /** Indicates if the mouse cursor should transform into a hand while it's over the sprite. + * @default false */ + public function get useHandCursor():Boolean { return _useHandCursor; } + public function set useHandCursor(value:Boolean):void + { + if (value == _useHandCursor) return; + _useHandCursor = value; + + if (_useHandCursor) + addEventListener(TouchEvent.TOUCH, onTouch); + else + removeEventListener(TouchEvent.TOUCH, onTouch); + } + + private function onTouch(event:TouchEvent):void + { + Mouse.cursor = event.interactsWith(this) ? MouseCursor.BUTTON : MouseCursor.AUTO; + } + + /** The bounds of the object relative to the local coordinates of the parent. */ + public function get bounds():Rectangle + { + return getBounds(_parent); + } + + /** The width of the object in pixels. + * Note that for objects in a 3D space (connected to a Sprite3D), this value might not + * be accurate until the object is part of the display list. */ + public function get width():Number { return getBounds(_parent, sHelperRect).width; } + public function set width(value:Number):void + { + // this method calls 'this.scaleX' instead of changing _scaleX directly. + // that way, subclasses reacting on size changes need to override only the scaleX method. + + var actualWidth:Number; + var scaleIsNaN:Boolean = _scaleX != _scaleX; // avoid 'isNaN' call + + if (_scaleX == 0.0 || scaleIsNaN) { scaleX = 1.0; actualWidth = width; } + else actualWidth = Math.abs(width / _scaleX); + + if (actualWidth) scaleX = value / actualWidth; + } + + /** The height of the object in pixels. + * Note that for objects in a 3D space (connected to a Sprite3D), this value might not + * be accurate until the object is part of the display list. */ + public function get height():Number { return getBounds(_parent, sHelperRect).height; } + public function set height(value:Number):void + { + var actualHeight:Number; + var scaleIsNaN:Boolean = _scaleY != _scaleY; // avoid 'isNaN' call + + if (_scaleY == 0.0 || scaleIsNaN) { scaleY = 1.0; actualHeight = height; } + else actualHeight = Math.abs(height / _scaleY); + + if (actualHeight) scaleY = value / actualHeight; + } + + /** The x coordinate of the object relative to the local coordinates of the parent. */ + public function get x():Number { return _x; } + public function set x(value:Number):void + { + if (_x != value) + { + _x = value; + setOrientationChanged(); + } + } + + /** The y coordinate of the object relative to the local coordinates of the parent. */ + public function get y():Number { return _y; } + public function set y(value:Number):void + { + if (_y != value) + { + _y = value; + setOrientationChanged(); + } + } + + /** The x coordinate of the object's origin in its own coordinate space (default: 0). */ + public function get pivotX():Number { return _pivotX; } + public function set pivotX(value:Number):void + { + if (_pivotX != value) + { + _pivotX = value; + setOrientationChanged(); + } + } + + /** The y coordinate of the object's origin in its own coordinate space (default: 0). */ + public function get pivotY():Number { return _pivotY; } + public function set pivotY(value:Number):void + { + if (_pivotY != value) + { + _pivotY = value; + setOrientationChanged(); + } + } + + /** The horizontal scale factor. '1' means no scale, negative values flip the object. + * @default 1 */ + public function get scaleX():Number { return _scaleX; } + public function set scaleX(value:Number):void + { + if (_scaleX != value) + { + _scaleX = value; + setOrientationChanged(); + } + } + + /** The vertical scale factor. '1' means no scale, negative values flip the object. + * @default 1 */ + public function get scaleY():Number { return _scaleY; } + public function set scaleY(value:Number):void + { + if (_scaleY != value) + { + _scaleY = value; + setOrientationChanged(); + } + } + + /** Sets both 'scaleX' and 'scaleY' to the same value. The getter simply returns the + * value of 'scaleX' (even if the scaling values are different). @default 1 */ + public function get scale():Number { return scaleX; } + public function set scale(value:Number):void { scaleX = scaleY = value; } + + /** The horizontal skew angle in radians. */ + public function get skewX():Number { return _skewX; } + public function set skewX(value:Number):void + { + value = MathUtil.normalizeAngle(value); + + if (_skewX != value) + { + _skewX = value; + setOrientationChanged(); + } + } + + /** The vertical skew angle in radians. */ + public function get skewY():Number { return _skewY; } + public function set skewY(value:Number):void + { + value = MathUtil.normalizeAngle(value); + + if (_skewY != value) + { + _skewY = value; + setOrientationChanged(); + } + } + + /** The rotation of the object in radians. (In Starling, all angles are measured + * in radians.) */ + public function get rotation():Number { return _rotation; } + public function set rotation(value:Number):void + { + value = MathUtil.normalizeAngle(value); + + if (_rotation != value) + { + _rotation = value; + setOrientationChanged(); + } + } + + /** @private Indicates if the object is rotated or skewed in any way. */ + internal function get isRotated():Boolean + { + return _rotation != 0.0 || _skewX != 0.0 || _skewY != 0.0; + } + + /** The opacity of the object. 0 = transparent, 1 = opaque. @default 1 */ + public function get alpha():Number { return _alpha; } + public function set alpha(value:Number):void + { + if (value != _alpha) + { + _alpha = value < 0.0 ? 0.0 : (value > 1.0 ? 1.0 : value); + setRequiresRedraw(); + } + } + + /** The visibility of the object. An invisible object will be untouchable. */ + public function get visible():Boolean { return _visible; } + public function set visible(value:Boolean):void + { + if (value != _visible) + { + _visible = value; + setRequiresRedraw(); + } + } + + /** Indicates if this object (and its children) will receive touch events. */ + public function get touchable():Boolean { return _touchable; } + public function set touchable(value:Boolean):void { _touchable = value; } + + /** The blend mode determines how the object is blended with the objects underneath. + * @default auto + * @see starling.display.BlendMode */ + public function get blendMode():String { return _blendMode; } + public function set blendMode(value:String):void + { + if (value != _blendMode) + { + _blendMode = value; + setRequiresRedraw(); + } + } + + /** The name of the display object (default: null). Used by 'getChildByName()' of + * display object containers. */ + public function get name():String { return _name; } + public function set name(value:String):void { _name = value; } + + /** The filter that is attached to the display object. The starling.filters + * package contains several classes that define specific filters you can use. To combine + * several filters, assign an instance of the FilterChain class; to remove + * all filters, assign null. + * + *

Beware that a filter instance may only be used on one object at a time! Furthermore, + * when you remove or replace a filter, it is NOT disposed automatically (since you might + * want to reuse it on a different object).

+ * + * @default null + * @see starling.filters.FragmentFilter + * @see starling.filters.FilterChain + */ + public function get filter():FragmentFilter { return _filter; } + public function set filter(value:FragmentFilter):void + { + if (value != _filter) + { + if (_filter) _filter.setTarget(null); + if (value) value.setTarget(this); + + _filter = value; + setRequiresRedraw(); + } + } + + /** The display object that acts as a mask for the current object. + * Assign null to remove it. + * + *

A pixel of the masked display object will only be drawn if it is within one of the + * mask's polygons. Texture pixels and alpha values of the mask are not taken into + * account. The mask object itself is never visible.

+ * + *

If the mask is part of the display list, masking will occur at exactly the + * location it occupies on the stage. If it is not, the mask will be placed in the local + * coordinate system of the target object (as if it was one of its children).

+ * + *

For rectangular masks, you can use simple quads; for other forms (like circles + * or arbitrary shapes) it is recommended to use a 'Canvas' instance.

+ * + *

Beware that a mask will typically cause at least two additional draw calls: + * one to draw the mask to the stencil buffer and one to erase it. However, if the + * mask object is an instance of starling.display.Quad and is aligned + * parallel to the stage axes, rendering will be optimized: instead of using the + * stencil buffer, the object will be clipped using the scissor rectangle. That's + * faster and reduces the number of draw calls, so make use of this when possible.

+ * + * @see Canvas + * @default null + */ + public function get mask():DisplayObject { return _mask; } + public function set mask(value:DisplayObject):void + { + if (_mask != value) + { + if (_mask) _mask._maskee = null; + if (value) + { + value._maskee = this; + value._hasVisibleArea = false; + } + + _mask = value; + setRequiresRedraw(); + } + } + + /** The display object container that contains this display object. */ + public function get parent():DisplayObjectContainer { return _parent; } + + /** The topmost object in the display tree the object is part of. */ + public function get base():DisplayObject + { + var currentObject:DisplayObject = this; + while (currentObject._parent) currentObject = currentObject._parent; + return currentObject; + } + + /** The root object the display object is connected to (i.e. an instance of the class + * that was passed to the Starling constructor), or null if the object is not connected + * to the stage. */ + public function get root():DisplayObject + { + var currentObject:DisplayObject = this; + while (currentObject._parent) + { + if (currentObject._parent is Stage) return currentObject; + else currentObject = currentObject.parent; + } + + return null; + } + + /** The stage the display object is connected to, or null if it is not connected + * to the stage. */ + public function get stage():Stage { return this.base as Stage; } + } +} diff --git a/mobile_version/src/starling/display/DisplayObjectContainer.as b/mobile_version/src/starling/display/DisplayObjectContainer.as new file mode 100644 index 00000000..c3962bca --- /dev/null +++ b/mobile_version/src/starling/display/DisplayObjectContainer.as @@ -0,0 +1,508 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.display +{ + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.system.Capabilities; + import flash.utils.getQualifiedClassName; + + import starling.core.starling_internal; + import starling.errors.AbstractClassError; + import starling.events.Event; + import starling.filters.FragmentFilter; + import starling.rendering.BatchToken; + import starling.rendering.Painter; + import starling.utils.MatrixUtil; + + use namespace starling_internal; + + /** + * A DisplayObjectContainer 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 a 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 "Sprite".

+ * + * 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 frontmost 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. + * + *
    + *
  • Event.ADDED: the object was added to a parent.
  • + *
  • Event.ADDED_TO_STAGE: the object was added to a parent that is + * connected to the stage, thus becoming visible now.
  • + *
  • Event.REMOVED: the object was removed from a parent.
  • + *
  • Event.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. + * + * @see Sprite + * @see DisplayObject + */ + public class DisplayObjectContainer extends DisplayObject + { + // members + + private var _children:Vector.; + private var _touchGroup:Boolean; + + // helper objects + private static var sHelperMatrix:Matrix = new Matrix(); + private static var sHelperPoint:Point = new Point(); + private static var sBroadcastListeners:Vector. = new []; + private static var sSortBuffer:Vector. = new []; + private static var sCacheToken:BatchToken = new BatchToken(); + + // construction + + /** @private */ + public function DisplayObjectContainer() + { + if (Capabilities.isDebugger && + getQualifiedClassName(this) == "starling.display::DisplayObjectContainer") + { + throw new AbstractClassError(); + } + + _children = new []; + } + + /** Disposes the resources of all children. */ + public override function dispose():void + { + for (var i:int=_children.length-1; i>=0; --i) + _children[i].dispose(); + + super.dispose(); + } + + // child management + + /** Adds a child to the container. It will be at the frontmost position. */ + public function addChild(child:DisplayObject):DisplayObject + { + return addChildAt(child, _children.length); + } + + /** Adds a child to the container at a certain index. */ + public function addChildAt(child:DisplayObject, index:int):DisplayObject + { + var numChildren:int = _children.length; + + if (index >= 0 && index <= numChildren) + { + setRequiresRedraw(); + + if (child.parent == this) + { + setChildIndex(child, index); // avoids dispatching events + } + else + { + _children.insertAt(index, child); + + child.removeFromParent(); + child.setParent(this); + child.dispatchEventWith(Event.ADDED, true); + + if (stage) + { + var container:DisplayObjectContainer = child as DisplayObjectContainer; + if (container) container.broadcastEventWith(Event.ADDED_TO_STAGE); + else child.dispatchEventWith(Event.ADDED_TO_STAGE); + } + } + + return child; + } + else + { + throw new RangeError("Invalid child index"); + } + } + + /** Removes a child from the container. If the object is not a child, the method returns + * null. If requested, the child will be disposed right away. */ + public function removeChild(child:DisplayObject, dispose:Boolean=false):DisplayObject + { + var childIndex:int = getChildIndex(child); + if (childIndex != -1) return removeChildAt(childIndex, dispose); + else return null; + } + + /** Removes a child at a certain index. The index positions of any display objects above + * the child are decreased by 1. If requested, the child will be disposed right away. */ + public function removeChildAt(index:int, dispose:Boolean=false):DisplayObject + { + if (index >= 0 && index < _children.length) + { + setRequiresRedraw(); + + var child:DisplayObject = _children[index]; + child.dispatchEventWith(Event.REMOVED, true); + + if (stage) + { + var container:DisplayObjectContainer = child as DisplayObjectContainer; + if (container) container.broadcastEventWith(Event.REMOVED_FROM_STAGE); + else child.dispatchEventWith(Event.REMOVED_FROM_STAGE); + } + + child.setParent(null); + index = _children.indexOf(child); // index might have changed by event handler + if (index >= 0) _children.removeAt(index); + if (dispose) child.dispose(); + + return child; + } + else + { + throw new RangeError("Invalid child index"); + } + } + + /** Removes a range of children from the container (endIndex included). + * If no arguments are given, all children will be removed. */ + public function removeChildren(beginIndex:int=0, endIndex:int=-1, dispose:Boolean=false):void + { + if (endIndex < 0 || endIndex >= numChildren) + endIndex = numChildren - 1; + + for (var i:int=beginIndex; i<=endIndex; ++i) + removeChildAt(beginIndex, dispose); + } + + /** Returns a child object at a certain index. If you pass a negative index, + * '-1' will return the last child, '-2' the second to last child, etc. */ + public function getChildAt(index:int):DisplayObject + { + var numChildren:int = _children.length; + + if (index < 0) + index = numChildren + index; + + if (index >= 0 && index < numChildren) + return _children[index]; + else + throw new RangeError("Invalid child index"); + } + + /** Returns a child object with a certain name (non-recursively). */ + public function getChildByName(name:String):DisplayObject + { + var numChildren:int = _children.length; + for (var i:int=0; i out.x) minX = out.x; + if (maxX < out.right) maxX = out.right; + if (minY > out.y) minY = out.y; + if (maxY < out.bottom) maxY = out.bottom; + } + + out.setTo(minX, minY, maxX - minX, maxY - minY); + } + + return out; + } + + /** @inheritDoc */ + public override function hitTest(localPoint:Point):DisplayObject + { + if (!visible || !touchable || !hitTestMask(localPoint)) return null; + + var target:DisplayObject = null; + var localX:Number = localPoint.x; + var localY:Number = localPoint.y; + var numChildren:int = _children.length; + + for (var i:int = numChildren - 1; i >= 0; --i) // front to back! + { + var child:DisplayObject = _children[i]; + if (child.isMask) continue; + + sHelperMatrix.copyFrom(child.transformationMatrix); + sHelperMatrix.invert(); + + MatrixUtil.transformCoords(sHelperMatrix, localX, localY, sHelperPoint); + target = child.hitTest(sHelperPoint); + + if (target) return _touchGroup ? this : target; + } + + return null; + } + + /** @inheritDoc */ + public override function render(painter:Painter):void + { + var numChildren:int = _children.length; + var frameID:uint = painter.frameID; + var cacheEnabled:Boolean = frameID !=0; + var selfOrParentChanged:Boolean = _lastParentOrSelfChangeFrameID == frameID; + + for (var i:int=0; i, compareFunc:Function, + startIndex:int, length:int, + buffer:Vector.):void + { + // This is a port of the C++ merge sort algorithm shown here: + // http://www.cprogramming.com/tutorial/computersciencetheory/mergesort.html + + if (length > 1) + { + var i:int; + var endIndex:int = startIndex + length; + var halfLength:int = length / 2; + var l:int = startIndex; // current position in the left subvector + var r:int = startIndex + halfLength; // current position in the right subvector + + // sort each subvector + mergeSort(input, compareFunc, startIndex, halfLength, buffer); + mergeSort(input, compareFunc, startIndex + halfLength, length - halfLength, buffer); + + // merge the vectors, using the buffer vector for temporary storage + for (i = 0; i < length; i++) + { + // Check to see if any elements remain in the left vector; + // if so, we check if there are any elements left in the right vector; + // if so, we compare them. Otherwise, we know that the merge must + // take the element from the left vector. */ + if (l < startIndex + halfLength && + (r == endIndex || compareFunc(input[l], input[r]) <= 0)) + { + buffer[i] = input[l]; + l++; + } + else + { + buffer[i] = input[r]; + r++; + } + } + + // copy the sorted subvector back to the input + for(i = startIndex; i < endIndex; i++) + input[i] = buffer[int(i - startIndex)]; + } + } + + /** @private */ + internal function getChildEventListeners(object:DisplayObject, eventType:String, + listeners:Vector.):void + { + var container:DisplayObjectContainer = object as DisplayObjectContainer; + + if (object.hasEventListener(eventType)) + listeners[listeners.length] = object; // avoiding 'push' + + if (container) + { + var children:Vector. = container._children; + var numChildren:int = children.length; + + for (var i:int=0; iTypically, the Image class will act as an equivalent of Flash's Bitmap class. Instead + * of BitmapData, Starling uses textures to represent the pixels of an image. To display a + * texture, you have to map it onto a quad - and that's what the Image class is for.

+ * + *

While the base class Quad already supports textures, the Image + * class adds some additional functionality.

+ * + *

First of all, it provides a convenient constructor that will automatically synchronize + * the size of the image with the displayed texture.

+ * + *

Furthermore, it adds support for a "Scale9" grid. This splits up the image into + * nine regions, the corners of which will always maintain their original size. + * The center region stretches in both directions to fill the remaining space; the side + * regions will stretch accordingly in either horizontal or vertical direction.

+ * + *

Finally, you can repeat a texture horizontally and vertically within the image's region, + * just like the tiles of a wallpaper. Use the tileGrid property to do that.

+ * + * @see starling.textures.Texture + * @see Quad + */ + public class Image extends Quad + { + private var _scale9Grid:Rectangle; + private var _tileGrid:Rectangle; + + // helper objects + private static var sPadding:Padding = new Padding(); + private static var sBounds:Rectangle = new Rectangle(); + private static var sBasCols:Vector. = new Vector.(3, true); + private static var sBasRows:Vector. = new Vector.(3, true); + private static var sPosCols:Vector. = new Vector.(3, true); + private static var sPosRows:Vector. = new Vector.(3, true); + private static var sTexCols:Vector. = new Vector.(3, true); + private static var sTexRows:Vector. = new Vector.(3, true); + + /** Creates an image with a texture mapped onto it. */ + public function Image(texture:Texture) + { + super(100, 100); + this.texture = texture; + readjustSize(); + } + + /** The current scaling grid that is in effect. If set to null, the image is scaled just + * like any other display object; assigning a rectangle will divide the image into a grid + * of nine regions, based on the center rectangle. The four corners of this grid will + * always maintain their original size; the other regions will stretch (horizontally, + * vertically, or both) to fill the complete area. + * + *

Notes:

+ * + *
    + *
  • Assigning a Scale9 rectangle will change the number of vertices to a maximum of 16 + * (less if possible) and all vertices will be colored like vertex 0 (the top left vertex). + *
  • + *
  • For Scale3-grid behavior, assign a zero size for all but the center row / column. + * This will cause the 'caps' to scale in a way that leaves the aspect ratio intact.
  • + *
  • An image can have either a scale9Grid or a tileGrid, but + * not both. Assigning one will delete the other.
  • + *
  • Changes will only be applied on assignment. To force an update, simply call + * image.scale9Grid = image.scale9Grid.
  • + *
  • Assignment causes an implicit call to readjustSize(), + * and the same will happen when the texture is changed afterwards.
  • + *
+ * + * @default null + */ + public function get scale9Grid():Rectangle { return _scale9Grid; } + public function set scale9Grid(value:Rectangle):void + { + if (value) + { + if (_scale9Grid == null) _scale9Grid = value.clone(); + else _scale9Grid.copyFrom(value); + + readjustSize(); + _tileGrid = null; + } + else _scale9Grid = null; + + setupVertices(); + } + + /** The current tiling grid that is in effect. If set to null, the image is scaled just + * like any other display object; assigning a rectangle will divide the image into a grid + * displaying the current texture in each and every cell. The assigned rectangle points + * to the bounds of one cell; all other elements will be calculated accordingly. A zero + * or negative value for the rectangle's width or height will be replaced with the actual + * texture size. Thus, you can make a 2x2 grid simply like this: + * + * + * var image:Image = new Image(texture); + * image.tileGrid = new Rectangle(); + * image.scale = 2; + * + *

Notes:

+ * + *
    + *
  • Assigning a tile rectangle will change the number of vertices to whatever is + * required by the grid. New vertices will be colored just like vertex 0 (the top left + * vertex).
  • + *
  • An image can have either a scale9Grid or a tileGrid, but + * not both. Assigning one will delete the other.
  • + *
  • Changes will only be applied on assignment. To force an update, simply call + * image.tileGrid = image.tileGrid.
  • + *
+ * + * @default null + */ + public function get tileGrid():Rectangle { return _tileGrid; } + public function set tileGrid(value:Rectangle):void + { + if (value) + { + if (_tileGrid == null) _tileGrid = value.clone(); + else _tileGrid.copyFrom(value); + + _scale9Grid = null; + } + else _tileGrid = null; + + setupVertices(); + } + + /** @private */ + override protected function setupVertices():void + { + if (texture && _scale9Grid) setupScale9Grid(); + else if (texture && _tileGrid) setupTileGrid(); + else super.setupVertices(); + } + + /** @private */ + override public function set scaleX(value:Number):void + { + super.scaleX = value; + if (texture && (_scale9Grid || _tileGrid)) setupVertices(); + } + + /** @private */ + override public function set scaleY(value:Number):void + { + super.scaleY = value; + if (texture && (_scale9Grid || _tileGrid)) setupVertices(); + } + + /** @private */ + override public function set texture(value:Texture):void + { + if (value != texture) + { + super.texture = value; + if (_scale9Grid && value) readjustSize(); + } + } + + // vertex setup + + private function setupScale9Grid():void + { + var texture:Texture = this.texture; + var frame:Rectangle = texture.frame; + var absScaleX:Number = scaleX > 0 ? scaleX : -scaleX; + var absScaleY:Number = scaleY > 0 ? scaleY : -scaleY; + + // If top and bottom row / left and right column are empty, this is actually + // a scale3 grid. In that case, we want the 'caps' to maintain their aspect ratio. + + if (MathUtil.isEquivalent(_scale9Grid.width, texture.frameWidth)) + absScaleY /= absScaleX; + else if (MathUtil.isEquivalent(_scale9Grid.height, texture.frameHeight)) + absScaleX /= absScaleY; + + var invScaleX:Number = 1.0 / absScaleX; + var invScaleY:Number = 1.0 / absScaleY; + var vertexData:VertexData = this.vertexData; + var indexData:IndexData = this.indexData; + var prevNumVertices:int = vertexData.numVertices; + var numVertices:int, numQuads:int; + var correction:Number; + + // The following rectangles are used to figure everything out. + // The meaning of each is depicted in this sketch: http://i.imgur.com/KUcv71O.jpg + + var gridCenter:Rectangle = Pool.getRectangle(); + var textureBounds:Rectangle = Pool.getRectangle(); + var pixelBounds:Rectangle = Pool.getRectangle(); + var intersection:Rectangle = Pool.getRectangle(); + + gridCenter.copyFrom(_scale9Grid); + textureBounds.setTo(0, 0, texture.frameWidth, texture.frameHeight); + + if (frame) pixelBounds.setTo(-frame.x, -frame.y, texture.width, texture.height); + else pixelBounds.copyFrom(textureBounds); + + // calculate 3x3 grid according to texture and scale9 properties, + // taking special care about the texture frame (headache included) + + RectangleUtil.intersect(gridCenter, pixelBounds, intersection); + + sBasCols[0] = sBasCols[2] = 0; + sBasRows[0] = sBasRows[2] = 0; + sBasCols[1] = intersection.width; + sBasRows[1] = intersection.height; + + if (pixelBounds.x < gridCenter.x) + sBasCols[0] = gridCenter.x - pixelBounds.x; + + if (pixelBounds.y < gridCenter.y) + sBasRows[0] = gridCenter.y - pixelBounds.y; + + if (pixelBounds.right > gridCenter.right) + sBasCols[2] = pixelBounds.right - gridCenter.right; + + if (pixelBounds.bottom > gridCenter.bottom) + sBasRows[2] = pixelBounds.bottom - gridCenter.bottom; + + // set vertex positions + + if (pixelBounds.x < gridCenter.x) + sPadding.left = pixelBounds.x * invScaleX; + else + sPadding.left = gridCenter.x * invScaleX + pixelBounds.x - gridCenter.x; + + if (pixelBounds.right > gridCenter.right) + sPadding.right = (textureBounds.width - pixelBounds.right) * invScaleX; + else + sPadding.right = (textureBounds.width - gridCenter.right) * invScaleX + gridCenter.right - pixelBounds.right; + + if (pixelBounds.y < gridCenter.y) + sPadding.top = pixelBounds.y * invScaleY; + else + sPadding.top = gridCenter.y * invScaleY + pixelBounds.y - gridCenter.y; + + if (pixelBounds.bottom > gridCenter.bottom) + sPadding.bottom = (textureBounds.height - pixelBounds.bottom) * invScaleY; + else + sPadding.bottom = (textureBounds.height - gridCenter.bottom) * invScaleY + gridCenter.bottom - pixelBounds.bottom; + + sPosCols[0] = sBasCols[0] * invScaleX; + sPosCols[2] = sBasCols[2] * invScaleX; + sPosCols[1] = textureBounds.width - sPadding.left - sPadding.right - sPosCols[0] - sPosCols[2]; + + sPosRows[0] = sBasRows[0] * invScaleY; + sPosRows[2] = sBasRows[2] * invScaleY; + sPosRows[1] = textureBounds.height - sPadding.top - sPadding.bottom - sPosRows[0] - sPosRows[2]; + + // if the total width / height becomes smaller than the outer columns / rows, + // we hide the center column / row and scale the rest normally. + + if (sPosCols[1] <= 0) + { + correction = textureBounds.width / (textureBounds.width - gridCenter.width) * absScaleX; + sPadding.left *= correction; + sPosCols[0] *= correction; + sPosCols[1] = 0.0; + sPosCols[2] *= correction; + } + + if (sPosRows[1] <= 0) + { + correction = textureBounds.height / (textureBounds.height - gridCenter.height) * absScaleY; + sPadding.top *= correction; + sPosRows[0] *= correction; + sPosRows[1] = 0.0; + sPosRows[2] *= correction; + } + + // now set the texture coordinates + + sTexCols[0] = sBasCols[0] / pixelBounds.width; + sTexCols[2] = sBasCols[2] / pixelBounds.width; + sTexCols[1] = 1.0 - sTexCols[0] - sTexCols[2]; + + sTexRows[0] = sBasRows[0] / pixelBounds.height; + sTexRows[2] = sBasRows[2] / pixelBounds.height; + sTexRows[1] = 1.0 - sTexRows[0] - sTexRows[2]; + + numVertices = setupScale9GridAttributes( + sPadding.left, sPadding.top, sPosCols, sPosRows, sTexCols, sTexRows); + + // update indices + + numQuads = numVertices / 4; + vertexData.numVertices = numVertices; + indexData.numIndices = 0; + + for (var i:int=0; i, posRows:Vector., + texCols:Vector., texRows:Vector.):int + { + const posAttr:String = "position"; + const texAttr:String = "texCoords"; + + var row:int, col:int; + var colWidthPos:Number, rowHeightPos:Number; + var colWidthTex:Number, rowHeightTex:Number; + var vertexData:VertexData = this.vertexData; + var texture:Texture = this.texture; + var currentX:Number = startX; + var currentY:Number = startY; + var currentU:Number = 0.0; + var currentV:Number = 0.0; + var vertexID:int = 0; + + for (row = 0; row < 3; ++row) + { + rowHeightPos = posRows[row]; + rowHeightTex = texRows[row]; + + if (rowHeightPos > 0) + { + for (col = 0; col < 3; ++col) + { + colWidthPos = posCols[col]; + colWidthTex = texCols[col]; + + if (colWidthPos > 0) + { + vertexData.setPoint(vertexID, posAttr, currentX, currentY); + texture.setTexCoords(vertexData, vertexID, texAttr, currentU, currentV); + vertexID++; + + vertexData.setPoint(vertexID, posAttr, currentX + colWidthPos, currentY); + texture.setTexCoords(vertexData, vertexID, texAttr, currentU + colWidthTex, currentV); + vertexID++; + + vertexData.setPoint(vertexID, posAttr, currentX, currentY + rowHeightPos); + texture.setTexCoords(vertexData, vertexID, texAttr, currentU, currentV + rowHeightTex); + vertexID++; + + vertexData.setPoint(vertexID, posAttr, currentX + colWidthPos, currentY + rowHeightPos); + texture.setTexCoords(vertexData, vertexID, texAttr, currentU + colWidthTex, currentV + rowHeightTex); + vertexID++; + + currentX += colWidthPos; + } + + currentU += colWidthTex; + } + + currentY += rowHeightPos; + } + + currentX = startX; + currentU = 0.0; + currentV += rowHeightTex; + } + + return vertexID; + } + + private function setupTileGrid():void + { + // calculate the grid of vertices simulating a repeating / tiled texture. + // again, texture frames make this somewhat more complicated than one would think. + + var texture:Texture = this.texture; + var frame:Rectangle = texture.frame; + var vertexData:VertexData = this.vertexData; + var indexData:IndexData = this.indexData; + var bounds:Rectangle = getBounds(this, sBounds); + var prevNumVertices:int = vertexData.numVertices; + var color:uint = prevNumVertices ? vertexData.getColor(0) : 0xffffff; + var alpha:Number = prevNumVertices ? vertexData.getAlpha(0) : 1.0; + var invScaleX:Number = scaleX > 0 ? 1.0 / scaleX : -1.0 / scaleX; + var invScaleY:Number = scaleY > 0 ? 1.0 / scaleY : -1.0 / scaleY; + var frameWidth:Number = _tileGrid.width > 0 ? _tileGrid.width : texture.frameWidth; + var frameHeight:Number = _tileGrid.height > 0 ? _tileGrid.height : texture.frameHeight; + + frameWidth *= invScaleX; + frameHeight *= invScaleY; + + var tileX:Number = frame ? -frame.x * (frameWidth / frame.width) : 0; + var tileY:Number = frame ? -frame.y * (frameHeight / frame.height) : 0; + var tileWidth:Number = texture.width * (frameWidth / texture.frameWidth); + var tileHeight:Number = texture.height * (frameHeight / texture.frameHeight); + var modX:Number = (_tileGrid.x * invScaleX) % frameWidth; + var modY:Number = (_tileGrid.y * invScaleY) % frameHeight; + + if (modX < 0) modX += frameWidth; + if (modY < 0) modY += frameHeight; + + var startX:Number = modX + tileX; + var startY:Number = modY + tileY; + + if (startX > (frameWidth - tileWidth)) startX -= frameWidth; + if (startY > (frameHeight - tileHeight)) startY -= frameHeight; + + var posLeft:Number, posRight:Number, posTop:Number, posBottom:Number; + var texLeft:Number, texRight:Number, texTop:Number, texBottom:Number; + var posAttrName:String = "position"; + var texAttrName:String = "texCoords"; + var currentX:Number; + var currentY:Number = startY; + var vertexID:int = 0; + + indexData.numIndices = 0; + + while (currentY < bounds.height) + { + currentX = startX; + + while (currentX < bounds.width) + { + indexData.addQuad(vertexID, vertexID + 1, vertexID + 2, vertexID + 3); + + posLeft = currentX < 0 ? 0 : currentX; + posTop = currentY < 0 ? 0 : currentY; + posRight = currentX + tileWidth > bounds.width ? bounds.width : currentX + tileWidth; + posBottom = currentY + tileHeight > bounds.height ? bounds.height : currentY + tileHeight; + + vertexData.setPoint(vertexID, posAttrName, posLeft, posTop); + vertexData.setPoint(vertexID + 1, posAttrName, posRight, posTop); + vertexData.setPoint(vertexID + 2, posAttrName, posLeft, posBottom); + vertexData.setPoint(vertexID + 3, posAttrName, posRight, posBottom); + + texLeft = (posLeft - currentX) / tileWidth; + texTop = (posTop - currentY) / tileHeight; + texRight = (posRight - currentX) / tileWidth; + texBottom = (posBottom - currentY) / tileHeight; + + texture.setTexCoords(vertexData, vertexID, texAttrName, texLeft, texTop); + texture.setTexCoords(vertexData, vertexID + 1, texAttrName, texRight, texTop); + texture.setTexCoords(vertexData, vertexID + 2, texAttrName, texLeft, texBottom); + texture.setTexCoords(vertexData, vertexID + 3, texAttrName, texRight, texBottom); + + currentX += frameWidth; + vertexID += 4; + } + + currentY += frameHeight; + } + + // trim to actual size + vertexData.numVertices = vertexID; + + for (var i:int = prevNumVertices; i < vertexID; ++i) + { + vertexData.setColor(i, "color", color); + vertexData.setAlpha(i, "color", alpha); + } + + setRequiresRedraw(); + } + } +} diff --git a/mobile_version/src/starling/display/Mesh.as b/mobile_version/src/starling/display/Mesh.as new file mode 100644 index 00000000..82977fba --- /dev/null +++ b/mobile_version/src/starling/display/Mesh.as @@ -0,0 +1,332 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.display +{ + import flash.geom.Point; + import flash.geom.Rectangle; + + import starling.core.starling_internal; + import starling.geom.Polygon; + import starling.rendering.IndexData; + import starling.rendering.Painter; + import starling.rendering.VertexData; + import starling.rendering.VertexDataFormat; + import starling.styles.MeshStyle; + import starling.textures.Texture; + import starling.utils.MatrixUtil; + import starling.utils.MeshUtil; + import starling.utils.execute; + + use namespace starling_internal; + + /** The base class for all tangible (non-container) display objects, spawned up by a number + * of triangles. + * + *

Since Starling uses Stage3D for rendering, all rendered objects must be constructed + * from triangles. A mesh stores the information of its triangles through VertexData and + * IndexData structures. The default format stores position, color and texture coordinates + * for each vertex.

+ * + *

How a mesh is rendered depends on its style. Per default, this is an instance + * of the MeshStyle base class; however, subclasses may extend its behavior + * to add support for color transformations, normal mapping, etc.

+ * + * @see MeshBatch + * @see starling.styles.MeshStyle + * @see starling.rendering.VertexData + * @see starling.rendering.IndexData + */ + public class Mesh extends DisplayObject + { + /** @private */ internal var _style:MeshStyle; + /** @private */ internal var _vertexData:VertexData; + /** @private */ internal var _indexData:IndexData; + /** @private */ internal var _pixelSnapping:Boolean; + + private static var sDefaultStyle:Class = MeshStyle; + private static var sDefaultStyleFactory:Function = null; + + /** Creates a new mesh with the given vertices and indices. + * If you don't pass a style, an instance of MeshStyle will be created + * for you. Note that the format of the vertex data will be matched to the + * given style right away. */ + public function Mesh(vertexData:VertexData, indexData:IndexData, style:MeshStyle=null) + { + if (vertexData == null) throw new ArgumentError("VertexData must not be null"); + if (indexData == null) throw new ArgumentError("IndexData must not be null"); + + _vertexData = vertexData; + _indexData = indexData; + + setStyle(style, false); + } + + /** @inheritDoc */ + override public function dispose():void + { + _vertexData.clear(); + _indexData.clear(); + + super.dispose(); + } + + /** @inheritDoc */ + override public function hitTest(localPoint:Point):DisplayObject + { + if (!visible || !touchable || !hitTestMask(localPoint)) return null; + else return MeshUtil.containsPoint(_vertexData, _indexData, localPoint) ? this : null; + } + + /** @inheritDoc */ + override public function getBounds(targetSpace:DisplayObject, out:Rectangle=null):Rectangle + { + return MeshUtil.calculateBounds(_vertexData, this, targetSpace, out); + } + + /** @inheritDoc */ + override public function render(painter:Painter):void + { + if (_pixelSnapping) + MatrixUtil.snapToPixels(painter.state.modelviewMatrix, painter.pixelSize); + + painter.batchMesh(this); + } + + /** Sets the style that is used to render the mesh. Styles (which are always subclasses of + * MeshStyle) provide a means to completely modify the way a mesh is rendered. + * For example, they may add support for color transformations or normal mapping. + * + *

When assigning a new style, the vertex format will be changed to fit it. + * Do not use the same style instance on multiple objects! Instead, make use of + * style.clone() to assign an identical style to multiple meshes.

+ * + * @param meshStyle the style to assign. If null, the default + * style will be created. + * @param mergeWithPredecessor if enabled, all attributes of the previous style will be + * be copied to the new one, if possible. + * @see #defaultStyle + * @see #defaultStyleFactory + */ + public function setStyle(meshStyle:MeshStyle=null, mergeWithPredecessor:Boolean=true):void + { + if (meshStyle == null) meshStyle = createDefaultMeshStyle(); + else if (meshStyle == _style) return; + else if (meshStyle.target) meshStyle.target.setStyle(); + + if (_style) + { + if (mergeWithPredecessor) meshStyle.copyFrom(_style); + _style.setTarget(null); + } + + _style = meshStyle; + _style.setTarget(this, _vertexData, _indexData); + } + + private function createDefaultMeshStyle():MeshStyle + { + var meshStyle:MeshStyle; + + if (sDefaultStyleFactory != null) + { + if (sDefaultStyleFactory.length == 0) meshStyle = sDefaultStyleFactory(); + else meshStyle = sDefaultStyleFactory(this); + } + + if (meshStyle == null) + meshStyle = new sDefaultStyle() as MeshStyle; + + return meshStyle; + } + + /** This method is called whenever the mesh's vertex data was changed. + * The base implementation simply forwards to setRequiresRedraw. */ + public function setVertexDataChanged():void + { + setRequiresRedraw(); + } + + /** This method is called whenever the mesh's index data was changed. + * The base implementation simply forwards to setRequiresRedraw. */ + public function setIndexDataChanged():void + { + setRequiresRedraw(); + } + + // vertex manipulation + + /** The position of the vertex at the specified index, in the mesh's local coordinate + * system. + * + *

Only modify the position of a vertex if you know exactly what you're doing, as + * some classes might not work correctly when their vertices are moved. E.g. the + * Quad class expects its vertices to spawn up a perfectly rectangular + * area; some of its optimized methods won't work correctly if that premise is no longer + * fulfilled or the original bounds change.

+ */ + public function getVertexPosition(vertexID:int, out:Point=null):Point + { + return _style.getVertexPosition(vertexID, out); + } + + public function setVertexPosition(vertexID:int, x:Number, y:Number):void + { + _style.setVertexPosition(vertexID, x, y); + } + + /** Returns the alpha value of the vertex at the specified index. */ + public function getVertexAlpha(vertexID:int):Number + { + return _style.getVertexAlpha(vertexID); + } + + /** Sets the alpha value of the vertex at the specified index to a certain value. */ + public function setVertexAlpha(vertexID:int, alpha:Number):void + { + _style.setVertexAlpha(vertexID, alpha); + } + + /** Returns the RGB color of the vertex at the specified index. */ + public function getVertexColor(vertexID:int):uint + { + return _style.getVertexColor(vertexID); + } + + /** Sets the RGB color of the vertex at the specified index to a certain value. */ + public function setVertexColor(vertexID:int, color:uint):void + { + _style.setVertexColor(vertexID, color); + } + + /** Returns the texture coordinates of the vertex at the specified index. */ + public function getTexCoords(vertexID:int, out:Point = null):Point + { + return _style.getTexCoords(vertexID, out); + } + + /** Sets the texture coordinates of the vertex at the specified index to the given values. */ + public function setTexCoords(vertexID:int, u:Number, v:Number):void + { + _style.setTexCoords(vertexID, u, v); + } + + // properties + + /** The vertex data describing all vertices of the mesh. + * Any change requires a call to setRequiresRedraw. */ + protected function get vertexData():VertexData { return _vertexData; } + + /** The index data describing how the vertices are interconnected. + * Any change requires a call to setRequiresRedraw. */ + protected function get indexData():IndexData { return _indexData; } + + /** The style that is used to render the mesh. Styles (which are always subclasses of + * MeshStyle) provide a means to completely modify the way a mesh is rendered. + * For example, they may add support for color transformations or normal mapping. + * + *

The setter will simply forward the assignee to setStyle(value).

+ * + * @default MeshStyle + */ + public function get style():MeshStyle { return _style; } + public function set style(value:MeshStyle):void + { + setStyle(value); + } + + /** The texture that is mapped to the mesh (or null, if there is none). */ + public function get texture():Texture { return _style.texture; } + public function set texture(value:Texture):void { _style.texture = value; } + + /** Changes the color of all vertices to the same value. + * The getter simply returns the color of the first vertex. */ + public function get color():uint { return _style.color; } + public function set color(value:uint):void { _style.color = value; } + + /** The smoothing filter that is used for the texture. + * @default bilinear */ + public function get textureSmoothing():String { return _style.textureSmoothing; } + public function set textureSmoothing(value:String):void { _style.textureSmoothing = value; } + + /** Indicates if pixels at the edges will be repeated or clamped. Only works for + * power-of-two textures; for a solution that works with all kinds of textures, + * see Image.tileGrid. @default false */ + public function get textureRepeat():Boolean { return _style.textureRepeat; } + public function set textureRepeat(value:Boolean):void { _style.textureRepeat = value; } + + /** Controls whether or not the instance snaps to the nearest pixel. This can prevent the + * object from looking blurry when it's not exactly aligned with the pixels of the screen. + * @default false */ + public function get pixelSnapping():Boolean { return _pixelSnapping; } + public function set pixelSnapping(value:Boolean):void { _pixelSnapping = value; } + + /** The total number of vertices in the mesh. */ + public function get numVertices():int { return _vertexData.numVertices; } + + /** The total number of indices referencing vertices. */ + public function get numIndices():int { return _indexData.numIndices; } + + /** The total number of triangles in this mesh. + * (In other words: the number of indices divided by three.) */ + public function get numTriangles():int { return _indexData.numTriangles; } + + /** The format used to store the vertices. */ + public function get vertexFormat():VertexDataFormat { return _style.vertexFormat; } + + // static properties + + /** The default style used for meshes if no specific style is provided. The default is + * starling.rendering.MeshStyle, and any assigned class must be a subclass + * of the same. */ + public static function get defaultStyle():Class { return sDefaultStyle; } + public static function set defaultStyle(value:Class):void + { + sDefaultStyle = value; + } + + /** A factory method that is used to create the 'MeshStyle' for a mesh if no specific + * style is provided. That's useful if you are creating a hierarchy of objects, all + * of which need to have a certain style. Different to the defaultStyle + * property, this method allows plugging in custom logic and passing arguments to the + * constructor. Return null to fall back to the default behavior (i.e. + * to instantiate defaultStyle). The mesh-parameter is optional + * and may be omitted. + * + * + * Mesh.defaultStyleFactory = function(mesh:Mesh):MeshStyle + * { + * return new ColorizeMeshStyle(Math.random() * 0xffffff); + * } + */ + public static function get defaultStyleFactory():Function { return sDefaultStyleFactory; } + public static function set defaultStyleFactory(value:Function):void + { + sDefaultStyleFactory = value; + } + + // static methods + + /** Creates a mesh from the specified polygon. + * Vertex positions and indices will be set up according to the polygon; + * any other vertex attributes (e.g. texture coordinates) need to be set up manually. + */ + public static function fromPolygon(polygon:Polygon, style:MeshStyle=null):Mesh + { + var vertexData:VertexData = new VertexData(null, polygon.numVertices); + var indexData:IndexData = new IndexData(polygon.numTriangles); + + polygon.copyToVertexData(vertexData); + polygon.triangulate(indexData); + + return new Mesh(vertexData, indexData, style); + } + } +} diff --git a/mobile_version/src/starling/display/MeshBatch.as b/mobile_version/src/starling/display/MeshBatch.as new file mode 100644 index 00000000..50ae1c0e --- /dev/null +++ b/mobile_version/src/starling/display/MeshBatch.as @@ -0,0 +1,300 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.display +{ + import flash.geom.Matrix; + + import starling.rendering.IndexData; + import starling.rendering.MeshEffect; + import starling.rendering.Painter; + import starling.rendering.VertexData; + import starling.styles.MeshStyle; + import starling.utils.MatrixUtil; + import starling.utils.MeshSubset; + + /** Combines a number of meshes to one display object and renders them efficiently. + * + *

The most basic tangible (non-container) display object in Starling is the Mesh. + * However, a mesh typically does not render itself; it just holds the data describing its + * geometry. Rendering is orchestrated by the "MeshBatch" class. As its name suggests, it + * acts as a batch for an arbitrary number of Mesh instances; add meshes to a batch and they + * are all rendered together, in one draw call.

+ * + *

You can only batch meshes that share similar properties, e.g. they need to have the + * same texture and the same blend mode. The first object you add to a batch will decide + * this state; call canAddMesh to find out if a new mesh shares that state. + * To reset the current state, you can call clear; this will also remove all + * geometry that has been added thus far.

+ * + *

Starling will use MeshBatch instances (or compatible objects) for all rendering. + * However, you can also instantiate MeshBatch instances yourself and add them to the display + * tree. That makes sense for an object containing a large number of meshes; that way, that + * object can be created once and then rendered very efficiently, without having to copy its + * vertices and indices between buffers and GPU memory.

+ * + * @see Mesh + * @see Sprite + */ + public class MeshBatch extends Mesh + { + /** The maximum number of vertices that fit into one MeshBatch. */ + public static const MAX_NUM_VERTICES:int = 65535; + + private var _effect:MeshEffect; + private var _batchable:Boolean; + private var _vertexSyncRequired:Boolean; + private var _indexSyncRequired:Boolean; + + // helper object + private static var sFullMeshSubset:MeshSubset = new MeshSubset(); + + /** Creates a new, empty MeshBatch instance. */ + public function MeshBatch() + { + var vertexData:VertexData = new VertexData(); + var indexData:IndexData = new IndexData(); + + super(vertexData, indexData); + } + + /** @inheritDoc */ + override public function dispose():void + { + if (_effect) _effect.dispose(); + super.dispose(); + } + + /** This method must be called whenever the mesh's vertex data was changed. Makes + * sure that the vertex buffer is synchronized before rendering, and forces a redraw. */ + override public function setVertexDataChanged():void + { + _vertexSyncRequired = true; + super.setVertexDataChanged(); + } + + /** This method must be called whenever the mesh's index data was changed. Makes + * sure that the index buffer is synchronized before rendering, and forces a redraw. */ + override public function setIndexDataChanged():void + { + _indexSyncRequired = true; + super.setIndexDataChanged(); + } + + private function setVertexAndIndexDataChanged():void + { + _vertexSyncRequired = _indexSyncRequired = true; + } + + private function syncVertexBuffer():void + { + _effect.uploadVertexData(_vertexData); + _vertexSyncRequired = false; + } + + private function syncIndexBuffer():void + { + _effect.uploadIndexData(_indexData); + _indexSyncRequired = false; + } + + /** Removes all geometry. */ + public function clear():void + { + if (_parent) setRequiresRedraw(); + + _vertexData.numVertices = 0; + _indexData.numIndices = 0; + _vertexSyncRequired = true; + _indexSyncRequired = true; + } + + /** Adds a mesh to the batch by appending its vertices and indices. + * + * @param mesh the mesh to add to the batch. + * @param matrix transform all vertex positions with a certain matrix. If this + * parameter is omitted, mesh.transformationMatrix + * will be used instead (except if the last parameter is enabled). + * @param alpha will be multiplied with each vertex' alpha value. + * @param subset the subset of the mesh you want to add, or null for + * the complete mesh. + * @param ignoreTransformations when enabled, the mesh's vertices will be added + * without transforming them in any way (no matter the value of the + * matrix parameter). + */ + public function addMesh(mesh:Mesh, matrix:Matrix=null, alpha:Number=1.0, + subset:MeshSubset=null, ignoreTransformations:Boolean=false):void + { + if (ignoreTransformations) matrix = null; + else if (matrix == null) matrix = mesh.transformationMatrix; + if (subset == null) subset = sFullMeshSubset; + + var targetVertexID:int = _vertexData.numVertices; + var targetIndexID:int = _indexData.numIndices; + var meshStyle:MeshStyle = mesh._style; + + if (targetVertexID == 0) + setupFor(mesh); + + meshStyle.batchVertexData(_style, targetVertexID, matrix, subset.vertexID, subset.numVertices); + meshStyle.batchIndexData(_style, targetIndexID, targetVertexID - subset.vertexID, + subset.indexID, subset.numIndices); + + if (alpha != 1.0) _vertexData.scaleAlphas("color", alpha, targetVertexID, subset.numVertices); + if (_parent) setRequiresRedraw(); + + _indexSyncRequired = _vertexSyncRequired = true; + } + + /** Adds a mesh to the batch by copying its vertices and indices to the given positions. + * Beware that you need to check for yourself if those positions make sense; for example, + * you need to make sure that they are aligned within the 3-indices groups making up + * the mesh's triangles. + * + *

It's easiest to only add objects with an identical setup, e.g. only quads. + * For the latter, indices are aligned in groups of 6 (one quad requires six indices), + * and the vertices in groups of 4 (one vertex for every corner).

+ */ + public function addMeshAt(mesh:Mesh, indexID:int, vertexID:int):void + { + var numIndices:int = mesh.numIndices; + var numVertices:int = mesh.numVertices; + var matrix:Matrix = mesh.transformationMatrix; + var meshStyle:MeshStyle = mesh._style; + + if (_vertexData.numVertices == 0) + setupFor(mesh); + + meshStyle.batchVertexData(_style, vertexID, matrix, 0, numVertices); + meshStyle.batchIndexData(_style, indexID, vertexID, 0, numIndices); + + if (alpha != 1.0) _vertexData.scaleAlphas("color", alpha, vertexID, numVertices); + if (_parent) setRequiresRedraw(); + + _indexSyncRequired = _vertexSyncRequired = true; + } + + private function setupFor(mesh:Mesh):void + { + var meshStyle:MeshStyle = mesh._style; + var meshStyleType:Class = meshStyle.type; + + if (_style.type != meshStyleType) + setStyle(new meshStyleType() as MeshStyle, false); + + _style.copyFrom(meshStyle); + } + + /** Indicates if the given mesh instance fits to the current state of the batch. + * Will always return true for the first added mesh; later calls + * will check if the style matches and if the maximum number of vertices is not + * exceeded. + * + * @param mesh the mesh to add to the batch. + * @param numVertices if -1, mesh.numVertices will be used + */ + public function canAddMesh(mesh:Mesh, numVertices:int=-1):Boolean + { + var currentNumVertices:int = _vertexData.numVertices; + + if (currentNumVertices == 0) return true; + if (numVertices < 0) numVertices = mesh.numVertices; + if (numVertices == 0) return true; + if (numVertices + currentNumVertices > MAX_NUM_VERTICES) return false; + + return _style.canBatchWith(mesh._style); + } + + /** If the batchable property is enabled, this method will add the batch + * to the painter's current batch. Otherwise, this will actually do the drawing. */ + override public function render(painter:Painter):void + { + if (_vertexData.numVertices == 0) return; + if (_pixelSnapping) MatrixUtil.snapToPixels( + painter.state.modelviewMatrix, painter.pixelSize); + + if (_batchable) + { + painter.batchMesh(this); + } + else + { + painter.finishMeshBatch(); + painter.drawCount += 1; + painter.prepareToDraw(); + painter.excludeFromCache(this); + + if (_vertexSyncRequired) syncVertexBuffer(); + if (_indexSyncRequired) syncIndexBuffer(); + + _style.updateEffect(_effect, painter.state); + _effect.render(0, _indexData.numTriangles); + } + } + + /** @inheritDoc */ + override public function setStyle(meshStyle:MeshStyle=null, + mergeWithPredecessor:Boolean=true):void + { + super.setStyle(meshStyle, mergeWithPredecessor); + + if (_effect) + _effect.dispose(); + + _effect = style.createEffect(); + _effect.onRestore = setVertexAndIndexDataChanged; + } + + /** The total number of vertices in the mesh. If you change this to a smaller value, + * the surplus will be deleted. Make sure that no indices reference those deleted + * vertices! */ + public function set numVertices(value:int):void + { + if (_vertexData.numVertices != value) + { + _vertexData.numVertices = value; + _vertexSyncRequired = true; + setRequiresRedraw(); + } + } + + /** The total number of indices in the mesh. If you change this to a smaller value, + * the surplus will be deleted. Always make sure that the number of indices + * is a multiple of three! */ + public function set numIndices(value:int):void + { + if (_indexData.numIndices != value) + { + _indexData.numIndices = value; + _indexSyncRequired = true; + setRequiresRedraw(); + } + } + + /** Indicates if this object will be added to the painter's batch on rendering, + * or if it will draw itself right away. + * + *

Only batchable meshes can profit from the render cache; but batching large meshes + * may take up a lot of CPU time. Activate this property only if the batch contains just + * a handful of vertices (say, 20 quads).

+ * + * @default false + */ + public function get batchable():Boolean { return _batchable; } + public function set batchable(value:Boolean):void + { + if (_batchable != value) + { + _batchable = value; + setRequiresRedraw(); + } + } + } +} diff --git a/mobile_version/src/starling/display/MovieClip.as b/mobile_version/src/starling/display/MovieClip.as new file mode 100644 index 00000000..320a4a2a --- /dev/null +++ b/mobile_version/src/starling/display/MovieClip.as @@ -0,0 +1,479 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.display +{ + import flash.errors.IllegalOperationError; + import flash.media.Sound; + import flash.media.SoundTransform; + + import starling.animation.IAnimatable; + import starling.events.Event; + import starling.textures.Texture; + + /** Dispatched whenever the movie has displayed its last frame. */ + [Event(name="complete", type="starling.events.Event")] + + /** A MovieClip is a simple way to display an animation depicted by a list of textures. + * + *

Pass the frames of the movie in a vector of textures to the constructor. The movie clip + * will have the width and height of the first frame. If you group your frames with the help + * of a texture atlas (which is recommended), use the getTextures-method of the + * atlas to receive the textures in the correct (alphabetic) order.

+ * + *

You can specify the desired framerate via the constructor. You can, however, manually + * give each frame a custom duration. You can also play a sound whenever a certain frame + * appears, or execute a callback (a "frame action").

+ * + *

The methods play and pause control playback of the movie. You + * will receive an event of type Event.COMPLETE when the movie finished + * playback. If the movie is looping, the event is dispatched once per loop.

+ * + *

As any animated object, a movie clip has to be added to a juggler (or have its + * advanceTime method called regularly) to run. The movie will dispatch + * an event of type "Event.COMPLETE" whenever it has displayed its last frame.

+ * + * @see starling.textures.TextureAtlas + */ + public class MovieClip extends Image implements IAnimatable + { + private var _frames:Vector.; + private var _defaultFrameDuration:Number; + private var _currentTime:Number; + private var _currentFrameID:int; + private var _loop:Boolean; + private var _playing:Boolean; + private var _muted:Boolean; + private var _wasStopped:Boolean; + private var _soundTransform:SoundTransform; + + /** Creates a movie clip from the provided textures and with the specified default framerate. + * The movie will have the size of the first frame. */ + public function MovieClip(textures:Vector., fps:Number=12) + { + if (textures.length > 0) + { + super(textures[0]); + init(textures, fps); + } + else + { + throw new ArgumentError("Empty texture array"); + } + } + + private function init(textures:Vector., fps:Number):void + { + if (fps <= 0) throw new ArgumentError("Invalid fps: " + fps); + var numFrames:int = textures.length; + + _defaultFrameDuration = 1.0 / fps; + _loop = true; + _playing = true; + _currentTime = 0.0; + _currentFrameID = 0; + _wasStopped = true; + _frames = new []; + + for (var i:int=0; i numFrames) throw new ArgumentError("Invalid frame id"); + if (duration < 0) duration = _defaultFrameDuration; + + var frame:MovieClipFrame = new MovieClipFrame(texture, duration); + frame.sound = sound; + _frames.insertAt(frameID, frame); + + if (frameID == numFrames) + { + var prevStartTime:Number = frameID > 0 ? _frames[frameID - 1].startTime : 0.0; + var prevDuration:Number = frameID > 0 ? _frames[frameID - 1].duration : 0.0; + frame.startTime = prevStartTime + prevDuration; + } + else + updateStartTimes(); + } + + /** Removes the frame at a certain ID. The successors will move down. */ + public function removeFrameAt(frameID:int):void + { + if (frameID < 0 || frameID >= numFrames) throw new ArgumentError("Invalid frame id"); + if (numFrames == 1) throw new IllegalOperationError("Movie clip must not be empty"); + + _frames.removeAt(frameID); + + if (frameID != numFrames) + updateStartTimes(); + } + + /** Returns the texture of a certain frame. */ + public function getFrameTexture(frameID:int):Texture + { + if (frameID < 0 || frameID >= numFrames) throw new ArgumentError("Invalid frame id"); + return _frames[frameID].texture; + } + + /** Sets the texture of a certain frame. */ + public function setFrameTexture(frameID:int, texture:Texture):void + { + if (frameID < 0 || frameID >= numFrames) throw new ArgumentError("Invalid frame id"); + _frames[frameID].texture = texture; + } + + /** Returns the sound of a certain frame. */ + public function getFrameSound(frameID:int):Sound + { + if (frameID < 0 || frameID >= numFrames) throw new ArgumentError("Invalid frame id"); + return _frames[frameID].sound; + } + + /** Sets the sound of a certain frame. The sound will be played whenever the frame + * is displayed. */ + public function setFrameSound(frameID:int, sound:Sound):void + { + if (frameID < 0 || frameID >= numFrames) throw new ArgumentError("Invalid frame id"); + _frames[frameID].sound = sound; + } + + /** Returns the method that is executed at a certain frame. */ + public function getFrameAction(frameID:int):Function + { + if (frameID < 0 || frameID >= numFrames) throw new ArgumentError("Invalid frame id"); + return _frames[frameID].action; + } + + /** Sets an action that will be executed whenever a certain frame is reached. */ + public function setFrameAction(frameID:int, action:Function):void + { + if (frameID < 0 || frameID >= numFrames) throw new ArgumentError("Invalid frame id"); + _frames[frameID].action = action; + } + + /** Returns the duration of a certain frame (in seconds). */ + public function getFrameDuration(frameID:int):Number + { + if (frameID < 0 || frameID >= numFrames) throw new ArgumentError("Invalid frame id"); + return _frames[frameID].duration; + } + + /** Sets the duration of a certain frame (in seconds). */ + public function setFrameDuration(frameID:int, duration:Number):void + { + if (frameID < 0 || frameID >= numFrames) throw new ArgumentError("Invalid frame id"); + _frames[frameID].duration = duration; + updateStartTimes(); + } + + /** Reverses the order of all frames, making the clip run from end to start. + * Makes sure that the currently visible frame stays the same. */ + public function reverseFrames():void + { + _frames.reverse(); + _currentTime = totalTime - _currentTime; + _currentFrameID = numFrames - _currentFrameID - 1; + updateStartTimes(); + } + + // playback methods + + /** Starts playback. Beware that the clip has to be added to a juggler, too! */ + public function play():void + { + _playing = true; + } + + /** Pauses playback. */ + public function pause():void + { + _playing = false; + } + + /** Stops playback, resetting "currentFrame" to zero. */ + public function stop():void + { + _playing = false; + _wasStopped = true; + currentFrame = 0; + } + + // helpers + + private function updateStartTimes():void + { + var numFrames:int = this.numFrames; + var prevFrame:MovieClipFrame = _frames[0]; + prevFrame.startTime = 0; + + for (var i:int=1; i= restTimeInFrame) + { + changedFrame = false; + passedTime -= restTimeInFrame; + _currentTime = frame.startTime + frame.duration; + + if (_currentFrameID == finalFrameID) + { + if (hasEventListener(Event.COMPLETE)) + { + dispatchCompleteEvent = true; + } + else if (_loop) + { + _currentTime = 0; + _currentFrameID = 0; + changedFrame = true; + } + else return; + } + else + { + _currentFrameID += 1; + changedFrame = true; + } + + frame = _frames[_currentFrameID]; + frameAction = frame.action; + + if (changedFrame) + frame.playSound(_soundTransform); + + if (dispatchCompleteEvent) + { + texture = frame.texture; + dispatchEventWith(Event.COMPLETE); + advanceTime(passedTime); + return; + } + else if (frameAction != null) + { + texture = frame.texture; + frame.executeAction(this, _currentFrameID); + advanceTime(passedTime); + return; + } + + restTimeInFrame = frame.duration; + + // prevent a mean floating point problem (issue #851) + if (passedTime + 0.0001 > restTimeInFrame && passedTime - 0.0001 < restTimeInFrame) + passedTime = restTimeInFrame; + } + + if (previousFrameID != _currentFrameID) + texture = _frames[_currentFrameID].texture; + + _currentTime += passedTime; + } + + // properties + + /** The total number of frames. */ + public function get numFrames():int { return _frames.length; } + + /** The total duration of the clip in seconds. */ + public function get totalTime():Number + { + var lastFrame:MovieClipFrame = _frames[_frames.length-1]; + return lastFrame.startTime + lastFrame.duration; + } + + /** The time that has passed since the clip was started (each loop starts at zero). */ + public function get currentTime():Number { return _currentTime; } + public function set currentTime(value:Number):void + { + if (value < 0 || value > totalTime) throw new ArgumentError("Invalid time: " + value); + + var lastFrameID:int = _frames.length - 1; + _currentTime = value; + _currentFrameID = 0; + + while (_currentFrameID < lastFrameID && _frames[_currentFrameID + 1].startTime <= value) + ++_currentFrameID; + + var frame:MovieClipFrame = _frames[_currentFrameID]; + texture = frame.texture; + } + + /** Indicates if the clip should loop. @default true */ + public function get loop():Boolean { return _loop; } + public function set loop(value:Boolean):void { _loop = value; } + + /** If enabled, no new sounds will be started during playback. Sounds that are already + * playing are not affected. */ + public function get muted():Boolean { return _muted; } + public function set muted(value:Boolean):void { _muted = value; } + + /** The SoundTransform object used for playback of all frame sounds. @default null */ + public function get soundTransform():SoundTransform { return _soundTransform; } + public function set soundTransform(value:SoundTransform):void { _soundTransform = value; } + + /** The index of the frame that is currently displayed. */ + public function get currentFrame():int { return _currentFrameID; } + public function set currentFrame(value:int):void + { + if (value < 0 || value >= numFrames) throw new ArgumentError("Invalid frame id"); + currentTime = _frames[value].startTime; + } + + /** The default number of frames per second. Individual frames can have different + * durations. If you change the fps, the durations of all frames will be scaled + * relatively to the previous value. */ + public function get fps():Number { return 1.0 / _defaultFrameDuration; } + public function set fps(value:Number):void + { + if (value <= 0) throw new ArgumentError("Invalid fps: " + value); + + var newFrameDuration:Number = 1.0 / value; + var acceleration:Number = newFrameDuration / _defaultFrameDuration; + _currentTime *= acceleration; + _defaultFrameDuration = newFrameDuration; + + for (var i:int=0; ifalse when the end + * is reached. */ + public function get isPlaying():Boolean + { + if (_playing) + return _loop || _currentTime < totalTime; + else + return false; + } + + /** Indicates if a (non-looping) movie has come to its end. */ + public function get isComplete():Boolean + { + return !_loop && _currentTime >= totalTime; + } + } +} + +import flash.media.Sound; +import flash.media.SoundTransform; + +import starling.display.MovieClip; +import starling.textures.Texture; + +class MovieClipFrame +{ + public function MovieClipFrame(texture:Texture, duration:Number=0.1, startTime:Number=0) + { + this.texture = texture; + this.duration = duration; + this.startTime = startTime; + } + + public var texture:Texture; + public var sound:Sound; + public var duration:Number; + public var startTime:Number; + public var action:Function; + + public function playSound(transform:SoundTransform):void + { + if (sound) sound.play(0, 0, transform); + } + + public function executeAction(movie:MovieClip, frameID:int):void + { + if (action != null) + { + var numArgs:int = action.length; + + if (numArgs == 0) action(); + else if (numArgs == 1) action(movie); + else if (numArgs == 2) action(movie, frameID); + else throw new Error("Frame actions support zero, one or two parameters: " + + "movie:MovieClip, frameID:int"); + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/display/Quad.as b/mobile_version/src/starling/display/Quad.as new file mode 100644 index 00000000..5c0f1e00 --- /dev/null +++ b/mobile_version/src/starling/display/Quad.as @@ -0,0 +1,209 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.display +{ + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.geom.Vector3D; + + import starling.rendering.IndexData; + import starling.rendering.VertexData; + import starling.styles.MeshStyle; + import starling.textures.Texture; + import starling.utils.RectangleUtil; + + /** A Quad represents a colored and/or textured rectangle. + * + *

Quads may have a color and a texture. When assigning a texture, the colors of the + * vertices will "tint" the texture, i.e. the vertex color will be multiplied with the color + * of the texture at the same position. That's why the default color of a quad is pure white: + * tinting with white does not change the texture color (that's a multiplication with one).

+ * + *

A quad is, by definition, always rectangular. The basic quad class will always contain + * exactly four vertices, arranged like this:

+ * + *
+     *  0 - 1
+     *  | / |
+     *  2 - 3
+     *  
+ * + *

You can set the color of each vertex individually; and since the colors will smoothly + * fade into each other over the area of the quad, you can use this to create simple linear + * color gradients (e.g. by assigning one color to vertices 0 and 1 and another to vertices + * 2 and 3).

+ * + *

However, note that the number of vertices may be different in subclasses. + * Check the property numVertices if you are unsure.

+ * + * @see starling.textures.Texture + * @see Image + */ + public class Quad extends Mesh + { + private var _bounds:Rectangle; + + // helper objects + private static var sPoint3D:Vector3D = new Vector3D(); + private static var sMatrix:Matrix = new Matrix(); + private static var sMatrix3D:Matrix3D = new Matrix3D(); + + /** Creates a quad with a certain size and color. */ + public function Quad(width:Number, height:Number, color:uint=0xffffff) + { + _bounds = new Rectangle(0, 0, width, height); + + var vertexData:VertexData = new VertexData(MeshStyle.VERTEX_FORMAT, 4); + var indexData:IndexData = new IndexData(6); + + super(vertexData, indexData); + + if (width == 0.0 || height == 0.0) + throw new ArgumentError("Invalid size: width and height must not be zero"); + + setupVertices(); + this.color = color; + } + + /** Sets up vertex- and index-data according to the current settings. */ + protected function setupVertices():void + { + var posAttr:String = "position"; + var texAttr:String = "texCoords"; + var texture:Texture = style.texture; + var vertexData:VertexData = this.vertexData; + var indexData:IndexData = this.indexData; + + indexData.numIndices = 0; + indexData.addQuad(0, 1, 2, 3); + + if (vertexData.numVertices != 4) + { + vertexData.numVertices = 4; + vertexData.trim(); + } + + if (texture) + { + texture.setupVertexPositions(vertexData, 0, "position", _bounds); + texture.setupTextureCoordinates(vertexData, 0, texAttr); + } + else + { + vertexData.setPoint(0, posAttr, _bounds.left, _bounds.top); + vertexData.setPoint(1, posAttr, _bounds.right, _bounds.top); + vertexData.setPoint(2, posAttr, _bounds.left, _bounds.bottom); + vertexData.setPoint(3, posAttr, _bounds.right, _bounds.bottom); + + vertexData.setPoint(0, texAttr, 0.0, 0.0); + vertexData.setPoint(1, texAttr, 1.0, 0.0); + vertexData.setPoint(2, texAttr, 0.0, 1.0); + vertexData.setPoint(3, texAttr, 1.0, 1.0); + } + + setRequiresRedraw(); + } + + /** @inheritDoc */ + public override function getBounds(targetSpace:DisplayObject, out:Rectangle=null):Rectangle + { + if (out == null) out = new Rectangle(); + + if (targetSpace == this) // optimization + { + out.copyFrom(_bounds); + } + else if (targetSpace == parent && !isRotated) // optimization + { + var scaleX:Number = this.scaleX; + var scaleY:Number = this.scaleY; + + out.setTo( x - pivotX * scaleX, y - pivotY * scaleY, + _bounds.width * scaleX, _bounds.height * scaleY); + + if (scaleX < 0) { out.width *= -1; out.x -= out.width; } + if (scaleY < 0) { out.height *= -1; out.y -= out.height; } + } + else if (is3D && stage) + { + stage.getCameraPosition(targetSpace, sPoint3D); + getTransformationMatrix3D(targetSpace, sMatrix3D); + RectangleUtil.getBoundsProjected(_bounds, sMatrix3D, sPoint3D, out); + } + else + { + getTransformationMatrix(targetSpace, sMatrix); + RectangleUtil.getBounds(_bounds, sMatrix, out); + } + + return out; + } + + /** @inheritDoc */ + override public function hitTest(localPoint:Point):DisplayObject + { + if (!visible || !touchable || !hitTestMask(localPoint)) return null; + else if (_bounds.containsPoint(localPoint)) return this; + else return null; + } + + /** Readjusts the dimensions of the quad. Use this method without any arguments to + * synchronize quad and texture size after assigning a texture with a different size. + * You can also force a certain width and height by passing positive, non-zero + * values for width and height. */ + public function readjustSize(width:Number=-1, height:Number=-1):void + { + if (width <= 0) width = texture ? texture.frameWidth : _bounds.width; + if (height <= 0) height = texture ? texture.frameHeight : _bounds.height; + + if (width != _bounds.width || height != _bounds.height) + { + _bounds.setTo(0, 0, width, height); + setupVertices(); + } + } + + /** Creates a quad from the given texture. + * The quad will have the same size as the texture. */ + public static function fromTexture(texture:Texture):Quad + { + var quad:Quad = new Quad(100, 100); + quad.texture = texture; + quad.readjustSize(); + return quad; + } + + /** The texture that is mapped to the quad (or null, if there is none). + * Per default, it is mapped to the complete quad, i.e. to the complete area between the + * top left and bottom right vertices. This can be changed with the + * setTexCoords-method. + * + *

Note that the size of the quad will not change when you assign a texture, which + * means that the texture might be distorted at first. Call readjustSize to + * synchronize quad and texture size.

+ * + *

You could also set the texture via the style.texture property. + * That way, however, the texture frame won't be taken into account. Since only rectangular + * objects can make use of a texture frame, only a property on the Quad class can do that. + *

+ */ + override public function set texture(value:Texture):void + { + if (value != texture) + { + super.texture = value; + setupVertices(); + } + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/display/Sprite.as b/mobile_version/src/starling/display/Sprite.as new file mode 100644 index 00000000..2cb45b84 --- /dev/null +++ b/mobile_version/src/starling/display/Sprite.as @@ -0,0 +1,27 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.display +{ + /** A Sprite is the most lightweight, non-abstract container class. + * Use it as a simple means of grouping objects together in one coordinate system. + * + * @see DisplayObject + * @see DisplayObjectContainer + */ + public class Sprite extends DisplayObjectContainer + { + /** Creates an empty sprite. */ + public function Sprite() + { + super(); + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/display/Sprite3D.as b/mobile_version/src/starling/display/Sprite3D.as new file mode 100644 index 00000000..d8951b03 --- /dev/null +++ b/mobile_version/src/starling/display/Sprite3D.as @@ -0,0 +1,372 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.display +{ + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Point; + import flash.geom.Vector3D; + + import starling.events.Event; + import starling.rendering.Painter; + import starling.utils.MathUtil; + import starling.utils.MatrixUtil; + import starling.utils.rad2deg; + + /** A container that allows you to position objects in three-dimensional space. + * + *

Starling is, at its heart, a 2D engine. However, sometimes, simple 3D effects are + * useful for special effects, e.g. for screen transitions or to turn playing cards + * realistically. This class makes it possible to create such 3D effects.

+ * + *

Positioning objects in 3D

+ * + *

Just like a normal sprite, you can add and remove children to this container, which + * allows you to group several display objects together. In addition to that, Sprite3D + * adds some interesting properties:

+ * + *
    + *
  • z - Moves the sprite closer to / further away from the camera.
  • + *
  • rotationX — Rotates the sprite around the x-axis.
  • + *
  • rotationY — Rotates the sprite around the y-axis.
  • + *
  • scaleZ - Scales the sprite along the z-axis.
  • + *
  • pivotZ - Moves the pivot point along the z-axis.
  • + *
+ * + *

With the help of these properties, you can move a sprite and all its children in the + * 3D space. By nesting several Sprite3D containers, it's even possible to construct simple + * volumetric objects (like a cube).

+ * + *

Note that Starling does not make any z-tests: visibility is solely established by the + * order of the children, just as with 2D objects.

+ * + *

Setting up the camera

+ * + *

The camera settings are found directly on the stage. Modify the 'focalLength' or + * 'fieldOfView' properties to change the distance between stage and camera; use the + * 'projectionOffset' to move it to a different position.

+ * + *

Limitations

+ * + *

On rendering, each Sprite3D requires its own draw call — except if the object does not + * contain any 3D transformations ('z', 'rotationX/Y' and 'pivotZ' are zero). Furthermore, + * it interrupts the render cache, i.e. the cache cannot contain objects within different + * 3D coordinate systems. Flat contents within the Sprite3D will be cached, though.

+ * + */ + public class Sprite3D extends DisplayObjectContainer + { + private static const E:Number = 0.00001; + + private var _rotationX:Number; + private var _rotationY:Number; + private var _scaleZ:Number; + private var _pivotZ:Number; + private var _z:Number; + + private var _transformationMatrix:Matrix; + private var _transformationMatrix3D:Matrix3D; + private var _transformationChanged:Boolean; + private var _is2D:Boolean; + + /** Helper objects. */ + private static var sHelperPoint:Vector3D = new Vector3D(); + private static var sHelperPointAlt:Vector3D = new Vector3D(); + private static var sHelperMatrix:Matrix3D = new Matrix3D(); + + /** Creates an empty Sprite3D. */ + public function Sprite3D() + { + _scaleZ = 1.0; + _rotationX = _rotationY = _pivotZ = _z = 0.0; + _transformationMatrix = new Matrix(); + _transformationMatrix3D = new Matrix3D(); + _is2D = true; // meaning: this 3D object contains only 2D content + setIs3D(true); // meaning: this display object supports 3D transformations + + addEventListener(Event.ADDED, onAddedChild); + addEventListener(Event.REMOVED, onRemovedChild); + } + + /** @inheritDoc */ + override public function render(painter:Painter):void + { + if (_is2D) super.render(painter); + else + { + painter.finishMeshBatch(); + painter.pushState(); + painter.state.transformModelviewMatrix3D(transformationMatrix3D); + + super.render(painter); + + painter.finishMeshBatch(); + painter.excludeFromCache(this); + painter.popState(); + } + } + + /** @inheritDoc */ + override public function hitTest(localPoint:Point):DisplayObject + { + if (_is2D) return super.hitTest(localPoint); + else + { + if (!visible || !touchable) return null; + + // We calculate the interception point between the 3D plane that is spawned up + // by this sprite3D and the straight line between the camera and the hit point. + + sHelperMatrix.copyFrom(transformationMatrix3D); + sHelperMatrix.invert(); + + stage.getCameraPosition(this, sHelperPoint); + MatrixUtil.transformCoords3D(sHelperMatrix, localPoint.x, localPoint.y, 0, sHelperPointAlt); + MathUtil.intersectLineWithXYPlane(sHelperPoint, sHelperPointAlt, localPoint); + + return super.hitTest(localPoint); + } + } + + /** @private */ + override public function setRequiresRedraw():void + { + _is2D = _z > -E && _z < E && + _rotationX > -E && _rotationX < E && + _rotationY > -E && _rotationY < E && + _pivotZ > -E && _pivotZ < E; + + super.setRequiresRedraw(); + } + + // helpers + + private function onAddedChild(event:Event):void + { + recursivelySetIs3D(event.target as DisplayObject, true); + } + + private function onRemovedChild(event:Event):void + { + recursivelySetIs3D(event.target as DisplayObject, false); + } + + private function recursivelySetIs3D(object:DisplayObject, value:Boolean):void + { + if (object is Sprite3D) + return; + + if (object is DisplayObjectContainer) + { + var container:DisplayObjectContainer = object as DisplayObjectContainer; + var numChildren:int = container.numChildren; + + for (var i:int=0; iThis class represents the Starling version of the stage. Don't confuse it with its + * Flash equivalent: while the latter contains objects of the type + * flash.display.DisplayObject, the Starling stage contains only objects of the + * type starling.display.DisplayObject. Those classes are not compatible, and + * you cannot exchange one type with the other.

+ * + *

A stage object is created automatically by the Starling class. Don't + * create a Stage instance manually.

+ * + * Keyboard Events + * + *

In Starling, keyboard events are only dispatched at the stage. Add an event listener + * directly to the stage to be notified of keyboard events.

+ * + * Resize Events + * + *

When the Flash player is resized, the stage dispatches a ResizeEvent. The + * event contains properties containing the updated width and height of the Flash player.

+ * + * @see starling.events.KeyboardEvent + * @see starling.events.ResizeEvent + * + */ + public class Stage extends DisplayObjectContainer + { + private var _width:int; + private var _height:int; + private var _color:uint; + private var _fieldOfView:Number; + private var _projectionOffset:Point; + private var _cameraPosition:Vector3D; + private var _enterFrameEvent:EnterFrameEvent; + private var _enterFrameListeners:Vector.; + + // helper objects + private static var sMatrix:Matrix = new Matrix(); + private static var sMatrix3D:Matrix3D = new Matrix3D(); + + /** @private */ + public function Stage(width:int, height:int, color:uint=0) + { + _width = width; + _height = height; + _color = color; + _fieldOfView = 1.0; + _projectionOffset = new Point(); + _cameraPosition = new Vector3D(); + _enterFrameEvent = new EnterFrameEvent(Event.ENTER_FRAME, 0.0); + _enterFrameListeners = new []; + } + + /** @inheritDoc */ + public function advanceTime(passedTime:Number):void + { + _enterFrameEvent.reset(Event.ENTER_FRAME, false, passedTime); + broadcastEvent(_enterFrameEvent); + } + + /** Returns the object that is found topmost beneath a point in stage coordinates, or + * the stage itself if nothing else is found. */ + public override function hitTest(localPoint:Point):DisplayObject + { + if (!visible || !touchable) return null; + + // locations outside of the stage area shouldn't be accepted + if (localPoint.x < 0 || localPoint.x > _width || + localPoint.y < 0 || localPoint.y > _height) + return null; + + // if nothing else is hit, the stage returns itself as target + var target:DisplayObject = super.hitTest(localPoint); + return target ? target : this; + } + + /** Draws the complete stage into a BitmapData object. + * + *

If you encounter problems with transparency, start Starling in BASELINE profile + * (or higher). BASELINE_CONSTRAINED might not support transparency on all platforms. + *

+ * + * @param destination If you pass null, the object will be created for you. + * If you pass a BitmapData object, it should have the size of the + * back buffer (which is accessible via the respective properties + * on the Starling instance). + * @param transparent If enabled, empty areas will appear transparent; otherwise, they + * will be filled with the stage color. + */ + public function drawToBitmapData(destination:BitmapData=null, + transparent:Boolean=true):BitmapData + { + var painter:Painter = Starling.painter; + var state:RenderState = painter.state; + var context:Context3D = painter.context; + + if (destination == null) + { + var width:int = context.backBufferWidth; + var height:int = context.backBufferHeight; + destination = new BitmapData(width, height, transparent); + } + + painter.pushState(); + state.renderTarget = null; + state.setProjectionMatrix(0, 0, _width, _height, _width, _height, cameraPosition); + + if (transparent) painter.clear(); + else painter.clear(_color, 1); + + render(painter); + painter.finishMeshBatch(); + + context.drawToBitmapData(destination); + context.present(); // required on some platforms to avoid flickering + + painter.popState(); + return destination; + } + + /** Returns the stage bounds (i.e. not the bounds of its contents, but the rectangle + * spawned up by 'stageWidth' and 'stageHeight') in another coordinate system. */ + public function getStageBounds(targetSpace:DisplayObject, out:Rectangle=null):Rectangle + { + if (out == null) out = new Rectangle(); + + out.setTo(0, 0, _width, _height); + getTransformationMatrix(targetSpace, sMatrix); + + return RectangleUtil.getBounds(out, sMatrix, out); + } + + // camera positioning + + /** Returns the position of the camera within the local coordinate system of a certain + * display object. If you do not pass a space, the method returns the global position. + * To change the position of the camera, you can modify the properties 'fieldOfView', + * 'focalDistance' and 'projectionOffset'. + */ + public function getCameraPosition(space:DisplayObject=null, out:Vector3D=null):Vector3D + { + getTransformationMatrix3D(space, sMatrix3D); + + return MatrixUtil.transformCoords3D(sMatrix3D, + _width / 2 + _projectionOffset.x, _height / 2 + _projectionOffset.y, + -focalLength, out); + } + + // enter frame event optimization + + /** @private */ + internal function addEnterFrameListener(listener:DisplayObject):void + { + var index:int = _enterFrameListeners.indexOf(listener); + if (index < 0) _enterFrameListeners[_enterFrameListeners.length] = listener; + } + + /** @private */ + internal function removeEnterFrameListener(listener:DisplayObject):void + { + var index:int = _enterFrameListeners.indexOf(listener); + if (index >= 0) _enterFrameListeners.removeAt(index); + } + + /** @private */ + internal override function getChildEventListeners(object:DisplayObject, eventType:String, + listeners:Vector.):void + { + if (eventType == Event.ENTER_FRAME && object == this) + { + for (var i:int=0, length:int=_enterFrameListeners.length; iviewPort property of the Starling object. */ + public function get stageWidth():int { return _width; } + public function set stageWidth(value:int):void { _width = value; } + + /** The height of the stage coordinate system. Change it to scale its contents relative + * to the viewPort property of the Starling object. */ + public function get stageHeight():int { return _height; } + public function set stageHeight(value:int):void { _height = value; } + + /** The Starling instance this stage belongs to. */ + public function get starling():Starling + { + var instances:Vector. = Starling.all; + var numInstances:int = instances.length; + + for (var i:int=0; iA value close to zero will look similar to an orthographic projection; a value + * close to PI results in a fisheye lens effect. If the field of view is set to 0 or PI, + * nothing is seen on the screen.

+ * + * @default 1.0 + */ + public function get fieldOfView():Number { return _fieldOfView; } + public function set fieldOfView(value:Number):void { _fieldOfView = value; } + + /** A vector that moves the camera away from its default position in the center of the + * stage. Use this property to change the center of projection, i.e. the vanishing + * point for 3D display objects.

CAUTION: not a copy, but the actual object!

+ */ + public function get projectionOffset():Point { return _projectionOffset; } + public function set projectionOffset(value:Point):void + { + _projectionOffset.setTo(value.x, value.y); + } + + /** The global position of the camera. This property can only be used to find out the + * current position, but not to modify it. For that, use the 'projectionOffset', + * 'fieldOfView' and 'focalLength' properties. If you need the camera position in + * a certain coordinate space, use 'getCameraPosition' instead. + * + *

CAUTION: not a copy, but the actual object!

+ */ + public function get cameraPosition():Vector3D + { + return getCameraPosition(null, _cameraPosition); + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/errors/AbstractClassError.as b/mobile_version/src/starling/errors/AbstractClassError.as new file mode 100644 index 00000000..a9168935 --- /dev/null +++ b/mobile_version/src/starling/errors/AbstractClassError.as @@ -0,0 +1,23 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.errors +{ + /** An AbstractClassError is thrown when you attempt to create an instance of an abstract + * class. */ + public class AbstractClassError extends Error + { + /** Creates a new AbstractClassError object. */ + public function AbstractClassError(message:*="Cannot instantiate abstract class", id:*=0) + { + super(message, id); + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/errors/AbstractMethodError.as b/mobile_version/src/starling/errors/AbstractMethodError.as new file mode 100644 index 00000000..3206babe --- /dev/null +++ b/mobile_version/src/starling/errors/AbstractMethodError.as @@ -0,0 +1,22 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.errors +{ + /** An AbstractMethodError is thrown when you attempt to call an abstract method. */ + public class AbstractMethodError extends Error + { + /** Creates a new AbstractMethodError object. */ + public function AbstractMethodError(message:*="Method needs to be implemented in subclass", id:*=0) + { + super(message, id); + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/errors/MissingContextError.as b/mobile_version/src/starling/errors/MissingContextError.as new file mode 100644 index 00000000..114735ee --- /dev/null +++ b/mobile_version/src/starling/errors/MissingContextError.as @@ -0,0 +1,23 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.errors +{ + /** A MissingContextError is thrown when a Context3D object is required but not (yet) + * available. */ + public class MissingContextError extends Error + { + /** Creates a new MissingContextError object. */ + public function MissingContextError(message:*="Starling context is missing", id:*=0) + { + super(message, id); + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/errors/NotSupportedError.as b/mobile_version/src/starling/errors/NotSupportedError.as new file mode 100644 index 00000000..abfacdf6 --- /dev/null +++ b/mobile_version/src/starling/errors/NotSupportedError.as @@ -0,0 +1,23 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.errors +{ + /** A NotSupportedError is thrown when you attempt to use a feature that is not supported + * on the current platform. */ + public class NotSupportedError extends Error + { + /** Creates a new NotSupportedError object. */ + public function NotSupportedError(message:* = "", id:* = 0) + { + super(message, id); + } + } +} diff --git a/mobile_version/src/starling/events/EnterFrameEvent.as b/mobile_version/src/starling/events/EnterFrameEvent.as new file mode 100644 index 00000000..de605a0f --- /dev/null +++ b/mobile_version/src/starling/events/EnterFrameEvent.as @@ -0,0 +1,34 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.events +{ + /** An EnterFrameEvent 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 independent of the frame rate, taking the passed time + * into account. + */ + public class EnterFrameEvent extends Event + { + /** Event type for a display object that is entering a new frame. */ + public static const ENTER_FRAME:String = "enterFrame"; + + /** Creates an enter frame event with the passed time. */ + public function EnterFrameEvent(type:String, passedTime:Number, bubbles:Boolean=false) + { + super(type, bubbles, passedTime); + } + + /** The time that has passed since the last frame (in seconds). */ + public function get passedTime():Number { return data as Number; } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/events/Event.as b/mobile_version/src/starling/events/Event.as new file mode 100644 index 00000000..395d22b8 --- /dev/null +++ b/mobile_version/src/starling/events/Event.as @@ -0,0 +1,190 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.events +{ + import flash.utils.getQualifiedClassName; + + import starling.core.starling_internal; + import starling.utils.StringUtil; + + use namespace starling_internal; + + /** Event objects are passed as parameters to event listeners when an event occurs. + * This is Starling's version of the Flash Event class. + * + *

EventDispatchers create instances of this class and send them to registered listeners. + * An event object 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 "Event" and add + * properties with all the information you require. The "EnterFrameEvent" 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.

+ * + * @see EventDispatcher + */ + public class Event + { + /** Event type for a display object that is added to a parent. */ + public static const ADDED:String = "added"; + /** Event type for a display object that is added to the stage */ + public static const ADDED_TO_STAGE:String = "addedToStage"; + /** Event type for a display object that is entering a new frame. */ + public static const ENTER_FRAME:String = "enterFrame"; + /** Event type for a display object that is removed from its parent. */ + public static const REMOVED:String = "removed"; + /** Event type for a display object that is removed from the stage. */ + public static const REMOVED_FROM_STAGE:String = "removedFromStage"; + /** Event type for a triggered button. */ + public static const TRIGGERED:String = "triggered"; + /** Event type for a resized Flash Player. */ + public static const RESIZE:String = "resize"; + /** Event type that may be used whenever something finishes. */ + public static const COMPLETE:String = "complete"; + /** Event type for a (re)created stage3D rendering context. */ + public static const CONTEXT3D_CREATE:String = "context3DCreate"; + /** Event type that is dispatched by the Starling instance directly before rendering. */ + public static const RENDER:String = "render"; + /** Event type that indicates that the root DisplayObject has been created. */ + public static const ROOT_CREATED:String = "rootCreated"; + /** Event type for an animated object that requests to be removed from the juggler. */ + public static const REMOVE_FROM_JUGGLER:String = "removeFromJuggler"; + /** Event type that is dispatched by the AssetManager after a context loss. */ + public static const TEXTURES_RESTORED:String = "texturesRestored"; + /** Event type that is dispatched by the AssetManager when a file/url cannot be loaded. */ + public static const IO_ERROR:String = "ioError"; + /** Event type that is dispatched by the AssetManager when a file/url cannot be loaded. */ + public static const SECURITY_ERROR:String = "securityError"; + /** Event type that is dispatched by the AssetManager when an xml or json file couldn't + * be parsed. */ + public static const PARSE_ERROR:String = "parseError"; + /** Event type that is dispatched by the Starling instance when it encounters a problem + * from which it cannot recover, e.g. a lost device context. */ + public static const FATAL_ERROR:String = "fatalError"; + + /** An event type to be utilized in custom events. Not used by Starling right now. */ + public static const CHANGE:String = "change"; + /** An event type to be utilized in custom events. Not used by Starling right now. */ + public static const CANCEL:String = "cancel"; + /** An event type to be utilized in custom events. Not used by Starling right now. */ + public static const SCROLL:String = "scroll"; + /** An event type to be utilized in custom events. Not used by Starling right now. */ + public static const OPEN:String = "open"; + /** An event type to be utilized in custom events. Not used by Starling right now. */ + public static const CLOSE:String = "close"; + /** An event type to be utilized in custom events. Not used by Starling right now. */ + public static const SELECT:String = "select"; + /** An event type to be utilized in custom events. Not used by Starling right now. */ + public static const READY:String = "ready"; + /** An event type to be utilized in custom events. Not used by Starling right now. */ + public static const UPDATE:String = "update"; + + private static var sEventPool:Vector. = new []; + + private var _target:EventDispatcher; + private var _currentTarget:EventDispatcher; + private var _type:String; + private var _bubbles:Boolean; + private var _stopsPropagation:Boolean; + private var _stopsImmediatePropagation:Boolean; + private var _data:Object; + + /** Creates an event object that can be passed to listeners. */ + public function Event(type:String, bubbles:Boolean=false, data:Object=null) + { + _type = type; + _bubbles = bubbles; + _data = data; + } + + /** Prevents listeners at the next bubble stage from receiving the event. */ + public function stopPropagation():void + { + _stopsPropagation = true; + } + + /** Prevents any other listeners from receiving the event. */ + public function stopImmediatePropagation():void + { + _stopsPropagation = _stopsImmediatePropagation = true; + } + + /** Returns a description of the event, containing type and bubble information. */ + public function toString():String + { + return StringUtil.format("[{0} type=\"{1}\" bubbles={2}]", + getQualifiedClassName(this).split("::").pop(), _type, _bubbles); + } + + /** Indicates if event will bubble. */ + public function get bubbles():Boolean { return _bubbles; } + + /** The object that dispatched the event. */ + public function get target():EventDispatcher { return _target; } + + /** The object the event is currently bubbling at. */ + public function get currentTarget():EventDispatcher { return _currentTarget; } + + /** A string that identifies the event. */ + public function get type():String { return _type; } + + /** Arbitrary data that is attached to the event. */ + public function get data():Object { return _data; } + + // properties for internal use + + /** @private */ + internal function setTarget(value:EventDispatcher):void { _target = value; } + + /** @private */ + internal function setCurrentTarget(value:EventDispatcher):void { _currentTarget = value; } + + /** @private */ + internal function setData(value:Object):void { _data = value; } + + /** @private */ + internal function get stopsPropagation():Boolean { return _stopsPropagation; } + + /** @private */ + internal function get stopsImmediatePropagation():Boolean { return _stopsImmediatePropagation; } + + // event pooling + + /** @private */ + starling_internal static function fromPool(type:String, bubbles:Boolean=false, data:Object=null):Event + { + if (sEventPool.length) return sEventPool.pop().reset(type, bubbles, data); + else return new Event(type, bubbles, data); + } + + /** @private */ + starling_internal static function toPool(event:Event):void + { + event._data = event._target = event._currentTarget = null; + sEventPool[sEventPool.length] = event; // avoiding 'push' + } + + /** @private */ + starling_internal function reset(type:String, bubbles:Boolean=false, data:Object=null):Event + { + _type = type; + _bubbles = bubbles; + _data = data; + _target = _currentTarget = null; + _stopsPropagation = _stopsImmediatePropagation = false; + return this; + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/events/EventDispatcher.as b/mobile_version/src/starling/events/EventDispatcher.as new file mode 100644 index 00000000..fad3f714 --- /dev/null +++ b/mobile_version/src/starling/events/EventDispatcher.as @@ -0,0 +1,215 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.events +{ + import flash.utils.Dictionary; + + import starling.core.starling_internal; + import starling.display.DisplayObject; + + use namespace starling_internal; + + /** The EventDispatcher class is the base class for all classes that dispatch events. + * This is the Starling version of the Flash class with the same name. + * + *

The event mechanism is a key feature of Starling's architecture. Objects can communicate + * with each other through events. Compared the the Flash event system, Starling's event system + * was simplified. The main difference is that Starling events have no "Capture" phase. + * They are simply dispatched at the target and may optionally bubble up. They cannot move + * in the opposite direction.

+ * + *

As in the conventional Flash classes, display objects inherit from EventDispatcher + * and can thus dispatch events. Beware, though, that the Starling event classes are + * not compatible with Flash events: Starling display objects dispatch + * Starling events, which will bubble along Starling display objects - but they cannot + * dispatch Flash events or bubble along Flash display objects.

+ * + * @see Event + * @see starling.display.DisplayObject DisplayObject + */ + public class EventDispatcher + { + private var _eventListeners:Dictionary; + + /** Helper object. */ + private static var sBubbleChains:Array = []; + + /** Creates an EventDispatcher. */ + public function EventDispatcher() + { } + + /** Registers an event listener at a certain object. */ + public function addEventListener(type:String, listener:Function):void + { + if (_eventListeners == null) + _eventListeners = new Dictionary(); + + var listeners:Vector. = _eventListeners[type] as Vector.; + if (listeners == null) + _eventListeners[type] = new [listener]; + else if (listeners.indexOf(listener) == -1) // check for duplicates + listeners[listeners.length] = listener; // avoid 'push' + } + + /** Removes an event listener from the object. */ + public function removeEventListener(type:String, listener:Function):void + { + if (_eventListeners) + { + var listeners:Vector. = _eventListeners[type] as Vector.; + var numListeners:int = listeners ? listeners.length : 0; + + if (numListeners > 0) + { + // we must not modify the original vector, but work on a copy. + // (see comment in 'invokeEvent') + + var index:int = listeners.indexOf(listener); + + if (index != -1) + { + var restListeners:Vector. = listeners.slice(0, index); + + for (var i:int=index+1; i = _eventListeners ? + _eventListeners[event.type] as Vector. : null; + var numListeners:int = listeners == null ? 0 : listeners.length; + + if (numListeners) + { + event.setCurrentTarget(this); + + // we can enumerate directly over the vector, because: + // when somebody modifies the list while we're looping, "addEventListener" is not + // problematic, and "removeEventListener" will create a new Vector, anyway. + + for (var i:int=0; i; + var element:DisplayObject = this as DisplayObject; + var length:int = 1; + + if (sBubbleChains.length > 0) { chain = sBubbleChains.pop(); chain[0] = element; } + else chain = new [element]; + + while ((element = element.parent) != null) + chain[int(length++)] = element; + + for (var i:int=0; i = _eventListeners ? _eventListeners[type] : null; + if (listeners == null) return false; + else + { + if (listener != null) return listeners.indexOf(listener) != -1; + else return listeners.length != 0; + } + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/events/KeyboardEvent.as b/mobile_version/src/starling/events/KeyboardEvent.as new file mode 100644 index 00000000..7c388ce9 --- /dev/null +++ b/mobile_version/src/starling/events/KeyboardEvent.as @@ -0,0 +1,88 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.events +{ + /** A KeyboardEvent is dispatched in response to user input through a keyboard. + * + *

This is Starling's version of the Flash KeyboardEvent class. It contains the same + * properties as the Flash equivalent.

+ * + *

To be notified of keyboard events, add an event listener to any display object that + * is part of your display tree. Starling has no concept of a "Focus" like native Flash.

+ * + * @see starling.display.Stage + */ + public class KeyboardEvent extends Event + { + /** Event type for a key that was released. */ + public static const KEY_UP:String = "keyUp"; + + /** Event type for a key that was pressed. */ + public static const KEY_DOWN:String = "keyDown"; + + private var _charCode:uint; + private var _keyCode:uint; + private var _keyLocation:uint; + private var _altKey:Boolean; + private var _ctrlKey:Boolean; + private var _shiftKey:Boolean; + private var _isDefaultPrevented:Boolean; + + /** Creates a new KeyboardEvent. */ + public function KeyboardEvent(type:String, charCode:uint=0, keyCode:uint=0, + keyLocation:uint=0, ctrlKey:Boolean=false, + altKey:Boolean=false, shiftKey:Boolean=false) + { + super(type, false, keyCode); + _charCode = charCode; + _keyCode = keyCode; + _keyLocation = keyLocation; + _ctrlKey = ctrlKey; + _altKey = altKey; + _shiftKey = shiftKey; + } + + // prevent default + + /** Cancels the keyboard event's default behavior. This will be forwarded to the native + * flash KeyboardEvent. */ + public function preventDefault():void + { + _isDefaultPrevented = true; + } + + /** Checks whether the preventDefault() method has been called on the event. */ + public function isDefaultPrevented():Boolean { return _isDefaultPrevented; } + + // properties + + /** Contains the character code of the key. */ + public function get charCode():uint { return _charCode; } + + /** The key code of the key. */ + public function get keyCode():uint { return _keyCode; } + + /** Indicates the location of the key on the keyboard. This is useful for differentiating + * keys that appear more than once on a keyboard. @see Keylocation */ + public function get keyLocation():uint { return _keyLocation; } + + /** Indicates whether the Alt key is active on Windows or Linux; + * indicates whether the Option key is active on Mac OS. */ + public function get altKey():Boolean { return _altKey; } + + /** Indicates whether the Ctrl key is active on Windows or Linux; + * indicates whether either the Ctrl or the Command key is active on Mac OS. */ + public function get ctrlKey():Boolean { return _ctrlKey; } + + /** Indicates whether the Shift key modifier is active (true) or inactive (false). */ + public function get shiftKey():Boolean { return _shiftKey; } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/events/ResizeEvent.as b/mobile_version/src/starling/events/ResizeEvent.as new file mode 100644 index 00000000..8f4162b7 --- /dev/null +++ b/mobile_version/src/starling/events/ResizeEvent.as @@ -0,0 +1,44 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.events +{ + import flash.geom.Point; + + /** A ResizeEvent is dispatched by the stage when the size of the Flash container changes. + * Use it to update the Starling viewport and the stage size. + * + *

The event contains properties containing the updated width and height of the Flash + * player. If you want to scale the contents of your stage to fill the screen, update the + * Starling.current.viewPort rectangle accordingly. If you want to make use of + * the additional screen estate, update the values of stage.stageWidth and + * stage.stageHeight as well.

+ * + * @see starling.display.Stage + * @see starling.core.Starling + */ + public class ResizeEvent extends Event + { + /** Event type for a resized Flash player. */ + public static const RESIZE:String = "resize"; + + /** Creates a new ResizeEvent. */ + public function ResizeEvent(type:String, width:int, height:int, bubbles:Boolean=false) + { + super(type, bubbles, new Point(width, height)); + } + + /** The updated width of the player. */ + public function get width():int { return (data as Point).x; } + + /** The updated height of the player. */ + public function get height():int { return (data as Point).y; } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/events/Touch.as b/mobile_version/src/starling/events/Touch.as new file mode 100644 index 00000000..f8fd139d --- /dev/null +++ b/mobile_version/src/starling/events/Touch.as @@ -0,0 +1,240 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.events +{ + import flash.geom.Point; + + import starling.display.DisplayObject; + import starling.utils.StringUtil; + + /** A Touch object contains information about the presence or movement of a finger + * or the mouse on the screen. + * + *

You receive objects of this type from a TouchEvent. When such an event is triggered, + * you can query it for all touches that are currently present on the screen. One touch + * object contains information about a single touch; it always transitions through a series + * of TouchPhases. Have a look at the TouchPhase class for more information.

+ * + * The position of a touch + * + *

You can get the current and previous position in stage coordinates with the 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.

+ * + * @see TouchEvent + * @see TouchPhase + */ + public class Touch + { + private var _id:int; + private var _globalX:Number; + private var _globalY:Number; + private var _previousGlobalX:Number; + private var _previousGlobalY:Number; + private var _tapCount:int; + private var _phase:String; + private var _target:DisplayObject; + private var _timestamp:Number; + private var _pressure:Number; + private var _width:Number; + private var _height:Number; + private var _cancelled:Boolean; + private var _bubbleChain:Vector.; + + /** Helper object. */ + private static var sHelperPoint:Point = new Point(); + + /** Creates a new Touch object. */ + public function Touch(id:int) + { + _id = id; + _tapCount = 0; + _phase = TouchPhase.HOVER; + _pressure = _width = _height = 1.0; + _bubbleChain = new []; + } + + /** Converts the current location of a touch to the local coordinate system of a display + * object. If you pass an out-point, the result will be stored in this point + * instead of creating a new object.*/ + public function getLocation(space:DisplayObject, out:Point=null):Point + { + sHelperPoint.setTo(_globalX, _globalY); + return space.globalToLocal(sHelperPoint, out); + } + + /** Converts the previous location of a touch to the local coordinate system of a display + * object. If you pass an out-point, the result will be stored in this point + * instead of creating a new object.*/ + public function getPreviousLocation(space:DisplayObject, out:Point=null):Point + { + sHelperPoint.setTo(_previousGlobalX, _previousGlobalY); + return space.globalToLocal(sHelperPoint, out); + } + + /** Returns the movement of the touch between the current and previous location. + * If you pass an out-point, the result will be stored in this point instead + * of creating a new object. */ + public function getMovement(space:DisplayObject, out:Point=null):Point + { + if (out == null) out = new Point(); + getLocation(space, out); + var x:Number = out.x; + var y:Number = out.y; + getPreviousLocation(space, out); + out.setTo(x - out.x, y - out.y); + return out; + } + + /** Indicates if the target or one of its children is touched. */ + public function isTouching(target:DisplayObject):Boolean + { + return _bubbleChain.indexOf(target) != -1; + } + + /** Returns a description of the object. */ + public function toString():String + { + return StringUtil.format("[Touch {0}: globalX={1}, globalY={2}, phase={3}]", + _id, _globalX, _globalY, _phase); + } + + /** Creates a clone of the Touch object. */ + public function clone():Touch + { + var clone:Touch = new Touch(_id); + clone._globalX = _globalX; + clone._globalY = _globalY; + clone._previousGlobalX = _previousGlobalX; + clone._previousGlobalY = _previousGlobalY; + clone._phase = _phase; + clone._tapCount = _tapCount; + clone._timestamp = _timestamp; + clone._pressure = _pressure; + clone._width = _width; + clone._height = _height; + clone._cancelled = _cancelled; + clone.target = _target; + return clone; + } + + // helper methods + + private function updateBubbleChain():void + { + if (_target) + { + var length:int = 1; + var element:DisplayObject = _target; + + _bubbleChain.length = 1; + _bubbleChain[0] = element; + + while ((element = element.parent) != null) + _bubbleChain[int(length++)] = element; + } + else + { + _bubbleChain.length = 0; + } + } + + // properties + + /** The identifier of a touch. '0' for mouse events, an increasing number for touches. */ + public function get id():int { return _id; } + + /** The previous x-position of the touch in stage coordinates. */ + public function get previousGlobalX():Number { return _previousGlobalX; } + + /** The previous y-position of the touch in stage coordinates. */ + public function get previousGlobalY():Number { return _previousGlobalY; } + + /** The x-position of the touch in stage coordinates. If you change this value, + * the previous one will be moved to "previousGlobalX". */ + public function get globalX():Number { return _globalX; } + public function set globalX(value:Number):void + { + _previousGlobalX = _globalX != _globalX ? value : _globalX; // isNaN check + _globalX = value; + } + + /** The y-position of the touch in stage coordinates. If you change this value, + * the previous one will be moved to "previousGlobalY". */ + public function get globalY():Number { return _globalY; } + public function set globalY(value:Number):void + { + _previousGlobalY = _globalY != _globalY ? value : _globalY; // isNaN check + _globalY = value; + } + + /** The number of taps the finger made in a short amount of time. Use this to detect + * double-taps / double-clicks, etc. */ + public function get tapCount():int { return _tapCount; } + public function set tapCount(value:int):void { _tapCount = value; } + + /** The current phase the touch is in. @see TouchPhase */ + public function get phase():String { return _phase; } + public function set phase(value:String):void { _phase = value; } + + /** The display object at which the touch occurred. */ + public function get target():DisplayObject { return _target; } + public function set target(value:DisplayObject):void + { + if (_target != value) + { + _target = value; + updateBubbleChain(); + } + } + + /** The moment the touch occurred (in seconds since application start). */ + public function get timestamp():Number { return _timestamp; } + public function set timestamp(value:Number):void { _timestamp = value; } + + /** A value between 0.0 and 1.0 indicating force of the contact with the device. + * If the device does not support detecting the pressure, the value is 1.0. */ + public function get pressure():Number { return _pressure; } + public function set pressure(value:Number):void { _pressure = value; } + + /** Width of the contact area. + * If the device does not support detecting the pressure, the value is 1.0. */ + public function get width():Number { return _width; } + public function set width(value:Number):void { _width = value; } + + /** Height of the contact area. + * If the device does not support detecting the pressure, the value is 1.0. */ + public function get height():Number { return _height; } + public function set height(value:Number):void { _height = value; } + + /** Indicates if the touch has been cancelled, which may happen when the app moves into + * the background ('Event.DEACTIVATE'). @default false */ + public function get cancelled():Boolean { return _cancelled; } + public function set cancelled(value:Boolean):void { _cancelled = value; } + + // internal methods + + /** @private + * Dispatches a touch event along the current bubble chain (which is updated each time + * a target is set). */ + internal function dispatchEvent(event:TouchEvent):void + { + if (_target) event.dispatch(_bubbleChain); + } + + /** @private */ + internal function get bubbleChain():Vector. + { + return _bubbleChain.concat(); + } + } +} diff --git a/mobile_version/src/starling/events/TouchEvent.as b/mobile_version/src/starling/events/TouchEvent.as new file mode 100644 index 00000000..613c4071 --- /dev/null +++ b/mobile_version/src/starling/events/TouchEvent.as @@ -0,0 +1,218 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.events +{ + import starling.core.starling_internal; + import starling.display.DisplayObject; + + use namespace starling_internal; + + /** A TouchEvent is triggered either by touch or mouse input. + * + *

In Starling, both touch events and mouse events are handled through the same class: + * TouchEvent. To process user input from a touch screen or the mouse, you have to register + * an event listener for events of the type TouchEvent.TOUCH. This is the only + * event type you need to handle; the long list of mouse event types as they are used in + * conventional Flash are mapped to so-called "TouchPhases" instead.

+ * + *

The difference between mouse input and touch input is that

+ * + *
    + *
  • only one mouse cursor can be present at a given moment and
  • + *
  • only the mouse can "hover" over an object without a pressed button.
  • + *
+ * + * Which objects receive touch events? + * + *

In Starling, any display object receives touch events, as long as the + * touchable property of the object and its parents is enabled. There + * is no "InteractiveObject" class in Starling.

+ * + * How to work with individual touches + * + *

The event contains a list of all touches that are currently present. Each individual + * touch is stored in an object of type "Touch". Since you are normally only interested in + * the touches that occurred on top of certain objects, you can query the event for touches + * with a specific target:

+ * + * var touches:Vector.<Touch> = touchEvent.getTouches(this); + * + *

This will return all touches of "this" or one of its children. When you are not using + * multitouch, you can also access the touch object directly, like this:

+ * + * var touch:Touch = touchEvent.getTouch(this); + * + * @see Touch + * @see TouchPhase + */ + public class TouchEvent extends Event + { + /** Event type for touch or mouse input. */ + public static const TOUCH:String = "touch"; + + private var _shiftKey:Boolean; + private var _ctrlKey:Boolean; + private var _timestamp:Number; + private var _visitedObjects:Vector.; + + /** Helper object. */ + private static var sTouches:Vector. = new []; + + /** Creates a new TouchEvent instance. */ + public function TouchEvent(type:String, touches:Vector.=null, shiftKey:Boolean=false, + ctrlKey:Boolean=false, bubbles:Boolean=true) + { + super(type, bubbles, touches); + + _shiftKey = shiftKey; + _ctrlKey = ctrlKey; + _visitedObjects = new []; + + updateTimestamp(touches); + } + + /** @private */ + internal function resetTo(type:String, touches:Vector.=null, shiftKey:Boolean=false, + ctrlKey:Boolean=false, bubbles:Boolean=true):TouchEvent + { + super.reset(type, bubbles, touches); + + _shiftKey = shiftKey; + _ctrlKey = ctrlKey; + _visitedObjects.length = 0; + updateTimestamp(touches); + + return this; + } + + private function updateTimestamp(touches:Vector.):void + { + _timestamp = -1.0; + var numTouches:int = touches ? touches.length : 0; + + for (var i:int=0; i _timestamp) + _timestamp = touches[i].timestamp; + } + + /** Returns a list of touches that originated over a certain target. If you pass an + * out-vector, the touches will be added to this vector instead of creating + * a new object. */ + public function getTouches(target:DisplayObject, phase:String=null, + out:Vector.=null):Vector. + { + if (out == null) out = new []; + var allTouches:Vector. = data as Vector.; + var numTouches:int = allTouches.length; + + for (var i:int=0; i 0) + { + var touch:Touch = null; + + if (id < 0) touch = sTouches[0]; + else + { + for (var i:int=0; i=0; --i) + { + if (sTouches[i].phase != TouchPhase.ENDED) + { + result = true; + break; + } + } + + sTouches.length = 0; + return result; + } + + // custom dispatching + + /** @private + * Dispatches the event along a custom bubble chain. During the lifetime of the event, + * each object is visited only once. */ + internal function dispatch(chain:Vector.):void + { + if (chain && chain.length) + { + var chainLength:int = bubbles ? chain.length : 1; + var previousTarget:EventDispatcher = target; + setTarget(chain[0] as EventDispatcher); + + for (var i:int=0; i { return (data as Vector.).concat(); } + + /** Indicates if the shift key was pressed when the event occurred. */ + public function get shiftKey():Boolean { return _shiftKey; } + + /** Indicates if the ctrl key was pressed when the event occurred. (Mac OS: Cmd or Ctrl) */ + public function get ctrlKey():Boolean { return _ctrlKey; } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/events/TouchMarker.as b/mobile_version/src/starling/events/TouchMarker.as new file mode 100644 index 00000000..df3ff940 --- /dev/null +++ b/mobile_version/src/starling/events/TouchMarker.as @@ -0,0 +1,104 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.events +{ + import flash.display.BitmapData; + import flash.display.Shape; + import flash.geom.Point; + + import starling.core.Starling; + import starling.display.Image; + import starling.display.Sprite; + import starling.textures.Texture; + + /** The TouchMarker is used internally to mark touches created through "simulateMultitouch". */ + internal class TouchMarker extends Sprite + { + private var _center:Point; + private var _texture:Texture; + + public function TouchMarker() + { + _center = new Point(); + _texture = createTexture(); + + for (var i:int=0; i<2; ++i) + { + var marker:Image = new Image(_texture); + marker.pivotX = _texture.width / 2; + marker.pivotY = _texture.height / 2; + marker.touchable = false; + addChild(marker); + } + } + + public override function dispose():void + { + _texture.dispose(); + super.dispose(); + } + + public function moveMarker(x:Number, y:Number, withCenter:Boolean=false):void + { + if (withCenter) + { + _center.x += x - realMarker.x; + _center.y += y - realMarker.y; + } + + realMarker.x = x; + realMarker.y = y; + mockMarker.x = 2*_center.x - x; + mockMarker.y = 2*_center.y - y; + } + + public function moveCenter(x:Number, y:Number):void + { + _center.x = x; + _center.y = y; + moveMarker(realX, realY); // reset mock position + } + + private function createTexture():Texture + { + var scale:Number = Starling.contentScaleFactor; + var radius:Number = 12 * scale; + var width:int = 32 * scale; + var height:int = 32 * scale; + var thickness:Number = 1.5 * scale; + var shape:Shape = new Shape(); + + // draw dark outline + shape.graphics.lineStyle(thickness, 0x0, 0.3); + shape.graphics.drawCircle(width/2, height/2, radius + thickness); + + // draw white inner circle + shape.graphics.beginFill(0xffffff, 0.4); + shape.graphics.lineStyle(thickness, 0xffffff); + shape.graphics.drawCircle(width/2, height/2, radius); + shape.graphics.endFill(); + + var bmpData:BitmapData = new BitmapData(width, height, true, 0x0); + bmpData.draw(shape); + + return Texture.fromBitmapData(bmpData, false, false, scale); + } + + private function get realMarker():Image { return getChildAt(0) as Image; } + private function get mockMarker():Image { return getChildAt(1) as Image; } + + public function get realX():Number { return realMarker.x; } + public function get realY():Number { return realMarker.y; } + + public function get mockX():Number { return mockMarker.x; } + public function get mockY():Number { return mockMarker.y; } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/events/TouchPhase.as b/mobile_version/src/starling/events/TouchPhase.as new file mode 100644 index 00000000..2749a8ae --- /dev/null +++ b/mobile_version/src/starling/events/TouchPhase.as @@ -0,0 +1,53 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.events +{ + import starling.errors.AbstractClassError; + + /** A class that provides constant values for the phases of a touch object. + * + *

A touch moves through at least the following phases in its life:

+ * + * BEGAN -> MOVED -> ENDED + * + *

Furthermore, a touch can enter a STATIONARY phase. That phase does not + * trigger a touch event itself, and it can only occur in multitouch environments. Picture a + * situation where one finger is moving and the other is stationary. A touch event will + * be dispatched only to the object under the moving finger. In the list of touches + * of that event, you will find the second touch in the stationary phase.

+ * + *

Finally, there's the HOVER phase, which is exclusive to mouse input. It is + * the equivalent of a MouseOver event in Flash when the mouse button is + * not pressed.

+ */ + public final class TouchPhase + { + /** @private */ + public function TouchPhase() { throw new AbstractClassError(); } + + /** Only available for mouse input: the cursor hovers over an object without a + * pressed button. */ + public static const HOVER:String = "hover"; + + /** The finger touched the screen just now, or the mouse button was pressed. */ + public static const BEGAN:String = "began"; + + /** The finger moves around on the screen, or the mouse is moved while the button is + * pressed. */ + public static const MOVED:String = "moved"; + + /** The finger or mouse (with pressed button) has not moved since the last frame. */ + public static const STATIONARY:String = "stationary"; + + /** The finger was lifted from the screen or from the mouse button. */ + public static const ENDED:String = "ended"; + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/events/TouchProcessor.as b/mobile_version/src/starling/events/TouchProcessor.as new file mode 100644 index 00000000..76ff3751 --- /dev/null +++ b/mobile_version/src/starling/events/TouchProcessor.as @@ -0,0 +1,481 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.events +{ + import flash.geom.Point; + import flash.utils.getDefinitionByName; + + import starling.core.Starling; + import starling.display.DisplayObject; + import starling.display.Stage; + + /** The TouchProcessor is used to convert mouse and touch events of the conventional + * Flash stage to Starling's TouchEvents. + * + *

The Starling instance listens to mouse and touch events on the native stage. The + * attributes of those events are enqueued (right as they are happening) in the + * TouchProcessor.

+ * + *

Once per frame, the "advanceTime" method is called. It analyzes the touch queue and + * figures out which touches are active at that moment; the properties of all touch objects + * are updated accordingly.

+ * + *

Once the list of touches has been finalized, the "processTouches" method is called + * (that might happen several times in one "advanceTime" execution; no information is + * discarded). It's responsible for dispatching the actual touch events to the Starling + * display tree.

+ * + * Subclassing TouchProcessor + * + *

You can extend the TouchProcessor if you need to have more control over touch and + * mouse input. For example, you could filter the touches by overriding the "processTouches" + * method, throwing away any touches you're not interested in and passing the rest to the + * super implementation.

+ * + *

To use your custom TouchProcessor, assign it to the "Starling.touchProcessor" + * property.

+ * + *

Note that you should not dispatch TouchEvents yourself, since they are + * much more complex to handle than conventional events (e.g. it must be made sure that an + * object receives a TouchEvent only once, even if it's manipulated with several fingers). + * Always use the base implementation of "processTouches" to let them be dispatched. That + * said: you can always dispatch your own custom events, of course.

+ */ + public class TouchProcessor + { + private var _stage:Stage; + private var _root:DisplayObject; + private var _elapsedTime:Number; + private var _lastTaps:Vector.; + private var _shiftDown:Boolean = false; + private var _ctrlDown:Boolean = false; + private var _multitapTime:Number = 0.3; + private var _multitapDistance:Number = 25; + private var _touchEvent:TouchEvent; + + private var _touchMarker:TouchMarker; + private var _simulateMultitouch:Boolean; + + /** A vector of arrays with the arguments that were passed to the "enqueue" + * method (the oldest being at the end of the vector). */ + protected var _queue:Vector.; + + /** The list of all currently active touches. */ + protected var _currentTouches:Vector.; + + /** Helper objects. */ + private static var sUpdatedTouches:Vector. = new []; + private static var sHoveringTouchData:Vector. = new []; + private static var sHelperPoint:Point = new Point(); + + /** Creates a new TouchProcessor that will dispatch events to the given stage. */ + public function TouchProcessor(stage:Stage) + { + _root = _stage = stage; + _elapsedTime = 0.0; + _currentTouches = new []; + _queue = new []; + _lastTaps = new []; + _touchEvent = new TouchEvent(TouchEvent.TOUCH); + + _stage.addEventListener(KeyboardEvent.KEY_DOWN, onKey); + _stage.addEventListener(KeyboardEvent.KEY_UP, onKey); + monitorInterruptions(true); + } + + /** Removes all event handlers on the stage and releases any acquired resources. */ + public function dispose():void + { + monitorInterruptions(false); + _stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKey); + _stage.removeEventListener(KeyboardEvent.KEY_UP, onKey); + if (_touchMarker) _touchMarker.dispose(); + } + + /** Analyzes the current touch queue and processes the list of current touches, emptying + * the queue while doing so. This method is called by Starling once per frame. */ + public function advanceTime(passedTime:Number):void + { + var i:int; + var touch:Touch; + + _elapsedTime += passedTime; + sUpdatedTouches.length = 0; + + // remove old taps + if (_lastTaps.length > 0) + { + for (i=_lastTaps.length-1; i>=0; --i) + if (_elapsedTime - _lastTaps[i].timestamp > _multitapTime) + _lastTaps.removeAt(i); + } + + while (_queue.length > 0) + { + // Set touches that were new or moving to phase 'stationary'. + for each (touch in _currentTouches) + if (touch.phase == TouchPhase.BEGAN || touch.phase == TouchPhase.MOVED) + touch.phase = TouchPhase.STATIONARY; + + // analyze new touches, but each ID only once + while (_queue.length > 0 && + !containsTouchWithID(sUpdatedTouches, _queue[_queue.length-1][0])) + { + var touchArgs:Array = _queue.pop(); + touch = createOrUpdateTouch( + touchArgs[0], touchArgs[1], touchArgs[2], touchArgs[3], + touchArgs[4], touchArgs[5], touchArgs[6]); + + sUpdatedTouches[sUpdatedTouches.length] = touch; // avoiding 'push' + } + + // process the current set of touches (i.e. dispatch touch events) + processTouches(sUpdatedTouches, _shiftDown, _ctrlDown); + + // remove ended touches + for (i=_currentTouches.length-1; i>=0; --i) + if (_currentTouches[i].phase == TouchPhase.ENDED) + _currentTouches.removeAt(i); + + sUpdatedTouches.length = 0; + } + } + + /** Dispatches TouchEvents to the display objects that are affected by the list of + * given touches. Called internally by "advanceTime". To calculate updated targets, + * the method will call "hitTest" on the "root" object. + * + * @param touches a list of all touches that have changed just now. + * @param shiftDown indicates if the shift key was down when the touches occurred. + * @param ctrlDown indicates if the ctrl or cmd key was down when the touches occurred. + */ + protected function processTouches(touches:Vector., + shiftDown:Boolean, ctrlDown:Boolean):void + { + var touch:Touch; + sHoveringTouchData.length = 0; + + // the same touch event will be dispatched to all targets; + // the 'dispatch' method makes sure each bubble target is visited only once. + _touchEvent.resetTo(TouchEvent.TOUCH, _currentTouches, shiftDown, ctrlDown); + + // hit test our updated touches + for each (touch in touches) + { + // hovering touches need special handling (see below) + if (touch.phase == TouchPhase.HOVER && touch.target) + sHoveringTouchData[sHoveringTouchData.length] = { + touch: touch, + target: touch.target, + bubbleChain: touch.bubbleChain + }; // avoiding 'push' + + if (touch.phase == TouchPhase.HOVER || touch.phase == TouchPhase.BEGAN) + { + sHelperPoint.setTo(touch.globalX, touch.globalY); + touch.target = _root.hitTest(sHelperPoint); + } + } + + // if the target of a hovering touch changed, we dispatch the event to the previous + // target to notify it that it's no longer being hovered over. + for each (var touchData:Object in sHoveringTouchData) + if (touchData.touch.target != touchData.target) + _touchEvent.dispatch(touchData.bubbleChain); + + // dispatch events for the rest of our updated touches + for each (touch in touches) + touch.dispatchEvent(_touchEvent); + + // clean up any references + _touchEvent.resetTo(TouchEvent.TOUCH); + } + + /** Enqueues a new touch our mouse event with the given properties. */ + public function enqueue(touchID:int, phase:String, globalX:Number, globalY:Number, + pressure:Number=1.0, width:Number=1.0, height:Number=1.0):void + { + _queue.unshift(arguments); + + // multitouch simulation (only with mouse) + if (_ctrlDown && _touchMarker && touchID == 0) + { + _touchMarker.moveMarker(globalX, globalY, _shiftDown); + _queue.unshift([1, phase, _touchMarker.mockX, _touchMarker.mockY]); + } + } + + /** Enqueues an artificial touch that represents the mouse leaving the stage. + * + *

On OS X, we get mouse events from outside the stage; on Windows, we do not. + * This method enqueues an artificial hover point that is just outside the stage. + * That way, objects listening for HOVERs over them will get notified everywhere.

+ */ + public function enqueueMouseLeftStage():void + { + var mouse:Touch = getCurrentTouch(0); + if (mouse == null || mouse.phase != TouchPhase.HOVER) return; + + var offset:int = 1; + var exitX:Number = mouse.globalX; + var exitY:Number = mouse.globalY; + var distLeft:Number = mouse.globalX; + var distRight:Number = _stage.stageWidth - distLeft; + var distTop:Number = mouse.globalY; + var distBottom:Number = _stage.stageHeight - distTop; + var minDist:Number = Math.min(distLeft, distRight, distTop, distBottom); + + // the new hover point should be just outside the stage, near the point where + // the mouse point was last to be seen. + + if (minDist == distLeft) exitX = -offset; + else if (minDist == distRight) exitX = _stage.stageWidth + offset; + else if (minDist == distTop) exitY = -offset; + else exitY = _stage.stageHeight + offset; + + enqueue(0, TouchPhase.HOVER, exitX, exitY); + } + + /** Force-end all current touches. Changes the phase of all touches to 'ENDED' and + * immediately dispatches a new TouchEvent (if touches are present). Called automatically + * when the app receives a 'DEACTIVATE' event. */ + public function cancelTouches():void + { + if (_currentTouches.length > 0) + { + // abort touches + for each (var touch:Touch in _currentTouches) + { + if (touch.phase == TouchPhase.BEGAN || touch.phase == TouchPhase.MOVED || + touch.phase == TouchPhase.STATIONARY) + { + touch.phase = TouchPhase.ENDED; + touch.cancelled = true; + } + } + + // dispatch events + processTouches(_currentTouches, _shiftDown, _ctrlDown); + } + + // purge touches + _currentTouches.length = 0; + _queue.length = 0; + } + + private function createOrUpdateTouch(touchID:int, phase:String, + globalX:Number, globalY:Number, + pressure:Number=1.0, + width:Number=1.0, height:Number=1.0):Touch + { + var touch:Touch = getCurrentTouch(touchID); + + if (touch == null) + { + touch = new Touch(touchID); + addCurrentTouch(touch); + } + + touch.globalX = globalX; + touch.globalY = globalY; + touch.phase = phase; + touch.timestamp = _elapsedTime; + touch.pressure = pressure; + touch.width = width; + touch.height = height; + + if (phase == TouchPhase.BEGAN) + updateTapCount(touch); + + return touch; + } + + private function updateTapCount(touch:Touch):void + { + var nearbyTap:Touch = null; + var minSqDist:Number = _multitapDistance * _multitapDistance; + + for each (var tap:Touch in _lastTaps) + { + var sqDist:Number = Math.pow(tap.globalX - touch.globalX, 2) + + Math.pow(tap.globalY - touch.globalY, 2); + if (sqDist <= minSqDist) + { + nearbyTap = tap; + break; + } + } + + if (nearbyTap) + { + touch.tapCount = nearbyTap.tapCount + 1; + _lastTaps.removeAt(_lastTaps.indexOf(nearbyTap)); + } + else + { + touch.tapCount = 1; + } + + _lastTaps[_lastTaps.length] = touch.clone(); // avoiding 'push' + } + + private function addCurrentTouch(touch:Touch):void + { + for (var i:int=_currentTouches.length-1; i>=0; --i) + if (_currentTouches[i].id == touch.id) + _currentTouches.removeAt(i); + + _currentTouches[_currentTouches.length] = touch; // avoiding 'push' + } + + private function getCurrentTouch(touchID:int):Touch + { + for each (var touch:Touch in _currentTouches) + if (touch.id == touchID) return touch; + + return null; + } + + private function containsTouchWithID(touches:Vector., touchID:int):Boolean + { + for each (var touch:Touch in touches) + if (touch.id == touchID) return true; + + return false; + } + + /** Indicates if multitouch simulation should be activated. When the user presses + * ctrl/cmd (and optionally shift), he'll see a second touch cursor that mimics the first. + * That's an easy way to develop and test multitouch when there's only a mouse available. + */ + public function get simulateMultitouch():Boolean { return _simulateMultitouch; } + public function set simulateMultitouch(value:Boolean):void + { + if (simulateMultitouch == value) return; // no change + + _simulateMultitouch = value; + var target:Starling = Starling.current; + + if (value && _touchMarker == null) + { + if (Starling.current.contextValid) + createTouchMarker(); + else + target.addEventListener(Event.CONTEXT3D_CREATE, createTouchMarker); + } + else if (!value && _touchMarker) + { + _touchMarker.removeFromParent(true); + _touchMarker = null; + } + + function createTouchMarker():void + { + target.removeEventListener(Event.CONTEXT3D_CREATE, createTouchMarker); + + if (_touchMarker == null) + { + _touchMarker = new TouchMarker(); + _touchMarker.visible = false; + _stage.addChild(_touchMarker); + } + } + } + + /** The time period (in seconds) in which two touches must occur to be recognized as + * a multitap gesture. */ + public function get multitapTime():Number { return _multitapTime; } + public function set multitapTime(value:Number):void { _multitapTime = value; } + + /** The distance (in points) describing how close two touches must be to each other to + * be recognized as a multitap gesture. */ + public function get multitapDistance():Number { return _multitapDistance; } + public function set multitapDistance(value:Number):void { _multitapDistance = value; } + + /** The base object that will be used for hit testing. Per default, this reference points + * to the stage; however, you can limit touch processing to certain parts of your game + * by assigning a different object. */ + public function get root():DisplayObject { return _root; } + public function set root(value:DisplayObject):void { _root = value; } + + /** The stage object to which the touch events are (per default) dispatched. */ + public function get stage():Stage { return _stage; } + + /** Returns the number of fingers / touch points that are currently on the stage. */ + public function get numCurrentTouches():int { return _currentTouches.length; } + + // keyboard handling + + private function onKey(event:KeyboardEvent):void + { + if (event.keyCode == 17 || event.keyCode == 15) // ctrl or cmd key + { + var wasCtrlDown:Boolean = _ctrlDown; + _ctrlDown = event.type == KeyboardEvent.KEY_DOWN; + + if (_touchMarker && wasCtrlDown != _ctrlDown) + { + _touchMarker.visible = _ctrlDown; + _touchMarker.moveCenter(_stage.stageWidth/2, _stage.stageHeight/2); + + var mouseTouch:Touch = getCurrentTouch(0); + var mockedTouch:Touch = getCurrentTouch(1); + + if (mouseTouch) + _touchMarker.moveMarker(mouseTouch.globalX, mouseTouch.globalY); + + if (wasCtrlDown && mockedTouch && mockedTouch.phase != TouchPhase.ENDED) + { + // end active touch ... + _queue.unshift([1, TouchPhase.ENDED, mockedTouch.globalX, mockedTouch.globalY]); + } + else if (_ctrlDown && mouseTouch) + { + // ... or start new one + if (mouseTouch.phase == TouchPhase.HOVER || mouseTouch.phase == TouchPhase.ENDED) + _queue.unshift([1, TouchPhase.HOVER, _touchMarker.mockX, _touchMarker.mockY]); + else + _queue.unshift([1, TouchPhase.BEGAN, _touchMarker.mockX, _touchMarker.mockY]); + } + } + } + else if (event.keyCode == 16) // shift key + { + _shiftDown = event.type == KeyboardEvent.KEY_DOWN; + } + } + + // interruption handling + + private function monitorInterruptions(enable:Boolean):void + { + // if the application moves into the background or is interrupted (e.g. through + // an incoming phone call), we need to abort all touches. + + try + { + var nativeAppClass:Object = getDefinitionByName("flash.desktop::NativeApplication"); + var nativeApp:Object = nativeAppClass["nativeApplication"]; + + if (enable) + nativeApp.addEventListener("deactivate", onInterruption, false, 0, true); + else + nativeApp.removeEventListener("deactivate", onInterruption); + } + catch (e:Error) {} // we're not running in AIR + } + + private function onInterruption(event:Object):void + { + cancelTouches(); + } + } +} diff --git a/mobile_version/src/starling/filters/BlurFilter.as b/mobile_version/src/starling/filters/BlurFilter.as new file mode 100644 index 00000000..7900468a --- /dev/null +++ b/mobile_version/src/starling/filters/BlurFilter.as @@ -0,0 +1,305 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.filters +{ + import starling.rendering.FilterEffect; + import starling.rendering.Painter; + import starling.textures.Texture; + + /** The BlurFilter applies a Gaussian blur to an object. The strength of the blur can be + * set for x- and y-axis separately. */ + public class BlurFilter extends FragmentFilter + { + private var _blurX:Number; + private var _blurY:Number; + + /** Create a new BlurFilter. For each blur direction, the number of required passes is + * Math.ceil(blur). + * + *
  • blur = 0.5: 1 pass
  • + *
  • blur = 1.0: 1 pass
  • + *
  • blur = 1.5: 2 passes
  • + *
  • blur = 2.0: 2 passes
  • + *
  • etc.
  • + *
+ */ + public function BlurFilter(blurX:Number=1.0, blurY:Number=1.0, resolution:Number=1.0) + { + _blurX = blurX; + _blurY = blurY; + this.resolution = resolution; + } + + /** @private */ + override public function process(painter:Painter, helper:IFilterHelper, + input0:Texture = null, input1:Texture = null, + input2:Texture = null, input3:Texture = null):Texture + { + var effect:BlurEffect = this.effect as BlurEffect; + + if (_blurX == 0 && _blurY == 0) + { + effect.strength = 0; + return super.process(painter, helper, input0); + } + + var blurX:Number = Math.abs(_blurX); + var blurY:Number = Math.abs(_blurY); + var outTexture:Texture = input0; + var inTexture:Texture; + + effect.direction = BlurEffect.HORIZONTAL; + + while (blurX > 0) + { + effect.strength = Math.min(1.0, blurX); + + blurX -= effect.strength; + inTexture = outTexture; + outTexture = super.process(painter, helper, inTexture); + + if (inTexture != input0) helper.putTexture(inTexture); + } + + effect.direction = BlurEffect.VERTICAL; + + while (blurY > 0) + { + effect.strength = Math.min(1.0, blurY); + + blurY -= effect.strength; + inTexture = outTexture; + outTexture = super.process(painter, helper, inTexture); + + if (inTexture != input0) helper.putTexture(inTexture); + } + + return outTexture; + } + + /** @private */ + override protected function createEffect():FilterEffect + { + return new BlurEffect(); + } + + /** @private */ + override public function set resolution(value:Number):void + { + super.resolution = value; + updatePadding(); + } + + /** @private */ + override public function get numPasses():int + { + return (Math.ceil(_blurX) + Math.ceil(_blurY)) || 1; + } + + private function updatePadding():void + { + var paddingX:Number = (_blurX ? Math.ceil(Math.abs(_blurX)) + 3 : 1) / resolution; + var paddingY:Number = (_blurY ? Math.ceil(Math.abs(_blurY)) + 3 : 1) / resolution; + + padding.setTo(paddingX, paddingX, paddingY, paddingY); + } + + /** The blur factor in x-direction. + * The number of required passes will be Math.ceil(value). */ + public function get blurX():Number { return _blurX; } + public function set blurX(value:Number):void + { + _blurX = value; + updatePadding(); + } + + /** The blur factor in y-direction. + * The number of required passes will be Math.ceil(value). */ + public function get blurY():Number { return _blurY; } + public function set blurY(value:Number):void + { + _blurY = value; + updatePadding(); + } + } +} + +import flash.display3D.Context3D; +import flash.display3D.Context3DProgramType; + +import starling.rendering.FilterEffect; +import starling.rendering.Program; +import starling.utils.MathUtil; + +class BlurEffect extends FilterEffect +{ + public static const HORIZONTAL:String = "horizontal"; + public static const VERTICAL:String = "vertical"; + + private static const MAX_SIGMA:Number = 2.0; + + private var _strength:Number; + private var _direction:String; + + private var _offsets:Vector. = new [0, 0, 0, 0]; + private var _weights:Vector. = new [0, 0, 0, 0]; + + // helpers + private var sTmpWeights:Vector. = new Vector.(5, true); + + /** Creates a new BlurEffect. + * + * @param direction horizontal or vertical + * @param strength range 0-1 + */ + public function BlurEffect(direction:String="horizontal", strength:Number=1):void + { + this.strength = strength; + this.direction = direction; + } + + override protected function createProgram():Program + { + if (_strength == 0) return super.createProgram(); + + var vertexShader:String = [ + "m44 op, va0, vc0 ", // 4x4 matrix transform to output space + "mov v0, va1 ", // pos: 0 | + "sub v1, va1, vc4.zwxx", // pos: -2 | + "sub v2, va1, vc4.xyxx", // pos: -1 | --> kernel positions + "add v3, va1, vc4.xyxx", // pos: +1 | (only 1st two values are relevant) + "add v4, va1, vc4.zwxx" // pos: +2 | + ].join("\n"); + + // v0-v4 - kernel position + // fs0 - input texture + // fc0 - weight data + // ft0-4 - pixel color from texture + // ft5 - output color + + var fragmentShader:String = [ + tex("ft0", "v0", 0, texture), // read center pixel + "mul ft5, ft0, fc0.xxxx ", // multiply with center weight + + tex("ft1", "v1", 0, texture), // read pixel -2 + "mul ft1, ft1, fc0.zzzz ", // multiply with weight + "add ft5, ft5, ft1 ", // add to output color + + tex("ft2", "v2", 0, texture), // read pixel -1 + "mul ft2, ft2, fc0.yyyy ", // multiply with weight + "add ft5, ft5, ft2 ", // add to output color + + tex("ft3", "v3", 0, texture), // read pixel +1 + "mul ft3, ft3, fc0.yyyy ", // multiply with weight + "add ft5, ft5, ft3 ", // add to output color + + tex("ft4", "v4", 0, texture), // read pixel +2 + "mul ft4, ft4, fc0.zzzz ", // multiply with weight + "add oc, ft5, ft4 " // add to output color + ].join("\n"); + + return Program.fromSource(vertexShader, fragmentShader); + } + + override protected function beforeDraw(context:Context3D):void + { + super.beforeDraw(context); + + if (_strength) + { + updateParameters(); + + context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 4, _offsets); + context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, _weights); + } + } + + override protected function get programVariantName():uint + { + return super.programVariantName | (_strength ? 1 << 4 : 0); + } + + private function updateParameters():void + { + // algorithm described here: + // http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/ + // + // To run in constrained mode, we can only make 5 texture look-ups in the fragment + // shader. By making use of linear texture sampling, we can produce similar output + // to what would be 9 look-ups. + + var sigma:Number; + var pixelSize:Number; + + if (_direction == HORIZONTAL) + { + sigma = _strength * MAX_SIGMA; + pixelSize = 1.0 / texture.root.width; + } + else + { + sigma = _strength * MAX_SIGMA; + pixelSize = 1.0 / texture.root.height; + } + + const twoSigmaSq:Number = 2 * sigma * sigma; + const multiplier:Number = 1.0 / Math.sqrt(twoSigmaSq * Math.PI); + + // get weights on the exact pixels (sTmpWeights) and calculate sums (_weights) + + for (var i:int=0; i<5; ++i) + sTmpWeights[i] = multiplier * Math.exp(-i*i / twoSigmaSq); + + _weights[0] = sTmpWeights[0]; + _weights[1] = sTmpWeights[1] + sTmpWeights[2]; + _weights[2] = sTmpWeights[3] + sTmpWeights[4]; + + // normalize weights so that sum equals "1.0" + + var weightSum:Number = _weights[0] + 2*_weights[1] + 2*_weights[2]; + var invWeightSum:Number = 1.0 / weightSum; + + _weights[0] *= invWeightSum; + _weights[1] *= invWeightSum; + _weights[2] *= invWeightSum; + + // calculate intermediate offsets + + var offset1:Number = ( pixelSize * sTmpWeights[1] + 2*pixelSize * sTmpWeights[2]) / _weights[1]; + var offset2:Number = (3*pixelSize * sTmpWeights[3] + 4*pixelSize * sTmpWeights[4]) / _weights[2]; + + // depending on pass, we move in x- or y-direction + + if (_direction == HORIZONTAL) + { + _offsets[0] = offset1; + _offsets[1] = 0; + _offsets[2] = offset2; + _offsets[3] = 0; + } + else + { + _offsets[0] = 0; + _offsets[1] = offset1; + _offsets[2] = 0; + _offsets[3] = offset2; + } + } + + public function get direction():String { return _direction; } + public function set direction(value:String):void { _direction = value; } + + public function get strength():Number { return _strength; } + public function set strength(value:Number):void + { + _strength = MathUtil.clamp(value, 0, 1); + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/filters/ColorMatrixFilter.as b/mobile_version/src/starling/filters/ColorMatrixFilter.as new file mode 100644 index 00000000..a9fc550c --- /dev/null +++ b/mobile_version/src/starling/filters/ColorMatrixFilter.as @@ -0,0 +1,318 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.filters +{ + import starling.rendering.FilterEffect; + import starling.utils.Color; + + /** The ColorMatrixFilter class lets you apply a 4x5 matrix transformation to the color + * and alpha values of every pixel in the input image to produce a result with a new set + * of color and alpha values. This allows saturation changes, hue rotation, + * luminance to alpha, and various other effects. + * + *

The class contains several convenience methods for frequently used color + * adjustments. All those methods change the current matrix, which means you can easily + * combine them in one filter:

+ * + * + * // create an inverted filter with 50% saturation and 180° hue rotation + * var filter:ColorMatrixFilter = new ColorMatrixFilter(); + * filter.invert(); + * filter.adjustSaturation(-0.5); + * filter.adjustHue(1.0); + * + *

If you want to gradually animate one of the predefined color adjustments, either reset + * the matrix after each step, or use an identical adjustment value for each step; the + * changes will add up.

+ */ + public class ColorMatrixFilter extends FragmentFilter + { + // Most of the color transformation math was taken from the excellent ColorMatrix class by + // Mario Klingemann: http://www.quasimondo.com/archives/000565.php -- THANKS!!! + + private static const LUMA_R:Number = 0.299; + private static const LUMA_G:Number = 0.587; + private static const LUMA_B:Number = 0.114; + + // helpers + private static var sMatrix:Vector. = new []; + + /** Creates a new ColorMatrixFilter instance with the specified matrix. + * @param matrix a vector of 20 items arranged as a 4x5 matrix. + */ + public function ColorMatrixFilter(matrix:Vector.=null) + { + if (matrix) colorEffect.matrix = matrix; + } + + /** @private */ + override protected function createEffect():FilterEffect + { + return new ColorMatrixEffect(); + } + + // color manipulation + + /** Inverts the colors of the filtered object. */ + public function invert():void + { + concatValues(-1, 0, 0, 0, 255, + 0, -1, 0, 0, 255, + 0, 0, -1, 0, 255, + 0, 0, 0, 1, 0); + } + + /** Changes the saturation. Typical values are in the range (-1, 1). + * Values above zero will raise, values below zero will reduce the saturation. + * '-1' will produce a grayscale image. */ + public function adjustSaturation(sat:Number):void + { + sat += 1; + + var invSat:Number = 1 - sat; + var invLumR:Number = invSat * LUMA_R; + var invLumG:Number = invSat * LUMA_G; + var invLumB:Number = invSat * LUMA_B; + + concatValues((invLumR + sat), invLumG, invLumB, 0, 0, + invLumR, (invLumG + sat), invLumB, 0, 0, + invLumR, invLumG, (invLumB + sat), 0, 0, + 0, 0, 0, 1, 0); + } + + /** Changes the contrast. Typical values are in the range (-1, 1). + * Values above zero will raise, values below zero will reduce the contrast. */ + public function adjustContrast(value:Number):void + { + var s:Number = value + 1; + var o:Number = 128 * (1 - s); + + concatValues(s, 0, 0, 0, o, + 0, s, 0, 0, o, + 0, 0, s, 0, o, + 0, 0, 0, 1, 0); + } + + /** Changes the brightness. Typical values are in the range (-1, 1). + * Values above zero will make the image brighter, values below zero will make it darker.*/ + public function adjustBrightness(value:Number):void + { + value *= 255; + + concatValues(1, 0, 0, 0, value, + 0, 1, 0, 0, value, + 0, 0, 1, 0, value, + 0, 0, 0, 1, 0); + } + + /** Changes the hue of the image. Typical values are in the range (-1, 1). */ + public function adjustHue(value:Number):void + { + value *= Math.PI; + + var cos:Number = Math.cos(value); + var sin:Number = Math.sin(value); + + concatValues( + ((LUMA_R + (cos * (1 - LUMA_R))) + (sin * -(LUMA_R))), ((LUMA_G + (cos * -(LUMA_G))) + (sin * -(LUMA_G))), ((LUMA_B + (cos * -(LUMA_B))) + (sin * (1 - LUMA_B))), 0, 0, + ((LUMA_R + (cos * -(LUMA_R))) + (sin * 0.143)), ((LUMA_G + (cos * (1 - LUMA_G))) + (sin * 0.14)), ((LUMA_B + (cos * -(LUMA_B))) + (sin * -0.283)), 0, 0, + ((LUMA_R + (cos * -(LUMA_R))) + (sin * -((1 - LUMA_R)))), ((LUMA_G + (cos * -(LUMA_G))) + (sin * LUMA_G)), ((LUMA_B + (cos * (1 - LUMA_B))) + (sin * LUMA_B)), 0, 0, + 0, 0, 0, 1, 0); + } + + /** Tints the image in a certain color, analog to what can be done in Adobe Animate. + * + * @param color the RGB color with which the image should be tinted. + * @param amount the intensity with which tinting should be applied. Range (0, 1). + */ + public function tint(color:uint, amount:Number=1.0):void + { + var r:Number = Color.getRed(color) / 255.0; + var g:Number = Color.getGreen(color) / 255.0; + var b:Number = Color.getBlue(color) / 255.0; + var q:Number = 1 - amount; + + var rA:Number = amount * r; + var gA:Number = amount * g; + var bA:Number = amount * b; + + concatValues( + q + rA * LUMA_R, rA * LUMA_G, rA * LUMA_B, 0, 0, + gA * LUMA_R, q + gA * LUMA_G, gA * LUMA_B, 0, 0, + bA * LUMA_R, bA * LUMA_G, q + bA * LUMA_B, 0, 0, + 0, 0, 0, 1, 0); + } + + // matrix manipulation + + /** Changes the filter matrix back to the identity matrix. */ + public function reset():void + { + matrix = null; + } + + /** Concatenates the current matrix with another one. */ + public function concat(matrix:Vector.):void + { + colorEffect.concat(matrix); + setRequiresRedraw(); + } + + /** Concatenates the current matrix with another one, passing its contents directly. */ + public function concatValues(m0:Number, m1:Number, m2:Number, m3:Number, m4:Number, + m5:Number, m6:Number, m7:Number, m8:Number, m9:Number, + m10:Number, m11:Number, m12:Number, m13:Number, m14:Number, + m15:Number, m16:Number, m17:Number, m18:Number, m19:Number):void + { + sMatrix.length = 0; + sMatrix.push(m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, + m10, m11, m12, m13, m14, m15, m16, m17, m18, m19); + + concat(sMatrix); + } + + /** A vector of 20 items arranged as a 4x5 matrix. */ + public function get matrix():Vector. { return colorEffect.matrix; } + public function set matrix(value:Vector.):void + { + colorEffect.matrix = value; + setRequiresRedraw(); + } + + private function get colorEffect():ColorMatrixEffect + { + return this.effect as ColorMatrixEffect; + } + } +} + +import flash.display3D.Context3D; +import flash.display3D.Context3DProgramType; + +import starling.rendering.FilterEffect; +import starling.rendering.Program; + +class ColorMatrixEffect extends FilterEffect +{ + private var _userMatrix:Vector.; // offset in range 0-255 + private var _shaderMatrix:Vector.; // offset in range 0-1, changed order + + private static const MIN_COLOR:Vector. = new [0, 0, 0, 0.0001]; + private static const IDENTITY:Array = [1,0,0,0,0, 0,1,0,0,0, 0,0,1,0,0, 0,0,0,1,0]; + + // helpers + private static var sMatrix:Vector. = new Vector.(20, true); + + public function ColorMatrixEffect():void + { + _userMatrix = new []; + _shaderMatrix = new []; + + this.matrix = null; + } + + override protected function createProgram():Program + { + var vertexShader:String = FilterEffect.STD_VERTEX_SHADER; + var fragmentShader:String = [ + tex("ft0", "v0", 0, texture), // read texture color + "max ft0, ft0, fc5 ", // avoid division through zero in next step + "div ft0.xyz, ft0.xyz, ft0.www ", // restore original (non-PMA) RGB values + "m44 ft0, ft0, fc0 ", // multiply color with 4x4 matrix + "add ft0, ft0, fc4 ", // add offset + "mul ft0.xyz, ft0.xyz, ft0.www ", // multiply with alpha again (PMA) + "mov oc, ft0 " // copy to output + ].join("\n"); + + return Program.fromSource(vertexShader, fragmentShader); + } + + override protected function beforeDraw(context:Context3D):void + { + super.beforeDraw(context); + + context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, _shaderMatrix); + context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 5, MIN_COLOR); + } + + // matrix manipulation + + public function reset():void + { + matrix = null; + } + + /** Concatenates the current matrix with another one. */ + public function concat(matrix:Vector.):void + { + var i:int = 0; + + for (var y:int=0; y<4; ++y) + { + for (var x:int=0; x<5; ++x) + { + sMatrix[i+x] = matrix[i ] * _userMatrix[x ] + + matrix[i + 1] * _userMatrix[x + 5] + + matrix[i + 2] * _userMatrix[x + 10] + + matrix[i + 3] * _userMatrix[x + 15] + + (x == 4 ? matrix[i + 4] : 0); + } + + i += 5; + } + + copyMatrix(sMatrix, _userMatrix); + updateShaderMatrix(); + } + + private function copyMatrix(from:Vector., to:Vector.):void + { + for (var i:int=0; i<20; ++i) + to[i] = from[i]; + } + + private function updateShaderMatrix():void + { + // the shader needs the matrix components in a different order, + // and it needs the offsets in the range 0-1. + + _shaderMatrix.length = 0; + _shaderMatrix.push( + _userMatrix[0 ], _userMatrix[ 1], _userMatrix[ 2], _userMatrix[ 3], + _userMatrix[5 ], _userMatrix[ 6], _userMatrix[ 7], _userMatrix[ 8], + _userMatrix[10], _userMatrix[11], _userMatrix[12], _userMatrix[13], + _userMatrix[15], _userMatrix[16], _userMatrix[17], _userMatrix[18], + _userMatrix[ 4] / 255.0, _userMatrix[9] / 255.0, _userMatrix[14] / 255.0, + _userMatrix[19] / 255.0 + ); + } + + // properties + + public function get matrix():Vector. { return _userMatrix; } + public function set matrix(value:Vector.):void + { + if (value && value.length != 20) + throw new ArgumentError("Invalid matrix length: must be 20"); + + if (value == null) + { + _userMatrix.length = 0; + _userMatrix.push.apply(_userMatrix, IDENTITY); + } + else + { + copyMatrix(value, _userMatrix); + } + + updateShaderMatrix(); + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/filters/CompositeFilter.as b/mobile_version/src/starling/filters/CompositeFilter.as new file mode 100644 index 00000000..8dd5d7cf --- /dev/null +++ b/mobile_version/src/starling/filters/CompositeFilter.as @@ -0,0 +1,327 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.filters +{ + import flash.geom.Point; + + import starling.rendering.FilterEffect; + import starling.rendering.Painter; + import starling.textures.Texture; + import starling.utils.MathUtil; + + /** The CompositeFilter class allows to combine several layers of textures into one texture. + * It's mainly used as a building block for more complex filters; e.g. the DropShadowFilter + * uses this class to draw the shadow (the result of a BlurFilter) behind an object. + */ + public class CompositeFilter extends FragmentFilter + { + /** Creates a new instance. */ + public function CompositeFilter() + { } + + /** Combines up to four input textures into one new texture, + * adhering to the properties of each layer. */ + override public function process(painter:Painter, helper:IFilterHelper, + input0:Texture = null, input1:Texture = null, + input2:Texture = null, input3:Texture = null):Texture + { + compositeEffect.texture = input0; + compositeEffect.getLayerAt(1).texture = input1; + compositeEffect.getLayerAt(2).texture = input2; + compositeEffect.getLayerAt(3).texture = input3; + + if (input1) input1.setupTextureCoordinates(vertexData, 0, "texCoords1"); + if (input2) input2.setupTextureCoordinates(vertexData, 0, "texCoords2"); + if (input3) input3.setupTextureCoordinates(vertexData, 0, "texCoords3"); + + return super.process(painter, helper, input0, input1, input2, input3); + } + + /** @private */ + override protected function createEffect():FilterEffect + { + return new CompositeEffect(); + } + + /** Returns the position (in points) at which a certain layer will be drawn. */ + public function getOffsetAt(layerID:int, out:Point=null):Point + { + if (out == null) out = new Point(); + + out.x = compositeEffect.getLayerAt(layerID).x; + out.y = compositeEffect.getLayerAt(layerID).y; + + return out; + } + + /** Indicates the position (in points) at which a certain layer will be drawn. */ + public function setOffsetAt(layerID:int, x:Number, y:Number):void + { + compositeEffect.getLayerAt(layerID).x = x; + compositeEffect.getLayerAt(layerID).y = y; + } + + /** Returns the RGB color with which a layer is tinted when it is being drawn. + * @default 0xffffff */ + public function getColorAt(layerID:int):uint + { + return compositeEffect.getLayerAt(layerID).color; + } + + /** Adjusts the RGB color with which a layer is tinted when it is being drawn. + * If replace is enabled, the pixels are not tinted, but instead + * the RGB channels will replace the texture's color entirely. + */ + public function setColorAt(layerID:int, color:uint, replace:Boolean=false):void + { + compositeEffect.getLayerAt(layerID).color = color; + compositeEffect.getLayerAt(layerID).replaceColor = replace; + } + + /** Indicates the alpha value with which the layer is drawn. + * @default 1.0 */ + public function getAlphaAt(layerID:int):Number + { + return compositeEffect.getLayerAt(layerID).alpha; + } + + /** Adjusts the alpha value with which the layer is drawn. */ + public function setAlphaAt(layerID:int, alpha:Number):void + { + compositeEffect.getLayerAt(layerID).alpha = alpha; + } + + private function get compositeEffect():CompositeEffect + { + return this.effect as CompositeEffect; + } + } +} + +import flash.display3D.Context3D; +import flash.display3D.Context3DProgramType; + +import starling.rendering.FilterEffect; +import starling.rendering.Program; +import starling.rendering.VertexDataFormat; +import starling.textures.Texture; +import starling.utils.Color; +import starling.utils.RenderUtil; +import starling.utils.StringUtil; + +class CompositeEffect extends FilterEffect +{ + public static const VERTEX_FORMAT:VertexDataFormat = + FilterEffect.VERTEX_FORMAT.extend( + "texCoords1:float2, texCoords2:float2, texCoords3:float2"); + + private var _layers:Vector.; + + private static var sLayers:Array = []; + private static var sOffset:Vector. = new [0, 0, 0, 0]; + private static var sColor:Vector. = new [0, 0, 0, 0]; + + public function CompositeEffect(numLayers:int=4) + { + if (numLayers < 1 || numLayers > 4) + throw new ArgumentError("number of layers must be between 1 and 4"); + + _layers = new Vector.(numLayers, true); + + for (var i:int=0; i texture coords + vertexShader.push( + StringUtil.format("add v{0}, va{1}, vc{2}", i, i + 1, i + 4) // add offset + ); + + var fragmentShader:Array = [ + "seq ft5, v0, v0" // ft5 -> 1, 1, 1, 1 + ]; + + for (i=0; i texture i color + ); + + if (layer.replaceColor) + fragmentShader.push( + "mul " + fti + ".w, " + fti + ".w, " + fci + ".w", + "sat " + fti + ".w, " + fti + ".w ", // make sure alpha <= 1.0 + "mul " + fti + ".xyz, " + fci + ".xyz, " + fti + ".www" + ); + else + fragmentShader.push( + "mul " + fti + ", " + fti + ", " + fci // fti *= color + ); + + if (i != 0) + { + // "normal" blending: src × ONE + dst × ONE_MINUS_SOURCE_ALPHA + fragmentShader.push( + "sub ft4, ft5, " + fti + ".wwww", // ft4 => 1 - src.alpha + "mul ft0, ft0, ft4", // ft0 => dst * (1 - src.alpha) + "add ft0, ft0, " + fti // ft0 => src + (dst * 1 - src.alpha) + ); + } + } + + fragmentShader.push("mov oc, ft0"); // done! :) + + return Program.fromSource(vertexShader.join("\n"), fragmentShader.join("\n")); + } + else + { + return super.createProgram(); + } + } + + override protected function get programVariantName():uint + { + var bits:uint; + var totalBits:uint = 0; + var layer:CompositeLayer; + var layers:Array = getUsedLayers(sLayers); + var numLayers:int = layers.length; + + for (var i:int=0; iThe filter uses the following formula:

+ * dstPixel[x, y] = srcPixel[x + ((componentX(x, y) - 128) * scaleX) / 256, + * y + ((componentY(x, y) - 128) * scaleY) / 256] + * + * + *

Where componentX(x, y) gets the componentX property color value from the + * map texture at (x - mapPoint.x, y - mapPoint.y).

+ */ + public class DisplacementMapFilter extends FragmentFilter + { + private var _mapX:Number; + private var _mapY:Number; + + // helpers + private static var sBounds:Rectangle = new Rectangle(); + + /** Creates a new displacement map filter that uses the provided map texture. */ + public function DisplacementMapFilter(mapTexture:Texture, + componentX:uint=0, componentY:uint=0, + scaleX:Number=0.0, scaleY:Number=0.0) + { + _mapX = _mapY = 0; + + this.mapTexture = mapTexture; + this.componentX = componentX; + this.componentY = componentY; + this.scaleX = scaleX; + this.scaleY = scaleY; + } + + /** @private */ + override public function process(painter:Painter, pool:IFilterHelper, + input0:Texture = null, input1:Texture = null, + input2:Texture = null, input3:Texture = null):Texture + { + var offsetX:Number = 0.0, offsetY:Number = 0.0; + var targetBounds:Rectangle = pool.targetBounds; + var stage:Stage = pool.target.stage; + + if (stage && (targetBounds.x < 0 || targetBounds.y < 0)) + { + // 'targetBounds' is actually already intersected with the stage bounds. + // If the target is partially outside the stage at the left or top, we need + // to adjust the map coordinates accordingly. That's what 'offsetX/Y' is for. + + pool.target.getBounds(stage, sBounds); + sBounds.inflate(padding.left, padding.top); + offsetX = sBounds.x - pool.targetBounds.x; + offsetY = sBounds.y - pool.targetBounds.y; + } + + updateVertexData(input0, mapTexture, offsetX, offsetY); + return super.process(painter, pool, input0); + } + + /** @private */ + override protected function createEffect():FilterEffect + { + return new DisplacementMapEffect(); + } + + private function updateVertexData(inputTexture:Texture, mapTexture:Texture, + mapOffsetX:Number=0.0, mapOffsetY:Number=0.0):void + { + // The size of input texture and map texture may be different. We need to calculate + // the right values for the texture coordinates at the filter vertices. + + var mapX:Number = (_mapX + mapOffsetX + padding.left) / mapTexture.width; + var mapY:Number = (_mapY + mapOffsetY + padding.top) / mapTexture.height; + var maxU:Number = inputTexture.width / mapTexture.width; + var maxV:Number = inputTexture.height / mapTexture.height; + + mapTexture.setTexCoords(vertexData, 0, "mapTexCoords", -mapX, -mapY); + mapTexture.setTexCoords(vertexData, 1, "mapTexCoords", -mapX + maxU, -mapY); + mapTexture.setTexCoords(vertexData, 2, "mapTexCoords", -mapX, -mapY + maxV); + mapTexture.setTexCoords(vertexData, 3, "mapTexCoords", -mapX + maxU, -mapY + maxV); + } + + private function updatePadding():void + { + var paddingX:Number = Math.ceil(Math.abs(dispEffect.scaleX) / 2); + var paddingY:Number = Math.ceil(Math.abs(dispEffect.scaleY) / 2); + + padding.setTo(paddingX, paddingX, paddingY, paddingY); + } + + // properties + + /** Describes which color channel to use in the map image to displace the x result. + * Possible values are constants from the BitmapDataChannel class. */ + public function get componentX():uint { return dispEffect.componentX; } + public function set componentX(value:uint):void + { + if (dispEffect.componentX != value) + { + dispEffect.componentX = value; + setRequiresRedraw(); + } + } + + /** Describes which color channel to use in the map image to displace the y result. + * Possible values are constants from the BitmapDataChannel class. */ + public function get componentY():uint { return dispEffect.componentY; } + public function set componentY(value:uint):void + { + if (dispEffect.componentY != value) + { + dispEffect.componentY = value; + setRequiresRedraw(); + } + } + + /** The multiplier used to scale the x displacement result from the map calculation. */ + public function get scaleX():Number { return dispEffect.scaleX; } + public function set scaleX(value:Number):void + { + if (dispEffect.scaleX != value) + { + dispEffect.scaleX = value; + updatePadding(); + } + } + + /** The multiplier used to scale the y displacement result from the map calculation. */ + public function get scaleY():Number { return dispEffect.scaleY; } + public function set scaleY(value:Number):void + { + if (dispEffect.scaleY != value) + { + dispEffect.scaleY = value; + updatePadding(); + } + } + + /** The horizontal offset of the map texture relative to the origin. @default 0 */ + public function get mapX():Number { return _mapX; } + public function set mapX(value:Number):void { _mapX = value; setRequiresRedraw(); } + + /** The vertical offset of the map texture relative to the origin. @default 0 */ + public function get mapY():Number { return _mapY; } + public function set mapY(value:Number):void { _mapY = value; setRequiresRedraw(); } + + /** The texture that will be used to calculate displacement. */ + public function get mapTexture():Texture { return dispEffect.mapTexture; } + public function set mapTexture(value:Texture):void + { + if (dispEffect.mapTexture != value) + { + dispEffect.mapTexture = value; + setRequiresRedraw(); + } + } + + /** Indicates how the pixels of the map texture will be wrapped at the edge. */ + public function get mapRepeat():Boolean { return dispEffect.mapRepeat; } + public function set mapRepeat(value:Boolean):void + { + if (dispEffect.mapRepeat != value) + { + dispEffect.mapRepeat = value; + setRequiresRedraw(); + } + } + + private function get dispEffect():DisplacementMapEffect + { + return this.effect as DisplacementMapEffect; + } + } +} + +import flash.display.BitmapDataChannel; +import flash.display3D.Context3D; +import flash.display3D.Context3DProgramType; +import flash.geom.Matrix3D; + +import starling.core.Starling; +import starling.rendering.FilterEffect; +import starling.rendering.Program; +import starling.rendering.VertexDataFormat; +import starling.textures.Texture; +import starling.utils.RenderUtil; + +class DisplacementMapEffect extends FilterEffect +{ + public static const VERTEX_FORMAT:VertexDataFormat = + FilterEffect.VERTEX_FORMAT.extend("mapTexCoords:float2"); + + private var _mapTexture:Texture; + private var _mapRepeat:Boolean; + private var _componentX:uint; + private var _componentY:uint; + private var _scaleX:Number; + private var _scaleY:Number; + + // helper objects + private static var sOffset:Vector. = new [0.5, 0.5, 0.0, 0.0]; + private static var sMatrix:Matrix3D = new Matrix3D(); + private static var sMatrixData:Vector. = + new [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + public function DisplacementMapEffect() + { + _componentX = _componentY = 0; + _scaleX = _scaleY = 0; + } + + override protected function createProgram():Program + { + if (_mapTexture) + { + // vc0-3: mvpMatrix + // va0: vertex position + // va1: input texture coords + // va2: map texture coords + + var vertexShader:String = [ + "m44 op, va0, vc0", // 4x4 matrix transform to output space + "mov v0, va1", // pass input texture coordinates to fragment program + "mov v1, va2" // pass map texture coordinates to fragment program + ].join("\n"); + + // v0: input texCoords + // v1: map texCoords + // fc0: offset (0.5, 0.5) + // fc1-4: matrix + + var fragmentShader:String = [ + tex("ft0", "v1", 1, _mapTexture, false), // read map texture + "sub ft1, ft0, fc0", // subtract 0.5 -> range [-0.5, 0.5] + "mul ft1.xy, ft1.xy, ft0.ww", // zero displacement when alpha == 0 + "m44 ft2, ft1, fc1", // multiply matrix with displacement values + "add ft3, v0, ft2", // add displacement values to texture coords + tex("oc", "ft3", 0, texture) // read input texture at displaced coords + ].join("\n"); + + return Program.fromSource(vertexShader, fragmentShader); + } + else return super.createProgram(); + } + + override protected function beforeDraw(context:Context3D):void + { + super.beforeDraw(context); + + if (_mapTexture) + { + // already set by super class: + // + // vertex constants 0-3: mvpMatrix (3D) + // vertex attribute 0: vertex position (FLOAT_2) + // vertex attribute 1: texture coordinates (FLOAT_2) + // texture 0: input texture + + getMapMatrix(sMatrix); + + vertexFormat.setVertexBufferAt(2, vertexBuffer, "mapTexCoords"); + context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, sOffset); + context.setProgramConstantsFromMatrix(Context3DProgramType.FRAGMENT, 1, sMatrix, true); + RenderUtil.setSamplerStateAt(1, _mapTexture.mipMapping, textureSmoothing, _mapRepeat); + context.setTextureAt(1, _mapTexture.base); + } + } + + override protected function afterDraw(context:Context3D):void + { + if (_mapTexture) + { + context.setVertexBufferAt(2, null); + context.setTextureAt(1, null); + } + + super.afterDraw(context); + } + + override public function get vertexFormat():VertexDataFormat + { + return VERTEX_FORMAT; + } + + /** This matrix maps RGBA values of the map texture to UV-offsets in the input texture. */ + private function getMapMatrix(out:Matrix3D):Matrix3D + { + if (out == null) out = new Matrix3D(); + + var columnX:int, columnY:int; + var scale:Number = Starling.contentScaleFactor; + var textureWidth:Number = texture.root.nativeWidth; + var textureHeight:Number = texture.root.nativeHeight; + + for (var i:int=0; i<16; ++i) + sMatrixData[i] = 0; + + if (_componentX == BitmapDataChannel.RED) columnX = 0; + else if (_componentX == BitmapDataChannel.GREEN) columnX = 1; + else if (_componentX == BitmapDataChannel.BLUE) columnX = 2; + else columnX = 3; + + if (_componentY == BitmapDataChannel.RED) columnY = 0; + else if (_componentY == BitmapDataChannel.GREEN) columnY = 1; + else if (_componentY == BitmapDataChannel.BLUE) columnY = 2; + else columnY = 3; + + sMatrixData[int(columnX * 4 )] = _scaleX * scale / textureWidth; + sMatrixData[int(columnY * 4 + 1)] = _scaleY * scale / textureHeight; + + out.copyRawDataFrom(sMatrixData); + + return out; + } + + // properties + + public function get componentX():uint { return _componentX; } + public function set componentX(value:uint):void { _componentX = value; } + + public function get componentY():uint { return _componentY; } + public function set componentY(value:uint):void { _componentY = value; } + + public function get scaleX():Number { return _scaleX; } + public function set scaleX(value:Number):void { _scaleX = value; } + + public function get scaleY():Number { return _scaleY; } + public function set scaleY(value:Number):void { _scaleY = value; } + + public function get mapTexture():Texture { return _mapTexture; } + public function set mapTexture(value:Texture):void { _mapTexture = value; } + + public function get mapRepeat():Boolean { return _mapRepeat; } + public function set mapRepeat(value:Boolean):void { _mapRepeat = value; } +} diff --git a/mobile_version/src/starling/filters/DropShadowFilter.as b/mobile_version/src/starling/filters/DropShadowFilter.as new file mode 100644 index 00000000..ed4ad134 --- /dev/null +++ b/mobile_version/src/starling/filters/DropShadowFilter.as @@ -0,0 +1,172 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.filters +{ + import starling.rendering.Painter; + import starling.textures.Texture; + import starling.utils.Padding; + + /** The DropShadowFilter class lets you add a drop shadow to display objects. + * To create the shadow, the class internally uses the BlurFilter. + */ + public class DropShadowFilter extends FragmentFilter + { + private var _blurFilter:BlurFilter; + private var _compositeFilter:CompositeFilter; + private var _distance:Number; + private var _angle:Number; + + /** Creates a new DropShadowFilter instance with the specified parameters. + * + * @param distance the offset distance of the shadow, in points. + * @param angle the angle with which the shadow is offset, in radians. + * @param color the color of the shadow. + * @param alpha the alpha value of the shadow. Values between 0 and 1 modify the + * opacity; values > 1 will make it stronger, i.e. produce a harder edge. + * @param blur the amount of blur with which the shadow is created. Note that high + * values will cause the number of render passes to grow. + * @param resolution the resolution of the filter texture. '1' means full resolution, + * '0.5' half resolution, etc. + */ + public function DropShadowFilter(distance:Number=4.0, angle:Number=0.785, + color:uint=0x0, alpha:Number=0.5, blur:Number=1.0, + resolution:Number=0.5) + { + _compositeFilter = new CompositeFilter(); + _blurFilter = new BlurFilter(blur, blur, resolution); + _distance = distance; + _angle = angle; + + this.color = color; + this.alpha = alpha; + + updatePadding(); + } + + /** @inheritDoc */ + override public function dispose():void + { + _blurFilter.dispose(); + _compositeFilter.dispose(); + + super.dispose(); + } + + /** @private */ + override public function process(painter:Painter, helper:IFilterHelper, + input0:Texture = null, input1:Texture = null, + input2:Texture = null, input3:Texture = null):Texture + { + var shadow:Texture = _blurFilter.process(painter, helper, input0); + var result:Texture = _compositeFilter.process(painter, helper, shadow, input0); + helper.putTexture(shadow); + return result; + } + + /** @private */ + override public function get numPasses():int + { + return _blurFilter.numPasses + _compositeFilter.numPasses; + } + + private function updatePadding():void + { + var offsetX:Number = Math.cos(_angle) * _distance; + var offsetY:Number = Math.sin(_angle) * _distance; + + _compositeFilter.setOffsetAt(0, offsetX, offsetY); + + var blurPadding:Padding = _blurFilter.padding; + var left:Number = blurPadding.left; + var right:Number = blurPadding.right; + var top:Number = blurPadding.top; + var bottom:Number = blurPadding.bottom; + + if (offsetX > 0) right += offsetX; else left -= offsetX; + if (offsetY > 0) bottom += offsetY; else top -= offsetY; + + padding.setTo(left, right, top, bottom); + } + + /** The color of the shadow. @default 0x0 */ + public function get color():uint { return _compositeFilter.getColorAt(0); } + public function set color(value:uint):void + { + if (color != value) + { + _compositeFilter.setColorAt(0, value, true); + setRequiresRedraw(); + } + } + + /** The alpha value of the shadow. Values between 0 and 1 modify the opacity; + * values > 1 will make it stronger, i.e. produce a harder edge. @default 0.5 */ + public function get alpha():Number { return _compositeFilter.getAlphaAt(0); } + public function set alpha(value:Number):void + { + if (alpha != value) + { + _compositeFilter.setAlphaAt(0, value); + setRequiresRedraw(); + } + } + + /** The offset distance for the shadow, in points. @default 4.0 */ + public function get distance():Number { return _distance; } + public function set distance(value:Number):void + { + if (_distance != value) + { + _distance = value; + setRequiresRedraw(); + updatePadding(); + } + } + + /** The angle with which the shadow is offset, in radians. @default Math.PI / 4 */ + public function get angle():Number { return _angle; } + public function set angle(value:Number):void + { + if (_angle != value) + { + _angle = value; + setRequiresRedraw(); + updatePadding(); + } + } + + /** The amount of blur with which the shadow is created. + * The number of required passes will be Math.ceil(value) × 2. + * @default 1.0 */ + public function get blur():Number { return _blurFilter.blurX; } + public function set blur(value:Number):void + { + if (blur != value) + { + _blurFilter.blurX = _blurFilter.blurY = value; + setRequiresRedraw(); + updatePadding(); + } + } + + /** @private */ + override public function get resolution():Number { return _blurFilter.resolution; } + override public function set resolution(value:Number):void + { + if (resolution != value) + { + _blurFilter.resolution = value; + setRequiresRedraw(); + updatePadding(); + } + } + } +} diff --git a/mobile_version/src/starling/filters/FilterChain.as b/mobile_version/src/starling/filters/FilterChain.as new file mode 100644 index 00000000..07b53f1c --- /dev/null +++ b/mobile_version/src/starling/filters/FilterChain.as @@ -0,0 +1,168 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.filters +{ + import starling.events.Event; + import starling.rendering.Painter; + import starling.textures.Texture; + import starling.utils.Padding; + + /** The FilterChain allows you to combine several filters into one. The filters will be + * processed in the given order, the number of draw calls per filter adding up. + * Just like conventional filters, a chain may be attached to any display object. + */ + public class FilterChain extends FragmentFilter + { + private var _filters:Vector.; + + // helpers + private static var sPadding:Padding = new Padding(); + + /** Creates a new chain with the given filters. */ + public function FilterChain(...args) + { + _filters = new []; + + for (var i:int = 0, len:int = args.length; i < len; ++i) + { + var filter:FragmentFilter = args[i] as FragmentFilter; + if (filter) addFilterAt(filter, i); + else throw new ArgumentError("pass only fragment filters to the constructor"); + } + + updatePadding(); + addEventListener(Event.ENTER_FRAME, onEnterFrame); + } + + /** Disposes the filter chain itself as well as all contained filters. */ + override public function dispose():void + { + for each (var filter:FragmentFilter in _filters) + filter.dispose(); + + _filters.length = 0; + + super.dispose(); + } + + /** @private */ + override protected function setRequiresRedraw():void + { + updatePadding(); + super.setRequiresRedraw(); + } + + /** @private */ + override public function process(painter:Painter, helper:IFilterHelper, + input0:Texture = null, input1:Texture = null, + input2:Texture = null, input3:Texture = null):Texture + { + var numFilters:int = _filters.length; + var outTexture:Texture = input0; + var inTexture:Texture; + + for (var i:int=0; i sPadding.left) sPadding.left = padding.left; + if (padding.right > sPadding.right) sPadding.right = padding.right; + if (padding.top > sPadding.top) sPadding.top = padding.top; + if (padding.bottom > sPadding.bottom) sPadding.bottom = padding.bottom; + } + + this.padding.copyFrom(sPadding); + } + + private function onEnterFrame(event:Event):void + { + var i:int, numFilters:int = _filters.length; + for (i=0; i; + private var _usePotTextures:Boolean; + private var _textureFormat:String; + private var _preferredScale:Number; + private var _scale:Number; + private var _sizeStep:int; + private var _numPasses:int; + private var _projectionMatrix:Matrix3D; + private var _renderTarget:Texture; + private var _targetBounds:Rectangle; + private var _target:DisplayObject; + + // helpers + private var sRegion:Rectangle = new Rectangle(); + + /** Creates a new, empty instance. */ + public function FilterHelper(textureFormat:String="bgra") + { + _usePotTextures = Starling.current.profile == Context3DProfile.BASELINE_CONSTRAINED; + _preferredScale = Starling.contentScaleFactor; + _textureFormat = textureFormat; + _sizeStep = 64; // must be POT! + _pool = new []; + _projectionMatrix = new Matrix3D(); + _targetBounds = new Rectangle(); + + setSize(_sizeStep, _sizeStep); + } + + /** Purges the pool. */ + public function dispose():void + { + purge(); + } + + /** Starts a new round of rendering. If numPasses is greater than zero, each + * getTexture() call will be counted as one pass; the final pass will then + * return null instead of a texture, to indicate that this pass should be + * rendered to the back buffer. + */ + public function start(numPasses:int, drawLastPassToBackBuffer:Boolean):void + { + _numPasses = drawLastPassToBackBuffer ? numPasses : -1; + } + + /** @inheritDoc */ + public function getTexture(resolution:Number=1.0):Texture + { + var texture:Texture; + var subTexture:SubTexture; + + if (_numPasses >= 0) + if (_numPasses-- == 0) return null; + + if (_pool.length) + texture = _pool.pop(); + else + texture = Texture.empty(_nativeWidth / _scale, _nativeHeight / _scale, + true, false, true, _scale, _textureFormat); + + if (!MathUtil.isEquivalent(texture.width, _width, 0.1) || + !MathUtil.isEquivalent(texture.height, _height, 0.1) || + !MathUtil.isEquivalent(texture.scale, _scale * resolution)) + { + sRegion.setTo(0, 0, _width * resolution, _height * resolution); + subTexture = texture as SubTexture; + + if (subTexture) + subTexture.setTo(texture.root, sRegion, true, null, false, resolution); + else + texture = new SubTexture(texture.root, sRegion, true, null, false, resolution); + } + + texture.root.clear(); + return texture; + } + + /** @inheritDoc */ + public function putTexture(texture:Texture):void + { + if (texture) + { + if (texture.root.nativeWidth == _nativeWidth && texture.root.nativeHeight == _nativeHeight) + _pool.insertAt(_pool.length, texture); + else + texture.dispose(); + } + } + + /** Purges the pool and disposes all textures. */ + public function purge():void + { + for (var i:int = 0, len:int = _pool.length; i < len; ++i) + _pool[i].dispose(); + + _pool.length = 0; + } + + /** Updates the size of the returned textures. Small size changes may allow the + * existing textures to be reused; big size changes will automatically dispose + * them. */ + private function setSize(width:Number, height:Number):void + { + var factor:Number; + var newScale:Number = _preferredScale; + var maxNativeSize:int = Texture.maxSize; + var newNativeWidth:int = getNativeSize(width, newScale); + var newNativeHeight:int = getNativeSize(height, newScale); + + if (newNativeWidth > maxNativeSize || newNativeHeight > maxNativeSize) + { + factor = maxNativeSize / Math.max(newNativeWidth, newNativeHeight); + newNativeWidth *= factor; + newNativeHeight *= factor; + newScale *= factor; + } + + if (_nativeWidth != newNativeWidth || _nativeHeight != newNativeHeight || + _scale != newScale) + { + purge(); + + _scale = newScale; + _nativeWidth = newNativeWidth; + _nativeHeight = newNativeHeight; + } + + _width = width; + _height = height; + } + + private function getNativeSize(size:Number, textureScale:Number):int + { + var nativeSize:Number = size * textureScale; + + if (_usePotTextures) + return nativeSize > _sizeStep ? MathUtil.getNextPowerOfTwo(nativeSize) : _sizeStep; + else + return Math.ceil(nativeSize / _sizeStep) * _sizeStep; + } + + /** The projection matrix that was active when the filter started processing. */ + public function get projectionMatrix3D():Matrix3D { return _projectionMatrix; } + public function set projectionMatrix3D(value:Matrix3D):void + { + _projectionMatrix.copyFrom(value); + } + + /** The render target that was active when the filter started processing. */ + public function get renderTarget():Texture { return _renderTarget; } + public function set renderTarget(value:Texture):void + { + _renderTarget = value; + } + + /** @inheritDoc */ + public function get targetBounds():Rectangle { return _targetBounds; } + public function set targetBounds(value:Rectangle):void + { + _targetBounds.copyFrom(value); + setSize(value.width, value.height); + } + + /** @inheritDoc */ + public function get target():DisplayObject { return _target; } + public function set target(value:DisplayObject):void { _target = value; } + + /** The scale factor of the returned textures. */ + public function get textureScale():Number { return _preferredScale; } + public function set textureScale(value:Number):void + { + _preferredScale = value > 0 ? value : Starling.contentScaleFactor; + } + + /** The texture format of the returned textures. @default BGRA */ + public function get textureFormat():String { return _textureFormat; } + public function set textureFormat(value:String):void { _textureFormat = value; } + } +} diff --git a/mobile_version/src/starling/filters/FragmentFilter.as b/mobile_version/src/starling/filters/FragmentFilter.as new file mode 100644 index 00000000..d78cddcb --- /dev/null +++ b/mobile_version/src/starling/filters/FragmentFilter.as @@ -0,0 +1,634 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.filters +{ + import flash.display3D.Context3DTextureFormat; + import flash.errors.IllegalOperationError; + import flash.geom.Matrix3D; + import flash.geom.Rectangle; + + import starling.core.Starling; + import starling.core.starling_internal; + import starling.display.DisplayObject; + import starling.display.Stage; + import starling.events.Event; + import starling.events.EventDispatcher; + import starling.rendering.FilterEffect; + import starling.rendering.IndexData; + import starling.rendering.Painter; + import starling.rendering.VertexData; + import starling.textures.Texture; + import starling.textures.TextureSmoothing; + import starling.utils.MatrixUtil; + import starling.utils.Padding; + import starling.utils.Pool; + import starling.utils.RectangleUtil; + + /** Dispatched when the settings change in a way that requires a redraw. */ + [Event(name="change", type="starling.events.Event")] + + /** Dispatched every frame on filters assigned to display objects connected to the stage. */ + [Event(name="enterFrame", type="starling.events.EnterFrameEvent")] + + /** The FragmentFilter class is the base class for all filter effects in Starling. + * All filters must extend this class. You can attach them to any display object through the + * filter property. + * + *

A fragment filter works in the following way:

+ *
    + *
  1. The object to be filtered is rendered into a texture.
  2. + *
  3. That texture is passed to the process method.
  4. + *
  5. This method processes the texture using a FilterEffect subclass + * that processes the input via fragment and vertex shaders to achieve a certain + * effect.
  6. + *
  7. If the filter requires several passes, the process method may execute the + * effect several times, or even make use of other filters in the process.
  8. + *
  9. In the end, a quad with the output texture is added to the batch renderer. + * In the next frame, if the object hasn't changed, the filter is drawn directly + * from the render cache.
  10. + *
  11. Alternatively, the last pass may be drawn directly to the back buffer. That saves + * one draw call, but means that the object may not be drawn from the render cache in + * the next frame. Starling makes an educated guess if that makes sense, but you can + * also force it to do so via the alwaysDrawToBackBuffer property.
  12. + *
+ * + *

All of this is set up by the basic FragmentFilter class. Concrete subclasses + * just need to override the protected method createEffect and (optionally) + * process. Multi-pass filters must also override numPasses.

+ * + *

Typically, any properties on the filter are just forwarded to an effect instance, + * which is then used automatically by process to render the filter pass. + * For a simple example on how to write a single-pass filter, look at the implementation of + * the ColorMatrixFilter; for a composite filter (i.e. a filter that combines + * several others), look at the GlowFilter. + *

+ * + *

Beware that a filter instance may only be used on one object at a time!

+ * + *

Animated filters

+ * + *

The process method of a filter is only called when it's necessary, i.e. + * when the filter properties or the target display object changes. This means that you cannot + * rely on the method to be called on a regular basis, as needed when creating an animated + * filter class. Instead, you can do so by listening for an ENTER_FRAME-event. + * It is dispatched on the filter once every frame, as long as the filter is assigned to + * a display object that is connected to the stage.

+ * + *

Caching

+ * + *

Per default, whenever the target display object is changed in any way (i.e. the render + * cache fails), the filter is reprocessed. However, you can manually cache the filter output + * via the method of the same name: this will let the filter redraw the current output texture, + * even if the target object changes later on. That's especially useful if you add a filter + * to an object that changes only rarely, e.g. a TextField or an Image. Keep in mind, though, + * that you have to call cache() again in order for any changes to show up.

+ * + * @see starling.rendering.FilterEffect + */ + public class FragmentFilter extends EventDispatcher + { + private var _quad:FilterQuad; + private var _target:DisplayObject; + private var _effect:FilterEffect; + private var _vertexData:VertexData; + private var _indexData:IndexData; + private var _padding:Padding; + private var _helper:FilterHelper; + private var _resolution:Number; + private var _textureFormat:String; + private var _textureSmoothing:String; + private var _alwaysDrawToBackBuffer:Boolean; + private var _cacheRequested:Boolean; + private var _cached:Boolean; + + // helpers + private static var sMatrix3D:Matrix3D; + + /** Creates a new instance. The base class' implementation just draws the unmodified + * input texture. */ + public function FragmentFilter() + { + _resolution = 1.0; + _textureFormat = Context3DTextureFormat.BGRA; + _textureSmoothing = TextureSmoothing.BILINEAR; + + // Handle lost context (using conventional Flash event for weak listener support) + Starling.current.stage3D.addEventListener(Event.CONTEXT3D_CREATE, + onContextCreated, false, 0, true); + } + + /** Disposes all resources that have been created by the filter. */ + public function dispose():void + { + Starling.current.stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated); + + if (_helper) _helper.dispose(); + if (_effect) _effect.dispose(); + if (_quad) _quad.dispose(); + + _effect = null; + _quad = null; + } + + private function onContextCreated(event:Object):void + { + setRequiresRedraw(); + } + + /** Renders the filtered target object. Most users will never have to call this manually; + * it's executed automatically in the rendering process of the filtered display object. + */ + public function render(painter:Painter):void + { + if (_target == null) + throw new IllegalOperationError("Cannot render filter without target"); + + if (_target.is3D) + _cached = _cacheRequested = false; + + if (!_cached || _cacheRequested) + { + renderPasses(painter, _cacheRequested); + _cacheRequested = false; + } + else if (_quad.visible) + { + _quad.render(painter); + } + } + + private function renderPasses(painter:Painter, forCache:Boolean):void + { + if (_helper == null) _helper = new FilterHelper(_textureFormat); + if (_quad == null) _quad = new FilterQuad(_textureSmoothing); + else { _helper.putTexture(_quad.texture); _quad.texture = null; } + + var bounds:Rectangle = Pool.getRectangle(); // might be recursive -> no static var + var drawLastPassToBackBuffer:Boolean = false; + var origResolution:Number = _resolution; + var renderSpace:DisplayObject = _target.stage || _target.parent; + var isOnStage:Boolean = renderSpace is Stage; + var stage:Stage = Starling.current.stage; + var stageBounds:Rectangle; + + if (!forCache && (_alwaysDrawToBackBuffer || _target.requiresRedraw)) + { + // If 'requiresRedraw' is true, the object is non-static, and we guess that this + // will be the same in the next frame. So we render directly to the back buffer. + // + // -- That, however, is only possible for full alpha values, because + // (1) 'FilterEffect' can't handle alpha (and that will do the rendering) + // (2) we don't want lower layers (CompositeFilter!) to shine through. + + drawLastPassToBackBuffer = painter.state.alpha == 1.0; + painter.excludeFromCache(_target); + } + + if (_target == Starling.current.root) + { + // full-screen filters use exactly the stage bounds + stage.getStageBounds(_target, bounds); + } + else + { + // Unfortunately, the following bounds calculation yields the wrong result when + // drawing a filter to a RenderTexture using a custom matrix. The 'modelviewMatrix' + // should be used for the bounds calculation, but the API doesn't support this. + // A future version should change this to: "getBounds(modelviewMatrix, bounds)" + + _target.getBounds(renderSpace, bounds); + + if (!forCache && isOnStage) // normally, we don't need anything outside + { + stageBounds = stage.getStageBounds(null, Pool.getRectangle()); + RectangleUtil.intersect(bounds, stageBounds, bounds); + Pool.putRectangle(stageBounds); + } + } + + _quad.visible = !bounds.isEmpty(); + if (!_quad.visible) { Pool.putRectangle(bounds); return; } + + if (_padding) RectangleUtil.extend(bounds, + _padding.left, _padding.right, _padding.top, _padding.bottom); + + // integer bounds for maximum sharpness + to avoid jiggling + bounds.setTo(Math.floor(bounds.x), Math.floor(bounds.y), + Math.ceil(bounds.width), Math.ceil(bounds.height)); + + _helper.textureScale = Starling.contentScaleFactor * _resolution; + _helper.projectionMatrix3D = painter.state.projectionMatrix3D; + _helper.renderTarget = painter.state.renderTarget; + _helper.targetBounds = bounds; + _helper.target = _target; + _helper.start(numPasses, drawLastPassToBackBuffer); + + _quad.setBounds(bounds); + _resolution = 1.0; // applied via '_helper.textureScale' already; + // only 'child'-filters use resolution directly (in 'process') + + var wasCacheEnabled:Boolean = painter.cacheEnabled; + var input:Texture = _helper.getTexture(); + var output:Texture; + + painter.cacheEnabled = false; // -> what follows should not be cached + painter.pushState(); + painter.state.alpha = 1.0; + painter.state.renderTarget = input; + painter.state.setProjectionMatrix(bounds.x, bounds.y, + input.root.width, input.root.height, + stage.stageWidth, stage.stageHeight, stage.cameraPosition); + + _target.render(painter); // -> draw target object into 'input' + + painter.finishMeshBatch(); + painter.state.setModelviewMatricesToIdentity(); + painter.state.clipRect = null; + + output = process(painter, _helper, input); // -> feed 'input' to actual filter code + + painter.popState(); + painter.cacheEnabled = wasCacheEnabled; // -> cache again + + if (output) // indirect rendering + { + painter.pushState(); + + if (_target.is3D) painter.state.setModelviewMatricesToIdentity(); // -> stage coords + else _quad.moveVertices(renderSpace, _target); // -> local coords + + _quad.texture = output; + _quad.render(painter); + + painter.finishMeshBatch(); + painter.popState(); + } + + _helper.target = null; + _helper.putTexture(input); + _resolution = origResolution; + Pool.putRectangle(bounds); + } + + /** Does the actual filter processing. This method will be called with up to four input + * textures and must return a new texture (acquired from the helper) that + * contains the filtered output. To to do this, it configures the FilterEffect + * (provided via createEffect) and calls its render method. + * + *

In a standard filter, only input0 will contain a texture; that's the + * object the filter was applied to, rendered into an appropriately sized texture. + * However, filters may also accept multiple textures; that's useful when you need to + * combine the output of several filters into one. For example, the DropShadowFilter + * uses a BlurFilter to create the shadow and then feeds both input and shadow texture + * into a CompositeFilter.

+ * + *

Never create or dispose any textures manually within this method; instead, get + * new textures from the provided helper object, and pass them to the helper when you do + * not need them any longer. Ownership of both input textures and returned texture + * lies at the caller; only temporary textures should be put into the helper.

+ */ + public function process(painter:Painter, helper:IFilterHelper, + input0:Texture=null, input1:Texture=null, + input2:Texture=null, input3:Texture=null):Texture + { + var effect:FilterEffect = this.effect; + var output:Texture = helper.getTexture(_resolution); + var projectionMatrix:Matrix3D; + var bounds:Rectangle = null; + var renderTarget:Texture; + + if (output) // render to texture + { + renderTarget = output; + projectionMatrix = MatrixUtil.createPerspectiveProjectionMatrix(0, 0, + output.root.width / _resolution, output.root.height / _resolution, + 0, 0, null, sMatrix3D); + } + else // render to back buffer + { + bounds = helper.targetBounds; + renderTarget = (helper as FilterHelper).renderTarget; + projectionMatrix = (helper as FilterHelper).projectionMatrix3D; + effect.textureSmoothing = _textureSmoothing; + } + + painter.state.renderTarget = renderTarget; + painter.prepareToDraw(); + painter.drawCount += 1; + + input0.setupVertexPositions(vertexData, 0, "position", bounds); + input0.setupTextureCoordinates(vertexData); + + effect.texture = input0; + effect.mvpMatrix3D = projectionMatrix; + effect.uploadVertexData(vertexData); + effect.uploadIndexData(indexData); + effect.render(0, indexData.numTriangles); + + return output; + } + + /** Creates the effect that does the actual, low-level rendering. + * Must be overridden by all subclasses that do any rendering on their own (instead + * of just forwarding processing to other filters). + */ + protected function createEffect():FilterEffect + { + return new FilterEffect(); + } + + /** Caches the filter output into a texture. + * + *

An uncached filter is rendered every frame (except if it can be rendered from the + * global render cache, which happens if the target object does not change its appearance + * or location relative to the stage). A cached filter is only rendered once; the output + * stays unchanged until you call cache again or change the filter settings. + *

+ * + *

Beware: you cannot cache filters on 3D objects; if the object the filter is attached + * to is a Sprite3D or has a Sprite3D as (grand-) parent, the request will be silently + * ignored. However, you can cache a 2D object that has 3D children!

+ */ + public function cache():void + { + _cached = _cacheRequested = true; + setRequiresRedraw(); + } + + /** Clears the cached output of the filter. After calling this method, the filter will be + * processed once per frame again. */ + public function clearCache():void + { + _cached = _cacheRequested = false; + setRequiresRedraw(); + } + + // enter frame event + + /** @private */ + override public function addEventListener(type:String, listener:Function):void + { + if (type == Event.ENTER_FRAME && _target) + _target.addEventListener(Event.ENTER_FRAME, onEnterFrame); + + super.addEventListener(type, listener); + } + + /** @private */ + override public function removeEventListener(type:String, listener:Function):void + { + if (type == Event.ENTER_FRAME && _target) + _target.removeEventListener(type, onEnterFrame); + + super.removeEventListener(type, listener); + } + + private function onEnterFrame(event:Event):void + { + dispatchEvent(event); + } + + // properties + + /** The effect instance returning the FilterEffect created via createEffect. */ + protected function get effect():FilterEffect + { + if (_effect == null) _effect = createEffect(); + return _effect; + } + + /** The VertexData used to process the effect. Per default, uses the format provided + * by the effect, and contains four vertices enclosing the target object. */ + protected function get vertexData():VertexData + { + if (_vertexData == null) _vertexData = new VertexData(effect.vertexFormat, 4); + return _vertexData; + } + + /** The IndexData used to process the effect. Per default, references a quad (two triangles) + * of four vertices. */ + protected function get indexData():IndexData + { + if (_indexData == null) + { + _indexData = new IndexData(6); + _indexData.addQuad(0, 1, 2, 3); + } + + return _indexData; + } + + /** Call this method when any of the filter's properties changes. + * This will make sure the filter is redrawn in the next frame. */ + protected function setRequiresRedraw():void + { + dispatchEventWith(Event.CHANGE); + if (_target) _target.setRequiresRedraw(); + if (_cached) _cacheRequested = true; + } + + /** Indicates the number of rendering passes required for this filter. + * Subclasses must override this method if the number of passes is not 1. */ + public function get numPasses():int + { + return 1; + } + + /** Called when assigning a target display object. + * Override to plug in class-specific logic. */ + protected function onTargetAssigned(target:DisplayObject):void + { } + + /** Padding can extend the size of the filter texture in all directions. + * That's useful when the filter "grows" the bounds of the object in any direction. */ + public function get padding():Padding + { + if (_padding == null) + { + _padding = new Padding(); + _padding.addEventListener(Event.CHANGE, setRequiresRedraw); + } + + return _padding; + } + + public function set padding(value:Padding):void + { + padding.copyFrom(value); + } + + /** Indicates if the filter is cached (via the cache method). */ + public function get isCached():Boolean { return _cached; } + + /** The resolution of the filter texture. "1" means stage resolution, "0.5" half the stage + * resolution. A lower resolution saves memory and execution time, but results in a lower + * output quality. Values greater than 1 are allowed; such values might make sense for a + * cached filter when it is scaled up. @default 1 + */ + public function get resolution():Number { return _resolution; } + public function set resolution(value:Number):void + { + if (value != _resolution) + { + if (value > 0) _resolution = value; + else throw new ArgumentError("resolution must be > 0"); + setRequiresRedraw(); + } + } + + /** The smoothing mode of the filter texture. @default bilinear */ + public function get textureSmoothing():String { return _textureSmoothing; } + public function set textureSmoothing(value:String):void + { + if (value != _textureSmoothing) + { + _textureSmoothing = value; + if (_quad) _quad.textureSmoothing = value; + setRequiresRedraw(); + } + } + + /** The format of the filter texture. @default BGRA */ + public function get textureFormat():String { return _textureFormat; } + public function set textureFormat(value:String):void + { + if (value != _textureFormat) + { + _textureFormat = value; + if (_helper) _helper.textureFormat = value; + setRequiresRedraw(); + } + } + + /** Indicates if the last filter pass is always drawn directly to the back buffer. + * + *

Per default, the filter tries to automatically render in a smart way: objects that + * are currently moving are rendered to the back buffer, objects that are static are + * rendered into a texture first, which allows the filter to be drawn directly from the + * render cache in the next frame (in case the object remains static).

+ * + *

However, this fails when filters are added to an object that does not support the + * render cache, or to a container with such a child (e.g. a Sprite3D object or a masked + * display object). In such a case, enable this property for maximum performance.

+ * + * @default false + */ + public function get alwaysDrawToBackBuffer():Boolean { return _alwaysDrawToBackBuffer; } + public function set alwaysDrawToBackBuffer(value:Boolean):void + { + _alwaysDrawToBackBuffer = value; + } + + // internal methods + + /** @private */ + starling_internal function setTarget(target:DisplayObject):void + { + if (target != _target) + { + var prevTarget:DisplayObject = _target; + _target = target; + + if (target == null) + { + if (_helper) _helper.purge(); + if (_effect) _effect.purgeBuffers(); + if (_quad) _quad.disposeTexture(); + } + + if (prevTarget) + { + prevTarget.filter = null; + prevTarget.removeEventListener(Event.ENTER_FRAME, onEnterFrame); + } + + if (target) + { + if (hasEventListener(Event.ENTER_FRAME)) + target.addEventListener(Event.ENTER_FRAME, onEnterFrame); + + onTargetAssigned(target); + } + } + } + } +} + +import flash.geom.Matrix; +import flash.geom.Rectangle; + +import starling.display.DisplayObject; +import starling.display.Mesh; +import starling.rendering.IndexData; +import starling.rendering.VertexData; +import starling.textures.Texture; + +class FilterQuad extends Mesh +{ + private static var sMatrix:Matrix = new Matrix(); + + public function FilterQuad(smoothing:String) + { + var vertexData:VertexData = new VertexData(null, 4); + vertexData.numVertices = 4; + + var indexData:IndexData = new IndexData(6); + indexData.addQuad(0, 1, 2, 3); + + super(vertexData, indexData); + + textureSmoothing = smoothing; + pixelSnapping = false; + } + + override public function dispose():void + { + disposeTexture(); + super.dispose(); + } + + public function disposeTexture():void + { + if (texture) + { + texture.dispose(); + texture = null; + } + } + + public function moveVertices(sourceSpace:DisplayObject, targetSpace:DisplayObject):void + { + if (targetSpace.is3D) + throw new Error("cannot move vertices into 3D space"); + else if (sourceSpace != targetSpace) + { + targetSpace.getTransformationMatrix(sourceSpace, sMatrix).invert(); // ss could be null! + vertexData.transformPoints("position", sMatrix); + } + } + + public function setBounds(bounds:Rectangle):void + { + var vertexData:VertexData = this.vertexData; + var attrName:String = "position"; + + vertexData.setPoint(0, attrName, bounds.x, bounds.y); + vertexData.setPoint(1, attrName, bounds.right, bounds.y); + vertexData.setPoint(2, attrName, bounds.x, bounds.bottom); + vertexData.setPoint(3, attrName, bounds.right, bounds.bottom); + } + + override public function set texture(value:Texture):void + { + super.texture = value; + if (value) value.setupTextureCoordinates(vertexData); + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/filters/GlowFilter.as b/mobile_version/src/starling/filters/GlowFilter.as new file mode 100644 index 00000000..82929a7a --- /dev/null +++ b/mobile_version/src/starling/filters/GlowFilter.as @@ -0,0 +1,125 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.filters +{ + import starling.rendering.Painter; + import starling.textures.Texture; + + /** The GlowFilter class lets you apply a glow effect to display objects. + * It is similar to the drop shadow filter with the distance and angle properties set to 0. + */ + public class GlowFilter extends FragmentFilter + { + private var _blurFilter:BlurFilter; + private var _compositeFilter:CompositeFilter; + + /** Initializes a new GlowFilter instance with the specified parameters. + * + * @param color the color of the glow + * @param alpha the alpha value of the glow. Values between 0 and 1 modify the + * opacity; values > 1 will make it stronger, i.e. produce a harder edge. + * @param blur the amount of blur used to create the glow. Note that high + * values will cause the number of render passes to grow. + * @param resolution the resolution of the filter texture. '1' means full resolution, + * '0.5' half resolution, etc. + */ + public function GlowFilter(color:uint=0xffff00, alpha:Number=1.0, blur:Number=1.0, + resolution:Number=0.5) + { + _blurFilter = new BlurFilter(blur, blur, resolution); + _compositeFilter = new CompositeFilter(); + _compositeFilter.setColorAt(0, color, true); + _compositeFilter.setAlphaAt(0, alpha); + + updatePadding(); + } + + /** @inheritDoc */ + override public function dispose():void + { + _blurFilter.dispose(); + _compositeFilter.dispose(); + + super.dispose(); + } + + /** @private */ + override public function process(painter:Painter, helper:IFilterHelper, + input0:Texture = null, input1:Texture = null, + input2:Texture = null, input3:Texture = null):Texture + { + var glow:Texture = _blurFilter.process(painter, helper, input0); + var result:Texture = _compositeFilter.process(painter, helper, glow, input0); + helper.putTexture(glow); + return result; + } + + /** @private */ + override public function get numPasses():int + { + return _blurFilter.numPasses + _compositeFilter.numPasses; + } + + private function updatePadding():void + { + padding.copyFrom(_blurFilter.padding); + } + + /** The color of the glow. @default 0xffff00 */ + public function get color():uint { return _compositeFilter.getColorAt(0); } + public function set color(value:uint):void + { + if (color != value) + { + _compositeFilter.setColorAt(0, value, true); + setRequiresRedraw(); + } + } + + /** The alpha value of the glow. Values between 0 and 1 modify the opacity; + * values > 1 will make it stronger, i.e. produce a harder edge. @default 1.0 */ + public function get alpha():Number { return _compositeFilter.getAlphaAt(0); } + public function set alpha(value:Number):void + { + if (alpha != value) + { + _compositeFilter.setAlphaAt(0, value); + setRequiresRedraw(); + } + } + + /** The amount of blur with which the glow is created. + * The number of required passes will be Math.ceil(value) × 2. + * @default 1.0 */ + public function get blur():Number { return _blurFilter.blurX; } + public function set blur(value:Number):void + { + if (blur != value) + { + _blurFilter.blurX = _blurFilter.blurY = value; + setRequiresRedraw(); + updatePadding(); + } + } + + /** @private */ + override public function get resolution():Number { return _blurFilter.resolution; } + override public function set resolution(value:Number):void + { + if (resolution != value) + { + _blurFilter.resolution = value; + setRequiresRedraw(); + updatePadding(); + } + } + } +} diff --git a/mobile_version/src/starling/filters/IFilterHelper.as b/mobile_version/src/starling/filters/IFilterHelper.as new file mode 100644 index 00000000..c1e47de9 --- /dev/null +++ b/mobile_version/src/starling/filters/IFilterHelper.as @@ -0,0 +1,49 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.filters +{ + import flash.geom.Rectangle; + + import starling.display.DisplayObject; + import starling.textures.Texture; + + /** An interface describing the methods available on the helper object passed + * to the process call of fragment filters. It provides information about the + * target object and methods to acquire and release pass textures. + * + * @see FragmentFilter#process() + */ + public interface IFilterHelper + { + /** Gets a pass texture from the pool, or creates a new one (cleared and ready to be used + * as render target). Its size is dictated by the bounds of the target display object + * plus padding. + * + *

Beware: each call of this method counts as one render pass. For performance reasons, + * the filter may be configured to render the last pass directly to the back buffer. In + * that case, this method will return null for the last pass! That's the + * sign for the process method to draw to the back buffer. If you receive + * null too soon, the filter class probably didn't correctly override + * numPasses.

+ */ + function getTexture(resolution:Number=1.0):Texture; + + /** Puts a texture back into the pool to be reused later (or to be disposed + * with the pool). */ + function putTexture(texture:Texture):void; + + /** The bounds of the target object (plus padding) in stage coordinates. */ + function get targetBounds():Rectangle; + + /** The display object the filter is currently attached to. */ + function get target():DisplayObject; + } +} diff --git a/mobile_version/src/starling/geom/Polygon.as b/mobile_version/src/starling/geom/Polygon.as new file mode 100644 index 00000000..75362b2e --- /dev/null +++ b/mobile_version/src/starling/geom/Polygon.as @@ -0,0 +1,613 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.geom +{ + import flash.geom.Point; + import flash.utils.getQualifiedClassName; + + import starling.rendering.IndexData; + import starling.rendering.VertexData; + import starling.utils.MathUtil; + import starling.utils.Pool; + + /** A polygon describes a closed two-dimensional shape bounded by a number of straight + * line segments. + * + *

The vertices of a polygon form a closed path (i.e. the last vertex will be connected + * to the first). It is recommended to provide the vertices in clockwise order. + * Self-intersecting paths are not supported and will give wrong results on triangulation, + * area calculation, etc.

+ */ + public class Polygon + { + private var _coords:Vector.; + + // Helper object + private static var sRestIndices:Vector. = new []; + + /** Creates a Polygon with the given coordinates. + * @param vertices an array that contains either 'Point' instances or + * alternating 'x' and 'y' coordinates. + */ + public function Polygon(vertices:Array=null) + { + _coords = new []; + addVertices.apply(this, vertices); + } + + /** Creates a clone of this polygon. */ + public function clone():Polygon + { + var clone:Polygon = new Polygon(); + var numCoords:int = _coords.length; + + for (var i:int=0; i 0) + { + if (args[0] is Point) + { + for (i=0; i= 0 && index <= numVertices) + { + _coords[index * 2 ] = x; + _coords[index * 2 + 1] = y; + } + else throw new RangeError("Invalid index: " + index); + } + + /** Returns the coordinates of a certain vertex. */ + public function getVertex(index:int, out:Point=null):Point + { + if (index >= 0 && index < numVertices) + { + out ||= new Point(); + out.setTo(_coords[index * 2], _coords[index * 2 + 1]); + return out; + } + else throw new RangeError("Invalid index: " + index); + } + + /** Figures out if the given coordinates lie within the polygon. */ + public function contains(x:Number, y:Number):Boolean + { + // Algorithm & implementation thankfully taken from: + // -> http://alienryderflex.com/polygon/ + + var i:int, j:int = numVertices - 1; + var oddNodes:uint = 0; + + for (i=0; i= y || jy < y && iy >= y) && (ix <= x || jx <= x)) + oddNodes ^= uint(ix + (y - iy) / (jy - iy) * (jx - ix) < x); + + j = i; + } + + return oddNodes != 0; + } + + /** Figures out if the given point lies within the polygon. */ + public function containsPoint(point:Point):Boolean + { + return contains(point.x, point.y); + } + + /** Calculates a possible representation of the polygon via triangles. The resulting + * IndexData instance will reference the polygon vertices as they are saved in this + * Polygon instance, optionally incremented by the given offset. + * + *

If you pass an indexData object, the new indices will be appended to it. + * Otherwise, a new instance will be created.

*/ + public function triangulate(indexData:IndexData=null, offset:int=0):IndexData + { + // Algorithm "Ear clipping method" described here: + // -> https://en.wikipedia.org/wiki/Polygon_triangulation + // + // Implementation inspired by: + // -> http://polyk.ivank.net + + var numVertices:int = this.numVertices; + var numTriangles:int = this.numTriangles; + var i:int, restIndexPos:int, numRestIndices:int; + + if (indexData == null) indexData = new IndexData(numTriangles * 3); + if (numTriangles == 0) return indexData; + + sRestIndices.length = numVertices; + for (i=0; i 3) + { + // In each step, we look at 3 subsequent vertices. If those vertices spawn up + // a triangle that is convex and does not contain any other vertices, it is an 'ear'. + // We remove those ears until only one remains -> each ear is one of our wanted + // triangles. + + var otherIndex:uint; + var earFound:Boolean = false; + var i0:uint = sRestIndices[ restIndexPos % numRestIndices]; + var i1:uint = sRestIndices[(restIndexPos + 1) % numRestIndices]; + var i2:uint = sRestIndices[(restIndexPos + 2) % numRestIndices]; + + a.setTo(_coords[2 * i0], _coords[2 * i0 + 1]); + b.setTo(_coords[2 * i1], _coords[2 * i1 + 1]); + c.setTo(_coords[2 * i2], _coords[2 * i2 + 1]); + + if (isConvexTriangle(a.x, a.y, b.x, b.y, c.x, c.y)) + { + earFound = true; + for (i = 3; i < numRestIndices; ++i) + { + otherIndex = sRestIndices[(restIndexPos + i) % numRestIndices]; + p.setTo(_coords[2 * otherIndex], _coords[2 * otherIndex + 1]); + + if (MathUtil.isPointInTriangle(p, a, b, c)) + { + earFound = false; + break; + } + } + } + + if (earFound) + { + indexData.addTriangle(i0 + offset, i1 + offset, i2 + offset); + sRestIndices.removeAt((restIndexPos + 1) % numRestIndices); + + numRestIndices--; + restIndexPos = 0; + } + else + { + restIndexPos++; + if (restIndexPos == numRestIndices) break; // no more ears + } + } + + Pool.putPoint(a); + Pool.putPoint(b); + Pool.putPoint(c); + Pool.putPoint(p); + + indexData.addTriangle(sRestIndices[0] + offset, + sRestIndices[1] + offset, + sRestIndices[2] + offset); + return indexData; + } + + /** Copies all vertices to a 'VertexData' instance, beginning at a certain target index. */ + public function copyToVertexData(target:VertexData=null, targetVertexID:int=0, + attrName:String="position"):void + { + var numVertices:int = this.numVertices; + var requiredTargetLength:int = targetVertexID + numVertices; + + if (target.numVertices < requiredTargetLength) + target.numVertices = requiredTargetLength; + + for (var i:int=0; i 0) result += "\n"; + + for (var i:int=0; ib->c is to on the right-hand side of a->b. */ + [Inline] + private static function isConvexTriangle(ax:Number, ay:Number, + bx:Number, by:Number, + cx:Number, cy:Number):Boolean + { + // dot product of [the normal of (a->b)] and (b->c) must be positive + return (ay - by) * (cx - bx) + (bx - ax) * (cy - by) >= 0; + } + + /** Finds out if the vector a->b intersects c->d. */ + private static function areVectorsIntersecting(ax:Number, ay:Number, bx:Number, by:Number, + cx:Number, cy:Number, dx:Number, dy:Number):Boolean + { + if ((ax == bx && ay == by) || (cx == dx && cy == dy)) return false; // length = 0 + + var abx:Number = bx - ax; + var aby:Number = by - ay; + var cdx:Number = dx - cx; + var cdy:Number = dy - cy; + var tDen:Number = cdy * abx - cdx * aby; + + if (tDen == 0.0) return false; // parallel or identical + + var t:Number = (aby * (cx - ax) - abx * (cy - ay)) / tDen; + + if (t < 0 || t > 1) return false; // outside c->d + + var s:Number = aby ? (cy - ay + t * cdy) / aby : + (cx - ax + t * cdx) / abx; + + return s >= 0.0 && s <= 1.0; // inside a->b + } + + // properties + + /** Indicates if the polygon's line segments are not self-intersecting. + * Beware: this is a brute-force implementation with O(n^2). */ + public function get isSimple():Boolean + { + var numCoords:int = _coords.length; + if (numCoords <= 6) return true; + + for (var i:int=0; i= 6) + { + for (var i:int = 0; i < numCoords; i += 2) + { + area += _coords[i ] * _coords[(i+3) % numCoords]; + area -= _coords[i+1] * _coords[(i+2) % numCoords]; + } + } + + return area / 2.0; + } + + /** Returns the total number of vertices spawning up the polygon. Assigning a value + * that's smaller than the current number of vertices will crop the path; a bigger + * value will fill up the path with zeros. */ + public function get numVertices():int + { + return _coords.length / 2; + } + + public function set numVertices(value:int):void + { + var oldLength:int = numVertices; + _coords.length = value * 2; + + if (oldLength < value) + { + for (var i:int=oldLength; i < value; ++i) + _coords[i * 2] = _coords[i * 2 + 1] = 0.0; + } + } + + /** Returns the number of triangles that will be required when triangulating the polygon. */ + public function get numTriangles():int + { + var numVertices:int = this.numVertices; + return numVertices >= 3 ? numVertices - 2 : 0; + } + } +} + +import flash.errors.IllegalOperationError; +import flash.utils.getQualifiedClassName; + +import starling.geom.Polygon; +import starling.rendering.IndexData; + +class ImmutablePolygon extends Polygon +{ + private var _frozen:Boolean; + + public function ImmutablePolygon(vertices:Array) + { + super(vertices); + _frozen = true; + } + + override public function addVertices(...args):void + { + if (_frozen) throw getImmutableError(); + else super.addVertices.apply(this, args); + } + + override public function setVertex(index:int, x:Number, y:Number):void + { + if (_frozen) throw getImmutableError(); + else super.setVertex(index, x, y); + } + + override public function reverse():void + { + if (_frozen) throw getImmutableError(); + else super.reverse(); + } + + override public function set numVertices(value:int):void + { + if (_frozen) throw getImmutableError(); + else super.reverse(); + } + + private function getImmutableError():Error + { + var className:String = getQualifiedClassName(this).split("::").pop(); + var msg:String = className + " cannot be modified. Call 'clone' to create a mutable copy."; + return new IllegalOperationError(msg); + } +} + +class Ellipse extends ImmutablePolygon +{ + private var _x:Number; + private var _y:Number; + private var _radiusX:Number; + private var _radiusY:Number; + + public function Ellipse(x:Number, y:Number, radiusX:Number, radiusY:Number, numSides:int = -1) + { + _x = x; + _y = y; + _radiusX = radiusX; + _radiusY = radiusY; + + super(getVertices(numSides)); + } + + private function getVertices(numSides:int):Array + { + if (numSides < 0) numSides = Math.PI * (_radiusX + _radiusY) / 4.0; + if (numSides < 6) numSides = 6; + + var vertices:Array = []; + var angleDelta:Number = 2 * Math.PI / numSides; + var angle:Number = 0; + + for (var i:int=0; i= _x && x <= _x + _width && + y >= _y && y <= _y + _height; + } + + override public function get area():Number + { + return _width * _height; + } + + override public function get isSimple():Boolean + { + return true; + } + + override public function get isConvex():Boolean + { + return true; + } +} diff --git a/mobile_version/src/starling/rendering/BatchProcessor.as b/mobile_version/src/starling/rendering/BatchProcessor.as new file mode 100644 index 00000000..dc04bd64 --- /dev/null +++ b/mobile_version/src/starling/rendering/BatchProcessor.as @@ -0,0 +1,218 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.rendering +{ + import flash.geom.Matrix; + + import starling.display.Mesh; + import starling.display.MeshBatch; + import starling.utils.MeshSubset; + + /** This class manages a list of mesh batches of different types; + * it acts as a "meta" MeshBatch that initiates all rendering. + */ + internal class BatchProcessor + { + private var _batches:Vector.; + private var _batchPool:BatchPool; + private var _currentBatch:MeshBatch; + private var _currentStyleType:Class; + private var _onBatchComplete:Function; + private var _cacheToken:BatchToken; + + // helper objects + private static var sMeshSubset:MeshSubset = new MeshSubset(); + + /** Creates a new batch processor. */ + public function BatchProcessor() + { + _batches = new []; + _batchPool = new BatchPool(); + _cacheToken = new BatchToken(); + } + + /** Disposes all batches (including those in the reusable pool). */ + public function dispose():void + { + for each (var batch:MeshBatch in _batches) + batch.dispose(); + + _batches.length = 0; + _batchPool.purge(); + _currentBatch = null; + _onBatchComplete = null; + } + + /** Adds a mesh to the current batch, or to a new one if the current one does not support + * it. Whenever the batch changes, onBatchComplete is called for the previous + * one. + * + * @param mesh the mesh to add to the current (or new) batch. + * @param state the render state from which to take the current settings for alpha, + * modelview matrix, and blend mode. + * @param subset the subset of the mesh you want to add, or null for + * the complete mesh. + * @param ignoreTransformations when enabled, the mesh's vertices will be added + * without transforming them in any way (no matter the value of the + * state's modelviewMatrix). + */ + public function addMesh(mesh:Mesh, state:RenderState, subset:MeshSubset=null, + ignoreTransformations:Boolean=false):void + { + if (subset == null) + { + subset = sMeshSubset; + subset.vertexID = subset.indexID = 0; + subset.numVertices = mesh.numVertices; + subset.numIndices = mesh.numIndices; + } + else + { + if (subset.numVertices < 0) subset.numVertices = mesh.numVertices - subset.vertexID; + if (subset.numIndices < 0) subset.numIndices = mesh.numIndices - subset.indexID; + } + + if (subset.numVertices > 0) + { + if (_currentBatch == null || !_currentBatch.canAddMesh(mesh, subset.numVertices)) + { + finishBatch(); + + _currentStyleType = mesh.style.type; + _currentBatch = _batchPool.get(_currentStyleType); + _currentBatch.blendMode = state ? state.blendMode : mesh.blendMode; + _cacheToken.setTo(_batches.length); + _batches[_batches.length] = _currentBatch; + } + + var matrix:Matrix = state ? state._modelviewMatrix : null; + var alpha:Number = state ? state._alpha : 1.0; + + _currentBatch.addMesh(mesh, matrix, alpha, subset, ignoreTransformations); + _cacheToken.vertexID += subset.numVertices; + _cacheToken.indexID += subset.numIndices; + } + } + + /** Finishes the current batch, i.e. call the 'onComplete' callback on the batch and + * prepares initialization of a new one. */ + public function finishBatch():void + { + var meshBatch:MeshBatch = _currentBatch; + + if (meshBatch) + { + _currentBatch = null; + _currentStyleType = null; + + if (_onBatchComplete != null) + _onBatchComplete(meshBatch); + } + } + + /** Clears all batches and adds them to a pool so they can be reused later. */ + public function clear():void + { + var numBatches:int = _batches.length; + + for (var i:int=0; i in _batchLists) + { + for (var i:int=0; i = _batchLists[styleType]; + if (batchList == null) + { + batchList = new []; + _batchLists[styleType] = batchList; + } + + if (batchList.length > 0) return batchList.pop(); + else return new MeshBatch(); + } + + public function put(meshBatch:MeshBatch):void + { + var styleType:Class = meshBatch.style.type; + var batchList:Vector. = _batchLists[styleType]; + if (batchList == null) + { + batchList = new []; + _batchLists[styleType] = batchList; + } + + meshBatch.clear(); + batchList[batchList.length] = meshBatch; + } +} diff --git a/mobile_version/src/starling/rendering/BatchToken.as b/mobile_version/src/starling/rendering/BatchToken.as new file mode 100644 index 00000000..f3fd49dc --- /dev/null +++ b/mobile_version/src/starling/rendering/BatchToken.as @@ -0,0 +1,77 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.rendering +{ + import starling.utils.StringUtil; + + /** Points to a location within a list of MeshBatches. + * + *

Starling uses these tokens in its render cache. Each call to + * painter.pushState() or painter.popState() provides a token + * referencing the current location within the cache. In the next frame, if the relevant + * part of the display tree has not changed, these tokens can be used to render directly + * from the cache instead of constructing new MeshBatches.

+ * + * @see Painter + */ + public class BatchToken + { + /** The ID of the current MeshBatch. */ + public var batchID:int; + + /** The ID of the next vertex within the current MeshBatch. */ + public var vertexID:int; + + /** The ID of the next index within the current MeshBatch. */ + public var indexID:int; + + /** Creates a new BatchToken. */ + public function BatchToken(batchID:int=0, vertexID:int=0, indexID:int=0) + { + setTo(batchID, vertexID, indexID); + } + + /** Copies the properties from the given token to this instance. */ + public function copyFrom(token:BatchToken):void + { + batchID = token.batchID; + vertexID = token.vertexID; + indexID = token.indexID; + } + + /** Changes all properties at once. */ + public function setTo(batchID:int=0, vertexID:int=0, indexID:int=0):void + { + this.batchID = batchID; + this.vertexID = vertexID; + this.indexID = indexID; + } + + /** Resets all properties to zero. */ + public function reset():void + { + batchID = vertexID = indexID = 0; + } + + /** Indicates if this token contains the same values as the given one. */ + public function equals(other:BatchToken):Boolean + { + return batchID == other.batchID && vertexID == other.vertexID && indexID == other.indexID; + } + + /** Creates a String representation of this instance. */ + public function toString():String + { + return StringUtil.format("[BatchToken batchID={0} vertexID={1} indexID={2}]", + batchID, vertexID, indexID); + } + } +} diff --git a/mobile_version/src/starling/rendering/Effect.as b/mobile_version/src/starling/rendering/Effect.as new file mode 100644 index 00000000..e9125557 --- /dev/null +++ b/mobile_version/src/starling/rendering/Effect.as @@ -0,0 +1,381 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.rendering +{ + import flash.display3D.Context3D; + import flash.display3D.Context3DProgramType; + import flash.display3D.IndexBuffer3D; + import flash.display3D.VertexBuffer3D; + import flash.events.Event; + import flash.geom.Matrix3D; + import flash.utils.Dictionary; + import flash.utils.getQualifiedClassName; + + import starling.core.Starling; + import starling.errors.MissingContextError; + import starling.utils.execute; + + /** An effect encapsulates all steps of a Stage3D draw operation. It configures the + * render context and sets up shader programs as well as index- and vertex-buffers, thus + * providing the basic mechanisms of all low-level rendering. + * + *

Using the Effect class

+ * + *

Effects are mostly used by the MeshStyle and FragmentFilter + * classes. When you extend those classes, you'll be required to provide a custom effect. + * Setting it up for rendering is done by the base class, though, so you rarely have to + * initiate the rendering yourself. Nevertheless, it's good to know how an effect is doing + * its work.

+ * + *

Using an effect always follows steps shown in the example below. You create the + * effect, configure it, upload vertex data and then: draw!

+ * + * + * // create effect + * var effect:MeshEffect = new MeshEffect(); + * + * // configure effect + * effect.mvpMatrix3D = painter.state.mvpMatrix3D; + * effect.texture = getHeroTexture(); + * effect.color = 0xf0f0f0; + * + * // upload vertex data + * effect.uploadIndexData(indexData); + * effect.uploadVertexData(vertexData); + * + * // draw! + * effect.render(0, numTriangles); + * + *

Note that the VertexData being uploaded has to be created with the same + * format as the one returned by the effect's vertexFormat property.

+ * + *

Extending the Effect class

+ * + *

The base Effect-class can only render white triangles, which is not much + * use in itself. However, it is designed to be extended; subclasses can easily implement any + * kinds of shaders.

+ * + *

Normally, you won't extend this class directly, but either FilterEffect + * or MeshEffect, depending on your needs (i.e. if you want to create a new + * fragment filter or a new mesh style). Whichever base class you're extending, you should + * override the following methods:

+ * + *
    + *
  • createProgram():Program — must create the actual program containing + * vertex- and fragment-shaders. A program will be created only once for each render + * context; this is taken care of by the base class.
  • + *
  • get programVariantName():uint (optional) — override this if your + * effect requires different programs, depending on its settings. The recommended + * way to do this is via a bit-mask that uniquely encodes the current settings.
  • + *
  • get vertexFormat():String (optional) — must return the + * VertexData format that this effect requires for its vertices. If + * the effect does not require any special attributes, you can leave this out.
  • + *
  • beforeDraw(context:Context3D):void — Set up your context by + * configuring program constants and buffer attributes.
  • + *
  • afterDraw(context:Context3D):void — Will be called directly after + * context.drawTriangles(). Clean up any context configuration here.
  • + *
+ * + *

Furthermore, you need to add properties that manage the data you require on rendering, + * e.g. the texture(s) that should be used, program constants, etc. I recommend looking at + * the implementations of Starling's FilterEffect and MeshEffect + * classes to see how to approach sub-classing.

+ * + * @see FilterEffect + * @see MeshEffect + * @see starling.styles.MeshStyle + * @see starling.filters.FragmentFilter + * @see starling.utils.RenderUtil + */ + public class Effect + { + /** The vertex format expected by uploadVertexData: + * "position:float2" */ + public static const VERTEX_FORMAT:VertexDataFormat = + VertexDataFormat.fromString("position:float2"); + + private var _vertexBuffer:VertexBuffer3D; + private var _vertexBufferSize:int; // in bytes + private var _indexBuffer:IndexBuffer3D; + private var _indexBufferSize:int; // in number of indices + private var _indexBufferUsesQuadLayout:Boolean; + + private var _mvpMatrix3D:Matrix3D; + private var _onRestore:Function; + private var _programBaseName:String; + + // helper objects + private static var sProgramNameCache:Dictionary = new Dictionary(); + + /** Creates a new effect. */ + public function Effect() + { + _mvpMatrix3D = new Matrix3D(); + _programBaseName = getQualifiedClassName(this); + + // Handle lost context (using conventional Flash event for weak listener support) + Starling.current.stage3D.addEventListener(Event.CONTEXT3D_CREATE, + onContextCreated, false, 20, true); + } + + /** Purges the index- and vertex-buffers. */ + public function dispose():void + { + Starling.current.stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated); + purgeBuffers(); + } + + private function onContextCreated(event:Event):void + { + purgeBuffers(); + execute(_onRestore, this); + } + + /** Purges one or both of the vertex- and index-buffers. */ + public function purgeBuffers(vertexBuffer:Boolean=true, indexBuffer:Boolean=true):void + { + // We wrap the dispose calls in a try/catch block to work around a stage3D problem. + // Since they are not re-used later, that shouldn't have any evil side effects. + + if (_vertexBuffer && vertexBuffer) + { + try { _vertexBuffer.dispose(); } catch (e:Error) {} + _vertexBuffer = null; + } + + if (_indexBuffer && indexBuffer) + { + try { _indexBuffer.dispose(); } catch (e:Error) {} + _indexBuffer = null; + } + } + + /** Uploads the given index data to the internal index buffer. If the buffer is too + * small, a new one is created automatically. + * + * @param indexData The IndexData instance to upload. + * @param bufferUsage The expected buffer usage. Use one of the constants defined in + * Context3DBufferUsage. Only used when the method call + * causes the creation of a new index buffer. + */ + public function uploadIndexData(indexData:IndexData, + bufferUsage:String="staticDraw"):void + { + var numIndices:int = indexData.numIndices; + var isQuadLayout:Boolean = indexData.useQuadLayout; + var wasQuadLayout:Boolean = _indexBufferUsesQuadLayout; + + if (_indexBuffer) + { + if (numIndices <= _indexBufferSize) + { + if (!isQuadLayout || !wasQuadLayout) + { + indexData.uploadToIndexBuffer(_indexBuffer); + _indexBufferUsesQuadLayout = isQuadLayout && numIndices == _indexBufferSize; + } + } + else + purgeBuffers(false, true); + } + if (_indexBuffer == null) + { + _indexBuffer = indexData.createIndexBuffer(true, bufferUsage); + _indexBufferSize = numIndices; + _indexBufferUsesQuadLayout = isQuadLayout; + } + } + + /** Uploads the given vertex data to the internal vertex buffer. If the buffer is too + * small, a new one is created automatically. + * + * @param vertexData The VertexData instance to upload. + * @param bufferUsage The expected buffer usage. Use one of the constants defined in + * Context3DBufferUsage. Only used when the method call + * causes the creation of a new vertex buffer. + */ + public function uploadVertexData(vertexData:VertexData, + bufferUsage:String="staticDraw"):void + { + if (_vertexBuffer) + { + if (vertexData.size <= _vertexBufferSize) + vertexData.uploadToVertexBuffer(_vertexBuffer); + else + purgeBuffers(true, false); + } + if (_vertexBuffer == null) + { + _vertexBuffer = vertexData.createVertexBuffer(true, bufferUsage); + _vertexBufferSize = vertexData.size; + } + } + + // rendering + + /** Draws the triangles described by the index- and vertex-buffers, or a range of them. + * This calls beforeDraw, context.drawTriangles, and + * afterDraw, in this order. */ + public function render(firstIndex:int=0, numTriangles:int=-1):void + { + if (numTriangles < 0) numTriangles = _indexBufferSize / 3; + if (numTriangles == 0) return; + + var context:Context3D = Starling.context; + if (context == null) throw new MissingContextError(); + + beforeDraw(context); + context.drawTriangles(indexBuffer, firstIndex, numTriangles); + afterDraw(context); + } + + /** This method is called by render, directly before + * context.drawTriangles. It activates the program and sets up + * the context with the following constants and attributes: + * + *
    + *
  • vc0-vc3 — MVP matrix
  • + *
  • va0 — vertex position (xy)
  • + *
+ */ + protected function beforeDraw(context:Context3D):void + { + program.activate(context); + vertexFormat.setVertexBufferAt(0, vertexBuffer, "position"); + context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, mvpMatrix3D, true); + } + + /** This method is called by render, directly after + * context.drawTriangles. Resets vertex buffer attributes. + */ + protected function afterDraw(context:Context3D):void + { + context.setVertexBufferAt(0, null); + } + + // program management + + /** Creates the program (a combination of vertex- and fragment-shader) used to render + * the effect with the current settings. Override this method in a subclass to create + * your shaders. This method will only be called once; the program is automatically stored + * in the Painter and re-used by all instances of this effect. + * + *

The basic implementation always outputs pure white.

+ */ + protected function createProgram():Program + { + var vertexShader:String = [ + "m44 op, va0, vc0", // 4x4 matrix transform to output clipspace + "seq v0, va0, va0" // this is a hack that always produces "1" + ].join("\n"); + + var fragmentShader:String = + "mov oc, v0"; // output color: white + + return Program.fromSource(vertexShader, fragmentShader); + } + + /** Override this method if the effect requires a different program depending on the + * current settings. Ideally, you do this by creating a bit mask encoding all the options. + * This method is called often, so do not allocate any temporary objects when overriding. + * + * @default 0 + */ + protected function get programVariantName():uint + { + return 0; + } + + /** Returns the base name for the program. + * @default the fully qualified class name + */ + protected function get programBaseName():String { return _programBaseName; } + protected function set programBaseName(value:String):void { _programBaseName = value; } + + /** Returns the full name of the program, which is used to register it at the current + * Painter. + * + *

The default implementation efficiently combines the program's base and variant + * names (e.g. LightEffect#42). It shouldn't be necessary to override + * this method.

+ */ + protected function get programName():String + { + var baseName:String = this.programBaseName; + var variantName:uint = this.programVariantName; + var nameCache:Dictionary = sProgramNameCache[baseName]; + + if (nameCache == null) + { + nameCache = new Dictionary(); + sProgramNameCache[baseName] = nameCache; + } + + var name:String = nameCache[variantName]; + + if (name == null) + { + if (variantName) name = baseName + "#" + variantName.toString(16); + else name = baseName; + + nameCache[variantName] = name; + } + + return name; + } + + /** Returns the current program, either by creating a new one (via + * createProgram) or by getting it from the Painter. + * Do not override this method! Instead, implement createProgram. */ + protected function get program():Program + { + var name:String = this.programName; + var painter:Painter = Starling.painter; + var program:Program = painter.getProgram(name); + + if (program == null) + { + program = createProgram(); + painter.registerProgram(name, program); + } + + return program; + } + + // properties + + /** The function that you provide here will be called after a context loss. + * Call both "upload..." methods from within the callback to restore any vertex or + * index buffers. The callback will be executed with the effect as its sole parameter. */ + public function get onRestore():Function { return _onRestore; } + public function set onRestore(value:Function):void { _onRestore = value; } + + /** The data format that this effect requires from the VertexData that it renders: + * "position:float2" */ + public function get vertexFormat():VertexDataFormat { return VERTEX_FORMAT; } + + /** The MVP (modelview-projection) matrix transforms vertices into clipspace. */ + public function get mvpMatrix3D():Matrix3D { return _mvpMatrix3D; } + public function set mvpMatrix3D(value:Matrix3D):void { _mvpMatrix3D.copyFrom(value); } + + /** The internally used index buffer used on rendering. */ + protected function get indexBuffer():IndexBuffer3D { return _indexBuffer; } + + /** The current size of the index buffer (in number of indices). */ + protected function get indexBufferSize():int { return _indexBufferSize; } + + /** The internally used vertex buffer used on rendering. */ + protected function get vertexBuffer():VertexBuffer3D { return _vertexBuffer; } + + /** The current size of the vertex buffer (in blocks of 32 bits). */ + protected function get vertexBufferSize():int { return _vertexBufferSize; } + } +} diff --git a/mobile_version/src/starling/rendering/FilterEffect.as b/mobile_version/src/starling/rendering/FilterEffect.as new file mode 100644 index 00000000..f03e0dd7 --- /dev/null +++ b/mobile_version/src/starling/rendering/FilterEffect.as @@ -0,0 +1,147 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.rendering +{ + import flash.display3D.Context3D; + + import starling.textures.Texture; + import starling.textures.TextureSmoothing; + import starling.utils.RenderUtil; + + /** An effect drawing a mesh of textured vertices. + * This is the standard effect that is the base for all fragment filters; + * if you want to create your own fragment filters, you will have to extend this class. + * + *

For more information about the usage and creation of effects, please have a look at + * the documentation of the parent class, "Effect".

+ * + * @see Effect + * @see MeshEffect + * @see starling.filters.FragmentFilter + */ + public class FilterEffect extends Effect + { + /** The vertex format expected by uploadVertexData: + * "position:float2, texCoords:float2" */ + public static const VERTEX_FORMAT:VertexDataFormat = + Effect.VERTEX_FORMAT.extend("texCoords:float2"); + + /** The AGAL code for the standard vertex shader that most filters will use. + * It simply transforms the vertex coordinates to clip-space and passes the texture + * coordinates to the fragment program (as 'v0'). */ + public static const STD_VERTEX_SHADER:String = + "m44 op, va0, vc0 \n"+ // 4x4 matrix transform to output clip-space + "mov v0, va1"; // pass texture coordinates to fragment program + + private var _texture:Texture; + private var _textureSmoothing:String; + private var _textureRepeat:Boolean; + + /** Creates a new FilterEffect instance. */ + public function FilterEffect() + { + _textureSmoothing = TextureSmoothing.BILINEAR; + } + + /** Override this method if the effect requires a different program depending on the + * current settings. Ideally, you do this by creating a bit mask encoding all the options. + * This method is called often, so do not allocate any temporary objects when overriding. + * + *

Reserve 4 bits for the variant name of the base class.

+ */ + override protected function get programVariantName():uint + { + return RenderUtil.getTextureVariantBits(_texture); + } + + /** @private */ + override protected function createProgram():Program + { + if (_texture) + { + var vertexShader:String = STD_VERTEX_SHADER; + var fragmentShader:String = tex("oc", "v0", 0, _texture); + return Program.fromSource(vertexShader, fragmentShader); + } + else + { + return super.createProgram(); + } + } + + /** This method is called by render, directly before + * context.drawTriangles. It activates the program and sets up + * the context with the following constants and attributes: + * + *
    + *
  • vc0-vc3 — MVP matrix
  • + *
  • va0 — vertex position (xy)
  • + *
  • va1 — texture coordinates (uv)
  • + *
  • fs0 — texture
  • + *
+ */ + override protected function beforeDraw(context:Context3D):void + { + super.beforeDraw(context); + + if (_texture) + { + var repeat:Boolean = _textureRepeat && _texture.root.isPotTexture; + RenderUtil.setSamplerStateAt(0, _texture.mipMapping, _textureSmoothing, repeat); + context.setTextureAt(0, _texture.base); + vertexFormat.setVertexBufferAt(1, vertexBuffer, "texCoords"); + } + } + + /** This method is called by render, directly after + * context.drawTriangles. Resets texture and vertex buffer attributes. */ + override protected function afterDraw(context:Context3D):void + { + if (_texture) + { + context.setTextureAt(0, null); + context.setVertexBufferAt(1, null); + } + + super.afterDraw(context); + } + + /** Creates an AGAL source string with a tex operation, including an options + * list with the appropriate format flag. This is just a convenience method forwarding + * to the respective RenderUtil method. + * + * @see starling.utils.RenderUtil#createAGALTexOperation() + */ + protected static function tex(resultReg:String, uvReg:String, sampler:int, texture:Texture, + convertToPmaIfRequired:Boolean=true):String + { + return RenderUtil.createAGALTexOperation(resultReg, uvReg, sampler, texture, + convertToPmaIfRequired); + } + + /** The data format that this effect requires from the VertexData that it renders: + * "position:float2, texCoords:float2" */ + override public function get vertexFormat():VertexDataFormat { return VERTEX_FORMAT; } + + /** The texture to be mapped onto the vertices. */ + public function get texture():Texture { return _texture; } + public function set texture(value:Texture):void { _texture = value; } + + /** The smoothing filter that is used for the texture. @default bilinear */ + public function get textureSmoothing():String { return _textureSmoothing; } + public function set textureSmoothing(value:String):void { _textureSmoothing = value; } + + /** Indicates if pixels at the edges will be repeated or clamped. + * Only works for power-of-two textures. @default false */ + public function get textureRepeat():Boolean { return _textureRepeat; } + public function set textureRepeat(value:Boolean):void { _textureRepeat = value; } + } +} diff --git a/mobile_version/src/starling/rendering/IndexData.as b/mobile_version/src/starling/rendering/IndexData.as new file mode 100644 index 00000000..4319d16e --- /dev/null +++ b/mobile_version/src/starling/rendering/IndexData.as @@ -0,0 +1,552 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.rendering +{ + import flash.display3D.Context3D; + import flash.display3D.IndexBuffer3D; + import flash.errors.EOFError; + import flash.utils.ByteArray; + import flash.utils.Endian; + + import starling.core.Starling; + import starling.errors.MissingContextError; + import starling.utils.StringUtil; + + /** The IndexData class manages a raw list of vertex indices, allowing direct upload + * to Stage3D index buffers. You only have to work with this class if you're writing + * your own rendering code (e.g. if you create custom display objects). + * + *

To render objects with Stage3D, you have to organize vertices and indices in so-called + * vertex- and index-buffers. Vertex buffers store the coordinates of the vertices that make + * up an object; index buffers reference those vertices to determine which vertices spawn + * up triangles. Those buffers reside in graphics memory and can be accessed very + * efficiently by the GPU.

+ * + *

Before you can move data into the buffers, you have to set it up in conventional + * memory — that is, in a Vector or a ByteArray. Since it's quite cumbersome to manually + * create and manipulate those data structures, the IndexData and VertexData classes provide + * a simple way to do just that. The data is stored in a ByteArray (one index or vertex after + * the other) that can easily be uploaded to a buffer.

+ * + * Basic Quad Layout + * + *

In many cases, the indices we are working with will reference just quads, i.e. + * triangles composing rectangles. That means that many IndexData instances will contain + * similar or identical data — a great opportunity for optimization!

+ * + *

If an IndexData instance follows a specific layout, it will be recognized + * automatically and many operations can be executed much faster. In Starling, that + * layout is called "basic quad layout". In order to recognize this specific sequence, + * the indices of each quad have to use the following order:

+ * + *
n, n+1, n+2, n+1, n+3, n+2
+ * + *

The subsequent quad has to use n+4 as starting value, the next one + * n+8, etc. Here is an example with 3 quads / 6 triangles:

+ * + *
0, 1, 2, 1, 3, 2,   4, 5, 6, 5, 7, 6,   8, 9, 10, 9, 11, 10
+ * + *

If you are describing quad-like meshes, make sure to always use this layout.

+ * + * @see VertexData + */ + public class IndexData + { + /** The number of bytes per index element. */ + private static const INDEX_SIZE:int = 2; + + private var _rawData:ByteArray; + private var _numIndices:int; + private var _initialCapacity:int; + private var _useQuadLayout:Boolean; + + // basic quad layout + private static var sQuadData:ByteArray = new ByteArray(); + private static var sQuadDataNumIndices:uint = 0; + + // helper objects + private static var sVector:Vector. = new []; + private static var sTrimData:ByteArray = new ByteArray(); + + /** Creates an empty IndexData instance with the given capacity (in indices). + * + * @param initialCapacity + * + * The initial capacity affects just the way the internal ByteArray is allocated, not the + * numIndices value, which will always be zero when the constructor returns. + * The reason for this behavior is the peculiar way in which ByteArrays organize their + * memory: + * + *

The first time you set the length of a ByteArray, it will adhere to that: + * a ByteArray with length 20 will take up 20 bytes (plus some overhead). When you change + * it to a smaller length, it will stick to the original value, e.g. with a length of 10 + * it will still take up 20 bytes. However, now comes the weird part: change it to + * anything above the original length, and it will allocate 4096 bytes!

+ * + *

Thus, be sure to always make a generous educated guess, depending on the planned + * usage of your IndexData instances.

+ */ + public function IndexData(initialCapacity:int=48) + { + _numIndices = 0; + _initialCapacity = initialCapacity; + _useQuadLayout = true; + } + + /** Explicitly frees up the memory used by the ByteArray, thus removing all indices. + * Quad layout will be restored (until adding data violating that layout). */ + public function clear():void + { + if (_rawData) + _rawData.clear(); + + _numIndices = 0; + _useQuadLayout = true; + } + + /** Creates a duplicate of the IndexData object. */ + public function clone():IndexData + { + var clone:IndexData = new IndexData(_numIndices); + + if (!_useQuadLayout) + { + clone.switchToGenericData(); + clone._rawData.writeBytes(_rawData); + } + + clone._numIndices = _numIndices; + return clone; + } + + /** Copies the index data (or a range of it, defined by 'indexID' and 'numIndices') + * of this instance to another IndexData object, starting at a certain target index. + * If the target is not big enough, it will grow to fit all the new indices. + * + *

By passing a non-zero offset, you can raise all copied indices + * by that value in the target object.

+ */ + public function copyTo(target:IndexData, targetIndexID:int=0, offset:int=0, + indexID:int=0, numIndices:int=-1):void + { + if (numIndices < 0 || indexID + numIndices > _numIndices) + numIndices = _numIndices - indexID; + + var sourceData:ByteArray, targetData:ByteArray; + var newNumIndices:int = targetIndexID + numIndices; + + if (target._numIndices < newNumIndices) + { + target._numIndices = newNumIndices; + + if (sQuadDataNumIndices < newNumIndices) + ensureQuadDataCapacity(newNumIndices); + } + + if (_useQuadLayout) + { + if (target._useQuadLayout) + { + var keepsQuadLayout:Boolean = true; + var distance:int = targetIndexID - indexID; + var distanceInQuads:int = distance / 6; + var offsetInQuads:int = offset / 4; + + // This code is executed very often. If it turns out that both IndexData + // instances use a quad layout, we don't need to do anything here. + // + // When "distance / 6 == offset / 4 && distance % 6 == 0 && offset % 4 == 0", + // the copy operation preserves the quad layout. In that case, we can exit + // right away. The code below is a little more complex, though, to avoid the + // (surprisingly costly) mod-operations. + + if (distanceInQuads == offsetInQuads && (offset & 3) == 0 && + distanceInQuads * 6 == distance) + { + keepsQuadLayout = true; + } + else if (numIndices > 2) + { + keepsQuadLayout = false; + } + else + { + for (var i:int=0; i offset % 4 == 0 + { + indexID += 6 * offset / 4; + offset = 0; + ensureQuadDataCapacity(indexID + numIndices); + } + } + else + { + if (target._useQuadLayout) + target.switchToGenericData(); + + sourceData = _rawData; + targetData = target._rawData; + } + + targetData.position = targetIndexID * INDEX_SIZE; + + if (offset == 0) + targetData.writeBytes(sourceData, indexID * INDEX_SIZE, numIndices * INDEX_SIZE); + else + { + sourceData.position = indexID * INDEX_SIZE; + + // by reading junks of 32 instead of 16 bits, we can spare half the time + while (numIndices > 1) + { + var indexAB:uint = sourceData.readUnsignedInt(); + var indexA:uint = ((indexAB & 0xffff0000) >> 16) + offset; + var indexB:uint = ((indexAB & 0x0000ffff) ) + offset; + targetData.writeUnsignedInt(indexA << 16 | indexB); + numIndices -= 2; + } + + if (numIndices) + targetData.writeShort(sourceData.readUnsignedShort() + offset); + } + } + + /** Sets an index at the specified position. */ + public function setIndex(indexID:int, index:uint):void + { + if (_numIndices < indexID + 1) + numIndices = indexID + 1; + + if (_useQuadLayout) + { + if (getBasicQuadIndexAt(indexID) == index) return; + else switchToGenericData(); + } + + _rawData.position = indexID * INDEX_SIZE; + _rawData.writeShort(index); + } + + /** Reads the index from the specified position. */ + public function getIndex(indexID:int):int + { + if (_useQuadLayout) + { + if (indexID < _numIndices) + return getBasicQuadIndexAt(indexID); + else + throw new EOFError(); + } + else + { + _rawData.position = indexID * INDEX_SIZE; + return _rawData.readUnsignedShort(); + } + } + + /** Adds an offset to all indices in the specified range. */ + public function offsetIndices(offset:int, indexID:int=0, numIndices:int=-1):void + { + if (numIndices < 0 || indexID + numIndices > _numIndices) + numIndices = _numIndices - indexID; + + var endIndex:int = indexID + numIndices; + + for (var i:int=indexID; i + * a - b + * | / | + * c - d + * + * + *

To make sure the indices will follow the basic quad layout, make sure each + * parameter increments the one before it (e.g. 0, 1, 2, 3).

+ */ + public function addQuad(a:uint, b:uint, c:uint, d:uint):void + { + if (_useQuadLayout) + { + if (a == getBasicQuadIndexAt(_numIndices) && + b == a + 1 && c == b + 1 && d == c + 1) + { + _numIndices += 6; + ensureQuadDataCapacity(_numIndices); + return; + } + else switchToGenericData(); + } + + _rawData.position = _numIndices * INDEX_SIZE; + _rawData.writeShort(a); + _rawData.writeShort(b); + _rawData.writeShort(c); + _rawData.writeShort(b); + _rawData.writeShort(d); + _rawData.writeShort(c); + _numIndices += 6; + } + + /** Creates a vector containing all indices. If you pass an existing vector to the method, + * its contents will be overwritten. */ + public function toVector(out:Vector.=null):Vector. + { + if (out == null) out = new Vector.(_numIndices); + else out.length = _numIndices; + + var rawData:ByteArray = _useQuadLayout ? sQuadData : _rawData; + rawData.position = 0; + + for (var i:int=0; i<_numIndices; ++i) + out[i] = rawData.readUnsignedShort(); + + return out; + } + + /** Returns a string representation of the IndexData object, + * including a comma-separated list of all indices. */ + public function toString():String + { + var string:String = StringUtil.format("[IndexData numIndices={0} indices=\"{1}\"]", + _numIndices, toVector(sVector).join()); + + sVector.length = 0; + return string; + } + + // private helpers + + private function switchToGenericData():void + { + if (_useQuadLayout) + { + _useQuadLayout = false; + + if (_rawData == null) + { + _rawData = new ByteArray(); + _rawData.endian = Endian.LITTLE_ENDIAN; + _rawData.length = _initialCapacity * INDEX_SIZE; // -> allocated memory + _rawData.length = _numIndices * INDEX_SIZE; // -> actual length + } + + if (_numIndices) + _rawData.writeBytes(sQuadData, 0, _numIndices * INDEX_SIZE); + } + } + + /** Makes sure that the ByteArray containing the normalized, basic quad data contains at + * least numIndices indices. The array might grow, but it will never be + * made smaller. */ + private function ensureQuadDataCapacity(numIndices:int):void + { + if (sQuadDataNumIndices >= numIndices) return; + + var i:int; + var oldNumQuads:int = sQuadDataNumIndices / 6; + var newNumQuads:int = Math.ceil(numIndices / 6); + + sQuadData.endian = Endian.LITTLE_ENDIAN; + sQuadData.position = sQuadData.length; + sQuadDataNumIndices = newNumQuads * 6; + + for (i = oldNumQuads; i < newNumQuads; ++i) + { + sQuadData.writeShort(4 * i); + sQuadData.writeShort(4 * i + 1); + sQuadData.writeShort(4 * i + 2); + sQuadData.writeShort(4 * i + 1); + sQuadData.writeShort(4 * i + 3); + sQuadData.writeShort(4 * i + 2); + } + } + + /** Returns the index that's expected at this position if following basic quad layout. */ + private static function getBasicQuadIndexAt(indexID:int):int + { + var quadID:int = indexID / 6; + var posInQuad:int = indexID - quadID * 6; // => indexID % 6 + var offset:int; + + if (posInQuad == 0) offset = 0; + else if (posInQuad == 1 || posInQuad == 3) offset = 1; + else if (posInQuad == 2 || posInQuad == 5) offset = 2; + else offset = 3; + + return quadID * 4 + offset; + } + + // IndexBuffer helpers + + /** Creates an index buffer object with the right size to fit the complete data. + * Optionally, the current data is uploaded right away. */ + public function createIndexBuffer(upload:Boolean=false, + bufferUsage:String="staticDraw"):IndexBuffer3D + { + var context:Context3D = Starling.context; + if (context == null) throw new MissingContextError(); + if (_numIndices == 0) return null; + + var buffer:IndexBuffer3D = context.createIndexBuffer(_numIndices, bufferUsage); + + if (upload) uploadToIndexBuffer(buffer); + return buffer; + } + + /** Uploads the complete data (or a section of it) to the given index buffer. */ + public function uploadToIndexBuffer(buffer:IndexBuffer3D, indexID:int=0, numIndices:int=-1):void + { + if (numIndices < 0 || indexID + numIndices > _numIndices) + numIndices = _numIndices - indexID; + + if (numIndices > 0) + buffer.uploadFromByteArray(rawData, 0, indexID, numIndices); + } + + /** Optimizes the ByteArray so that it has exactly the required capacity, without + * wasting any memory. If your IndexData object grows larger than the initial capacity + * you passed to the constructor, call this method to avoid the 4k memory problem. */ + public function trim():void + { + if (_useQuadLayout) return; + + sTrimData.length = _rawData.length; + sTrimData.position = 0; + sTrimData.writeBytes(_rawData); + + _rawData.clear(); + _rawData.length = sTrimData.length; + _rawData.writeBytes(sTrimData); + + sTrimData.clear(); + } + + // properties + + /** The total number of indices. + * + *

If this instance contains only standardized, basic quad indices, resizing + * will automatically fill up with appropriate quad indices. Otherwise, it will fill + * up with zeroes.

+ * + *

If you set the number of indices to zero, quad layout will be restored.

*/ + public function get numIndices():int { return _numIndices; } + public function set numIndices(value:int):void + { + if (value != _numIndices) + { + if (_useQuadLayout) ensureQuadDataCapacity(value); + else _rawData.length = value * INDEX_SIZE; + if (value == 0) _useQuadLayout = true; + + _numIndices = value; + } + } + + /** The number of triangles that can be spawned up with the contained indices. + * (In other words: the number of indices divided by three.) */ + public function get numTriangles():int { return _numIndices / 3; } + public function set numTriangles(value:int):void { numIndices = value * 3; } + + /** The number of quads that can be spawned up with the contained indices. + * (In other words: the number of triangles divided by two.) */ + public function get numQuads():int { return _numIndices / 6; } + public function set numQuads(value:int):void { numIndices = value * 6; } + + /** The number of bytes required for each index value. */ + public function get indexSizeInBytes():int { return INDEX_SIZE; } + + /** Indicates if all indices are following the basic quad layout. + * + *

This property is automatically updated if an index is set to a value that violates + * basic quad layout. Once the layout was violated, the instance will always stay that + * way, even if you fix that violating value later. Only calling clear or + * manually enabling the property will restore quad layout.

+ * + *

If you enable this property on an instance, all indices will immediately be + * replaced with indices following standard quad layout.

+ * + *

Please look at the class documentation for more information about that kind + * of layout, and why it is important.

+ * + * @default true + */ + public function get useQuadLayout():Boolean { return _useQuadLayout; } + public function set useQuadLayout(value:Boolean):void + { + if (value != _useQuadLayout) + { + if (value) + { + ensureQuadDataCapacity(_numIndices); + _rawData.length = 0; + _useQuadLayout = true; + } + else switchToGenericData(); + } + } + + /** The raw index data; not a copy! Beware: the referenced ByteArray may change any time. + * Never store a reference to it, and never modify its contents manually. */ + public function get rawData():ByteArray + { + if (_useQuadLayout) return sQuadData; + else return _rawData; + } + } +} diff --git a/mobile_version/src/starling/rendering/MeshEffect.as b/mobile_version/src/starling/rendering/MeshEffect.as new file mode 100644 index 00000000..69c5989b --- /dev/null +++ b/mobile_version/src/starling/rendering/MeshEffect.as @@ -0,0 +1,145 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.rendering +{ + import flash.display3D.Context3D; + import flash.display3D.Context3DProgramType; + import flash.utils.getQualifiedClassName; + + import starling.utils.RenderUtil; + + /** An effect drawing a mesh of textured, colored vertices. + * This is the standard effect that is the base for all mesh styles; + * if you want to create your own mesh styles, you will have to extend this class. + * + *

For more information about the usage and creation of effects, please have a look at + * the documentation of the root class, "Effect".

+ * + * @see Effect + * @see FilterEffect + * @see starling.styles.MeshStyle + */ + public class MeshEffect extends FilterEffect + { + /** The vertex format expected by uploadVertexData: + * "position:float2, texCoords:float2, color:bytes4" */ + public static const VERTEX_FORMAT:VertexDataFormat = + FilterEffect.VERTEX_FORMAT.extend("color:bytes4"); + + private var _alpha:Number; + private var _tinted:Boolean; + private var _optimizeIfNotTinted:Boolean; + + // helper objects + private static var sRenderAlpha:Vector. = new Vector.(4, true); + + /** Creates a new MeshEffect instance. */ + public function MeshEffect() + { + // Non-tinted meshes may be rendered with a simpler fragment shader, which brings + // a huge performance benefit on some low-end hardware. However, I don't want + // subclasses to become any more complicated because of this optimization (they + // probably use much longer shaders, anyway), so I only apply this optimization if + // this is actually the "MeshEffect" class. + + _alpha = 1.0; + _optimizeIfNotTinted = getQualifiedClassName(this) == "starling.rendering::MeshEffect"; + } + + /** @private */ + override protected function get programVariantName():uint + { + var noTinting:uint = uint(_optimizeIfNotTinted && !_tinted && _alpha == 1.0); + return super.programVariantName | (noTinting << 3); + } + + /** @private */ + override protected function createProgram():Program + { + var vertexShader:String, fragmentShader:String; + + if (texture) + { + if (_optimizeIfNotTinted && !_tinted && _alpha == 1.0) + return super.createProgram(); + + vertexShader = + "m44 op, va0, vc0 \n" + // 4x4 matrix transform to output clip-space + "mov v0, va1 \n" + // pass texture coordinates to fragment program + "mul v1, va2, vc4 \n"; // multiply alpha (vc4) with color (va2), pass to fp + + fragmentShader = + tex("ft0", "v0", 0, texture) + + "mul oc, ft0, v1 \n"; // multiply color with texel color + } + else + { + vertexShader = + "m44 op, va0, vc0 \n" + // 4x4 matrix transform to output clipspace + "mul v0, va2, vc4 \n"; // multiply alpha (vc4) with color (va2) + + fragmentShader = + "mov oc, v0 \n"; // output color + } + + return Program.fromSource(vertexShader, fragmentShader); + } + + /** This method is called by render, directly before + * context.drawTriangles. It activates the program and sets up + * the context with the following constants and attributes: + * + *
    + *
  • vc0-vc3 — MVP matrix
  • + *
  • vc4 — alpha value (same value for all components)
  • + *
  • va0 — vertex position (xy)
  • + *
  • va1 — texture coordinates (uv)
  • + *
  • va2 — vertex color (rgba), using premultiplied alpha
  • + *
  • fs0 — texture
  • + *
+ */ + override protected function beforeDraw(context:Context3D):void + { + super.beforeDraw(context); + + sRenderAlpha[0] = sRenderAlpha[1] = sRenderAlpha[2] = sRenderAlpha[3] = _alpha; + context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 4, sRenderAlpha); + + if (_tinted || _alpha != 1.0 || !_optimizeIfNotTinted || texture == null) + vertexFormat.setVertexBufferAt(2, vertexBuffer, "color"); + } + + /** This method is called by render, directly after + * context.drawTriangles. Resets texture and vertex buffer attributes. */ + override protected function afterDraw(context:Context3D):void + { + context.setVertexBufferAt(2, null); + + super.afterDraw(context); + } + + /** The data format that this effect requires from the VertexData that it renders: + * "position:float2, texCoords:float2, color:bytes4" */ + override public function get vertexFormat():VertexDataFormat { return VERTEX_FORMAT; } + + /** The alpha value of the object rendered by the effect. Must be taken into account + * by all subclasses. */ + public function get alpha():Number { return _alpha; } + public function set alpha(value:Number):void { _alpha = value; } + + /** Indicates if the rendered vertices are tinted in any way, i.e. if there are vertices + * that have a different color than fully opaque white. The base MeshEffect + * class uses this information to simplify the fragment shader if possible. May be + * ignored by subclasses. */ + public function get tinted():Boolean { return _tinted; } + public function set tinted(value:Boolean):void { _tinted = value; } + } +} diff --git a/mobile_version/src/starling/rendering/Painter.as b/mobile_version/src/starling/rendering/Painter.as new file mode 100644 index 00000000..2586e072 --- /dev/null +++ b/mobile_version/src/starling/rendering/Painter.as @@ -0,0 +1,909 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.rendering +{ + import flash.display.Stage3D; + import flash.display3D.Context3D; + import flash.display3D.Context3DCompareMode; + import flash.display3D.Context3DStencilAction; + import flash.display3D.Context3DTriangleFace; + import flash.display3D.textures.TextureBase; + import flash.errors.IllegalOperationError; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Rectangle; + import flash.geom.Vector3D; + import flash.utils.Dictionary; + + import starling.core.starling_internal; + import starling.display.BlendMode; + import starling.display.DisplayObject; + import starling.display.Mesh; + import starling.display.MeshBatch; + import starling.display.Quad; + import starling.events.Event; + import starling.textures.Texture; + import starling.utils.MathUtil; + import starling.utils.MatrixUtil; + import starling.utils.MeshSubset; + import starling.utils.Pool; + import starling.utils.RectangleUtil; + import starling.utils.RenderUtil; + import starling.utils.SystemUtil; + + use namespace starling_internal; + + /** A class that orchestrates rendering of all Starling display objects. + * + *

A Starling instance contains exactly one 'Painter' instance that should be used for all + * rendering purposes. Each frame, it is passed to the render methods of all rendered display + * objects. To access it outside a render method, call Starling.painter.

+ * + *

The painter is responsible for drawing all display objects to the screen. At its + * core, it is a wrapper for many Context3D methods, but that's not all: it also provides + * a convenient state mechanism, supports masking and acts as middleman between display + * objects and renderers.

+ * + * The State Stack + * + *

The most important concept of the Painter class is the state stack. A RenderState + * stores a combination of settings that are currently used for rendering, e.g. the current + * projection- and modelview-matrices and context-related settings. It can be accessed + * and manipulated via the state property. Use the methods + * pushState and popState to store a specific state and restore + * it later. That makes it easy to write rendering code that doesn't have any side effects.

+ * + * + * painter.pushState(); // save a copy of the current state on the stack + * painter.state.renderTarget = renderTexture; + * painter.state.transformModelviewMatrix(object.transformationMatrix); + * painter.state.alpha = 0.5; + * painter.prepareToDraw(); // apply all state settings at the render context + * drawSomething(); // insert Stage3D rendering code here + * painter.popState(); // restores previous state + * + * @see RenderState + */ + public class Painter + { + // the key for the programs stored in 'sharedData' + private static const PROGRAM_DATA_NAME:String = "starling.rendering.Painter.Programs"; + + // members + + private var _stage3D:Stage3D; + private var _context:Context3D; + private var _shareContext:Boolean; + private var _drawCount:int; + private var _frameID:uint; + private var _pixelSize:Number; + private var _enableErrorChecking:Boolean; + private var _stencilReferenceValues:Dictionary; + private var _clipRectStack:Vector.; + private var _batchCacheExclusions:Vector.; + + private var _batchProcessor:BatchProcessor; + private var _batchProcessorCurr:BatchProcessor; // current processor + private var _batchProcessorPrev:BatchProcessor; // previous processor (cache) + private var _batchProcessorSpec:BatchProcessor; // special processor (no cache) + + private var _actualRenderTarget:TextureBase; + private var _actualCulling:String; + private var _actualBlendMode:String; + + private var _backBufferWidth:Number; + private var _backBufferHeight:Number; + private var _backBufferScaleFactor:Number; + + private var _state:RenderState; + private var _stateStack:Vector.; + private var _stateStackPos:int; + private var _stateStackLength:int; + + // shared data + private static var sSharedData:Dictionary = new Dictionary(); + + // helper objects + private static var sMatrix:Matrix = new Matrix(); + private static var sPoint3D:Vector3D = new Vector3D(); + private static var sMatrix3D:Matrix3D = new Matrix3D(); + private static var sClipRect:Rectangle = new Rectangle(); + private static var sBufferRect:Rectangle = new Rectangle(); + private static var sScissorRect:Rectangle = new Rectangle(); + private static var sMeshSubset:MeshSubset = new MeshSubset(); + + // construction + + /** Creates a new Painter object. Normally, it's not necessary to create any custom + * painters; instead, use the global painter found on the Starling instance. */ + public function Painter(stage3D:Stage3D) + { + _stage3D = stage3D; + _stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreated, false, 40, true); + _context = _stage3D.context3D; + _shareContext = _context && _context.driverInfo != "Disposed"; + _backBufferWidth = _context ? _context.backBufferWidth : 0; + _backBufferHeight = _context ? _context.backBufferHeight : 0; + _backBufferScaleFactor = _pixelSize = 1.0; + _stencilReferenceValues = new Dictionary(true); + _clipRectStack = new []; + + _batchProcessorCurr = new BatchProcessor(); + _batchProcessorCurr.onBatchComplete = drawBatch; + + _batchProcessorPrev = new BatchProcessor(); + _batchProcessorPrev.onBatchComplete = drawBatch; + + _batchProcessorSpec = new BatchProcessor(); + _batchProcessorSpec.onBatchComplete = drawBatch; + + _batchProcessor = _batchProcessorCurr; + _batchCacheExclusions = new Vector.(); + + _state = new RenderState(); + _state.onDrawRequired = finishMeshBatch; + _stateStack = new []; + _stateStackPos = -1; + _stateStackLength = 0; + } + + /** Disposes all mesh batches, programs, and - if it is not being shared - + * the render context. */ + public function dispose():void + { + _batchProcessorCurr.dispose(); + _batchProcessorPrev.dispose(); + _batchProcessorSpec.dispose(); + + if (!_shareContext) + { + _context.dispose(false); + sSharedData = new Dictionary(); + } + } + + // context handling + + /** Requests a context3D object from the stage3D object. + * This is called by Starling internally during the initialization process. + * You normally don't need to call this method yourself. (For a detailed description + * of the parameters, look at the documentation of the method with the same name in the + * "RenderUtil" class.) + * + * @see starling.utils.RenderUtil + */ + public function requestContext3D(renderMode:String, profile:*):void + { + RenderUtil.requestContext3D(_stage3D, renderMode, profile); + } + + private function onContextCreated(event:Object):void + { + _context = _stage3D.context3D; + _context.enableErrorChecking = _enableErrorChecking; + _context.setDepthTest(false, Context3DCompareMode.ALWAYS); + + _actualBlendMode = null; + _actualCulling = null; + } + + /** Sets the viewport dimensions and other attributes of the rendering buffer. + * Starling will call this method internally, so most apps won't need to mess with this. + * + *

Beware: if shareContext is enabled, the method will only update the + * painter's context-related information (like the size of the back buffer), but won't + * make any actual changes to the context.

+ * + * @param viewPort the position and size of the area that should be rendered + * into, in pixels. + * @param contentScaleFactor only relevant for Desktop (!) HiDPI screens. If you want + * to support high resolutions, pass the 'contentScaleFactor' + * of the Flash stage; otherwise, '1.0'. + * @param antiAlias from 0 (none) to 16 (very high quality). + * @param enableDepthAndStencil indicates whether the depth and stencil buffers should + * be enabled. Note that on AIR, you also have to enable + * this setting in the app-xml (application descriptor); + * otherwise, this setting will be silently ignored. + */ + public function configureBackBuffer(viewPort:Rectangle, contentScaleFactor:Number, + antiAlias:int, enableDepthAndStencil:Boolean):void + { + if (!_shareContext) + { + enableDepthAndStencil &&= SystemUtil.supportsDepthAndStencil; + + // Changing the stage3D position might move the back buffer to invalid bounds + // temporarily. To avoid problems, we set it to the smallest possible size first. + + if (_context.profile == "baselineConstrained") + _context.configureBackBuffer(32, 32, antiAlias, enableDepthAndStencil); + + // If supporting HiDPI mode would exceed the maximum buffer size + // (can happen e.g in software mode), we stick to the low resolution. + + if (viewPort.width * contentScaleFactor > _context.maxBackBufferWidth || + viewPort.height * contentScaleFactor > _context.maxBackBufferHeight) + { + contentScaleFactor = 1.0; + } + + _stage3D.x = viewPort.x; + _stage3D.y = viewPort.y; + + _context.configureBackBuffer(viewPort.width, viewPort.height, + antiAlias, enableDepthAndStencil, contentScaleFactor != 1.0); + } + + _backBufferWidth = viewPort.width; + _backBufferHeight = viewPort.height; + _backBufferScaleFactor = contentScaleFactor; + } + + // program management + + /** Registers a program under a certain name. + * If the name was already used, the previous program is overwritten. */ + public function registerProgram(name:String, program:Program):void + { + deleteProgram(name); + programs[name] = program; + } + + /** Deletes the program of a certain name. */ + public function deleteProgram(name:String):void + { + var program:Program = getProgram(name); + if (program) + { + program.dispose(); + delete programs[name]; + } + } + + /** Returns the program registered under a certain name, or null if no program with + * this name has been registered. */ + public function getProgram(name:String):Program + { + return programs[name] as Program; + } + + /** Indicates if a program is registered under a certain name. */ + public function hasProgram(name:String):Boolean + { + return name in programs; + } + + // state stack + + /** Pushes the current render state to a stack from which it can be restored later. + * + *

If you pass a BatchToken, it will be updated to point to the current location within + * the render cache. That way, you can later reference this location to render a subset of + * the cache.

+ */ + public function pushState(token:BatchToken=null):void + { + _stateStackPos++; + + if (_stateStackLength < _stateStackPos + 1) _stateStack[_stateStackLength++] = new RenderState(); + if (token) _batchProcessor.fillToken(token); + + _stateStack[_stateStackPos].copyFrom(_state); + } + + /** Modifies the current state with a transformation matrix, alpha factor, and blend mode. + * + * @param transformationMatrix Used to transform the current modelviewMatrix. + * @param alphaFactor Multiplied with the current alpha value. + * @param blendMode Replaces the current blend mode; except for "auto", which + * means the current value remains unchanged. + */ + public function setStateTo(transformationMatrix:Matrix, alphaFactor:Number=1.0, + blendMode:String="auto"):void + { + if (transformationMatrix) MatrixUtil.prependMatrix(_state._modelviewMatrix, transformationMatrix); + if (alphaFactor != 1.0) _state._alpha *= alphaFactor; + if (blendMode != BlendMode.AUTO) _state.blendMode = blendMode; + } + + /** Restores the render state that was last pushed to the stack. If this changes + * blend mode, clipping rectangle, render target or culling, the current batch + * will be drawn right away. + * + *

If you pass a BatchToken, it will be updated to point to the current location within + * the render cache. That way, you can later reference this location to render a subset of + * the cache.

+ */ + public function popState(token:BatchToken=null):void + { + if (_stateStackPos < 0) + throw new IllegalOperationError("Cannot pop empty state stack"); + + _state.copyFrom(_stateStack[_stateStackPos]); // -> might cause 'finishMeshBatch' + _stateStackPos--; + + if (token) _batchProcessor.fillToken(token); + } + + // masks + + /** Draws a display object into the stencil buffer, incrementing the buffer on each + * used pixel. The stencil reference value is incremented as well; thus, any subsequent + * stencil tests outside of this area will fail. + * + *

If 'mask' is part of the display list, it will be drawn at its conventional stage + * coordinates. Otherwise, it will be drawn with the current modelview matrix.

+ * + *

As an optimization, this method might update the clipping rectangle of the render + * state instead of utilizing the stencil buffer. This is possible when the mask object + * is of type starling.display.Quad and is aligned parallel to the stage + * axes.

+ * + *

Note that masking breaks the render cache; the masked object must be redrawn anew + * in the next frame. If you pass maskee, the method will automatically + * call excludeFromCache(maskee) for you.

+ */ + public function drawMask(mask:DisplayObject, maskee:DisplayObject=null):void + { + if (_context == null) return; + + finishMeshBatch(); + + if (isRectangularMask(mask, maskee, sMatrix)) + { + mask.getBounds(mask, sClipRect); + RectangleUtil.getBounds(sClipRect, sMatrix, sClipRect); + pushClipRect(sClipRect); + } + else + { + _context.setStencilActions(Context3DTriangleFace.FRONT_AND_BACK, + Context3DCompareMode.EQUAL, Context3DStencilAction.INCREMENT_SATURATE); + + renderMask(mask); + stencilReferenceValue++; + + _context.setStencilActions(Context3DTriangleFace.FRONT_AND_BACK, + Context3DCompareMode.EQUAL, Context3DStencilAction.KEEP); + } + + excludeFromCache(maskee); + } + + /** Draws a display object into the stencil buffer, decrementing the + * buffer on each used pixel. This effectively erases the object from the stencil buffer, + * restoring the previous state. The stencil reference value will be decremented. + * + *

Note: if the mask object meets the requirements of using the clipping rectangle, + * it will be assumed that this erase operation undoes the clipping rectangle change + * caused by the corresponding drawMask() call.

+ */ + public function eraseMask(mask:DisplayObject, maskee:DisplayObject=null):void + { + if (_context == null) return; + + finishMeshBatch(); + + if (isRectangularMask(mask, maskee, sMatrix)) + { + popClipRect(); + } + else + { + _context.setStencilActions(Context3DTriangleFace.FRONT_AND_BACK, + Context3DCompareMode.EQUAL, Context3DStencilAction.DECREMENT_SATURATE); + + renderMask(mask); + stencilReferenceValue--; + + _context.setStencilActions(Context3DTriangleFace.FRONT_AND_BACK, + Context3DCompareMode.EQUAL, Context3DStencilAction.KEEP); + } + } + + private function renderMask(mask:DisplayObject):void + { + var wasCacheEnabled:Boolean = cacheEnabled; + + pushState(); + cacheEnabled = false; + _state.alpha = 0.0; + + var matrix:Matrix = null; + var matrix3D:Matrix3D = null; + + if (mask.stage) + { + _state.setModelviewMatricesToIdentity(); + + if (mask.is3D) matrix3D = mask.getTransformationMatrix3D(null, sMatrix3D); + else matrix = mask.getTransformationMatrix(null, sMatrix); + } + else + { + if (mask.is3D) matrix3D = mask.transformationMatrix3D; + else matrix = mask.transformationMatrix; + } + + if (matrix3D) _state.transformModelviewMatrix3D(matrix3D); + else _state.transformModelviewMatrix(matrix); + + mask.render(this); + finishMeshBatch(); + + cacheEnabled = wasCacheEnabled; + popState(); + } + + private function pushClipRect(clipRect:Rectangle):void + { + var stack:Vector. = _clipRectStack; + var stackLength:uint = stack.length; + var intersection:Rectangle = Pool.getRectangle(); + + if (stackLength) + RectangleUtil.intersect(stack[stackLength - 1], clipRect, intersection); + else + intersection.copyFrom(clipRect); + + stack[stackLength] = intersection; + _state.clipRect = intersection; + } + + private function popClipRect():void + { + var stack:Vector. = _clipRectStack; + var stackLength:uint = stack.length; + + if (stackLength == 0) + throw new Error("Trying to pop from empty clip rectangle stack"); + + stackLength--; + Pool.putRectangle(stack.pop()); + _state.clipRect = stackLength ? stack[stackLength - 1] : null; + } + + /** Figures out if the mask can be represented by a scissor rectangle; this is possible + * if it's just a simple (untextured) quad that is parallel to the stage axes. The 'out' + * parameter will be filled with the transformation matrix required to move the mask into + * stage coordinates. */ + private function isRectangularMask(mask:DisplayObject, maskee:DisplayObject, out:Matrix):Boolean + { + var quad:Quad = mask as Quad; + var is3D:Boolean = mask.is3D || (maskee && maskee.is3D && mask.stage == null); + + if (quad && !is3D && quad.texture == null) + { + if (mask.stage) mask.getTransformationMatrix(null, out); + else + { + out.copyFrom(mask.transformationMatrix); + out.concat(_state.modelviewMatrix); + } + + return (MathUtil.isEquivalent(out.a, 0) && MathUtil.isEquivalent(out.d, 0)) || + (MathUtil.isEquivalent(out.b, 0) && MathUtil.isEquivalent(out.c, 0)); + } + return false; + } + + // mesh rendering + + /** Adds a mesh to the current batch of unrendered meshes. If the current batch is not + * compatible with the mesh, all previous meshes are rendered at once and the batch + * is cleared. + * + * @param mesh The mesh to batch. + * @param subset The range of vertices to be batched. If null, the complete + * mesh will be used. + */ + public function batchMesh(mesh:Mesh, subset:MeshSubset=null):void + { + _batchProcessor.addMesh(mesh, _state, subset); + } + + /** Finishes the current mesh batch and prepares the next one. */ + public function finishMeshBatch():void + { + _batchProcessor.finishBatch(); + } + + /** Completes all unfinished batches, cleanup procedures. */ + public function finishFrame():void + { + if (_frameID % 99 == 0) _batchProcessorCurr.trim(); // odd number -> alternating processors + if (_frameID % 150 == 0) _batchProcessorSpec.trim(); + + _batchProcessor.finishBatch(); + _batchProcessor = _batchProcessorSpec; // no cache between frames + processCacheExclusions(); + } + + private function processCacheExclusions():void + { + var i:int, length:int = _batchCacheExclusions.length; + for (i=0; istartToken and + * (but not including) endToken. The render cache contains all meshes + * rendered in the previous frame. */ + public function drawFromCache(startToken:BatchToken, endToken:BatchToken):void + { + var meshBatch:MeshBatch; + var subset:MeshSubset = sMeshSubset; + + if (!startToken.equals(endToken)) + { + pushState(); + + for (var i:int = startToken.batchID; i <= endToken.batchID; ++i) + { + meshBatch = _batchProcessorPrev.getBatchAt(i); + subset.setTo(); // resets subset + + if (i == startToken.batchID) + { + subset.vertexID = startToken.vertexID; + subset.indexID = startToken.indexID; + subset.numVertices = meshBatch.numVertices - subset.vertexID; + subset.numIndices = meshBatch.numIndices - subset.indexID; + } + + if (i == endToken.batchID) + { + subset.numVertices = endToken.vertexID - subset.vertexID; + subset.numIndices = endToken.indexID - subset.indexID; + } + + if (subset.numVertices) + { + _state.alpha = 1.0; + _state.blendMode = meshBatch.blendMode; + _batchProcessor.addMesh(meshBatch, _state, subset, true); + } + } + + popState(); + } + } + + /** Prevents the object from being drawn from the render cache in the next frame. + * Different to setRequiresRedraw(), this does not indicate that the object + * has changed in any way, but just that it doesn't support being drawn from cache. + * + *

Note that when a container is excluded from the render cache, its children will + * still be cached! This just means that batching is interrupted at this object when + * the display tree is traversed.

+ */ + public function excludeFromCache(object:DisplayObject):void + { + if (object) _batchCacheExclusions[_batchCacheExclusions.length] = object; + } + + private function drawBatch(meshBatch:MeshBatch):void + { + pushState(); + + state.blendMode = meshBatch.blendMode; + state.modelviewMatrix.identity(); + state.alpha = 1.0; + + meshBatch.render(this); + + popState(); + } + + // helper methods + + /** Applies all relevant state settings to at the render context. This includes + * blend mode, render target and clipping rectangle. Always call this method before + * context.drawTriangles(). + */ + public function prepareToDraw():void + { + applyBlendMode(); + applyRenderTarget(); + applyClipRect(); + applyCulling(); + } + + /** Clears the render context with a certain color and alpha value. Since this also + * clears the stencil buffer, the stencil reference value is also reset to '0'. */ + public function clear(rgb:uint=0, alpha:Number=0.0):void + { + applyRenderTarget(); + stencilReferenceValue = 0; + RenderUtil.clear(rgb, alpha); + } + + /** Resets the render target to the back buffer and displays its contents. */ + public function present():void + { + _state.renderTarget = null; + _actualRenderTarget = null; + _context.present(); + } + + private function applyBlendMode():void + { + var blendMode:String = _state.blendMode; + + if (blendMode != _actualBlendMode) + { + BlendMode.get(_state.blendMode).activate(); + _actualBlendMode = blendMode; + } + } + + private function applyCulling():void + { + var culling:String = _state.culling; + + if (culling != _actualCulling) + { + _context.setCulling(culling); + _actualCulling = culling; + } + } + + private function applyRenderTarget():void + { + var target:TextureBase = _state.renderTargetBase; + + if (target != _actualRenderTarget) + { + if (target) + { + var antiAlias:int = _state.renderTargetAntiAlias; + var depthAndStencil:Boolean = _state.renderTargetSupportsDepthAndStencil; + _context.setRenderToTexture(target, depthAndStencil, antiAlias); + } + else + _context.setRenderToBackBuffer(); + + _context.setStencilReferenceValue(stencilReferenceValue); + _actualRenderTarget = target; + } + } + + private function applyClipRect():void + { + var clipRect:Rectangle = _state.clipRect; + + if (clipRect) + { + var width:int, height:int; + var projMatrix:Matrix3D = _state.projectionMatrix3D; + var renderTarget:Texture = _state.renderTarget; + + if (renderTarget) + { + width = renderTarget.root.nativeWidth; + height = renderTarget.root.nativeHeight; + } + else + { + width = _backBufferWidth; + height = _backBufferHeight; + } + + // convert to pixel coordinates (matrix transformation ends up in range [-1, 1]) + MatrixUtil.transformCoords3D(projMatrix, clipRect.x, clipRect.y, 0.0, sPoint3D); + sPoint3D.project(); // eliminate w-coordinate + sClipRect.x = (sPoint3D.x * 0.5 + 0.5) * width; + sClipRect.y = (0.5 - sPoint3D.y * 0.5) * height; + + MatrixUtil.transformCoords3D(projMatrix, clipRect.right, clipRect.bottom, 0.0, sPoint3D); + sPoint3D.project(); // eliminate w-coordinate + sClipRect.right = (sPoint3D.x * 0.5 + 0.5) * width; + sClipRect.bottom = (0.5 - sPoint3D.y * 0.5) * height; + + sBufferRect.setTo(0, 0, width, height); + RectangleUtil.intersect(sClipRect, sBufferRect, sScissorRect); + + // an empty rectangle is not allowed, so we set it to the smallest possible size + if (sScissorRect.width < 1 || sScissorRect.height < 1) + sScissorRect.setTo(0, 0, 1, 1); + + _context.setScissorRectangle(sScissorRect); + } + else + { + _context.setScissorRectangle(null); + } + } + + // properties + + /** Indicates the number of stage3D draw calls. */ + public function get drawCount():int { return _drawCount; } + public function set drawCount(value:int):void { _drawCount = value; } + + /** The current stencil reference value of the active render target. This value + * is typically incremented when drawing a mask and decrementing when erasing it. + * The painter keeps track of one stencil reference value per render target. + * Only change this value if you know what you're doing! + */ + public function get stencilReferenceValue():uint + { + var key:Object = _state.renderTarget ? _state.renderTargetBase : this; + if (key in _stencilReferenceValues) return _stencilReferenceValues[key]; + else return 0; + } + + public function set stencilReferenceValue(value:uint):void + { + var key:Object = _state.renderTarget ? _state.renderTargetBase : this; + _stencilReferenceValues[key] = value; + + if (contextValid) + _context.setStencilReferenceValue(value); + } + + /** Indicates if the render cache is enabled. Normally, this should be left at the default; + * however, some custom rendering logic might require to change this property temporarily. + * Also note that the cache is automatically reactivated each frame, right before the + * render process. + * + * @default true + */ + public function get cacheEnabled():Boolean { return _batchProcessor == _batchProcessorCurr; } + public function set cacheEnabled(value:Boolean):void + { + if (value != cacheEnabled) + { + finishMeshBatch(); + + if (value) _batchProcessor = _batchProcessorCurr; + else _batchProcessor = _batchProcessorSpec; + } + } + + /** The current render state, containing some of the context settings, projection- and + * modelview-matrix, etc. Always returns the same instance, even after calls to "pushState" + * and "popState". + * + *

When you change the current RenderState, and this change is not compatible with + * the current render batch, the batch will be concluded right away. Thus, watch out + * for changes of blend mode, clipping rectangle, render target or culling.

+ */ + public function get state():RenderState { return _state; } + + /** The Stage3D instance this painter renders into. */ + public function get stage3D():Stage3D { return _stage3D; } + + /** The Context3D instance this painter renders into. */ + public function get context():Context3D { return _context; } + + /** Returns the index of the current frame if the render cache is enabled; + * otherwise, returns zero. To get the frameID regardless of the render cache, call + * Starling.frameID instead. */ + public function set frameID(value:uint):void { _frameID = value; } + public function get frameID():uint + { + return _batchProcessor == _batchProcessorCurr ? _frameID : 0; + } + + /** The size (in points) that represents one pixel in the back buffer. */ + public function get pixelSize():Number { return _pixelSize; } + public function set pixelSize(value:Number):void { _pixelSize = value; } + + /** Indicates if another Starling instance (or another Stage3D framework altogether) + * uses the same render context. @default false */ + public function get shareContext():Boolean { return _shareContext; } + public function set shareContext(value:Boolean):void { _shareContext = value; } + + /** Indicates if Stage3D render methods will report errors. Activate only when needed, + * as this has a negative impact on performance. @default false */ + public function get enableErrorChecking():Boolean { return _enableErrorChecking; } + public function set enableErrorChecking(value:Boolean):void + { + _enableErrorChecking = value; + if (_context) _context.enableErrorChecking = value; + } + + /** Returns the current width of the back buffer. In most cases, this value is in pixels; + * however, if the app is running on an HiDPI display with an activated + * 'supportHighResolutions' setting, you have to multiply with 'backBufferPixelsPerPoint' + * for the actual pixel count. Alternatively, use the Context3D-property with the + * same name: it will return the exact pixel values. */ + public function get backBufferWidth():int { return _backBufferWidth; } + + /** Returns the current height of the back buffer. In most cases, this value is in pixels; + * however, if the app is running on an HiDPI display with an activated + * 'supportHighResolutions' setting, you have to multiply with 'backBufferPixelsPerPoint' + * for the actual pixel count. Alternatively, use the Context3D-property with the + * same name: it will return the exact pixel values. */ + public function get backBufferHeight():int { return _backBufferHeight; } + + /** The number of pixels per point returned by the 'backBufferWidth/Height' properties. + * Except for desktop HiDPI displays with an activated 'supportHighResolutions' setting, + * this will always return '1'. */ + public function get backBufferScaleFactor():Number { return _backBufferScaleFactor; } + + /** Indicates if the Context3D object is currently valid (i.e. it hasn't been lost or + * disposed). */ + public function get contextValid():Boolean + { + if (_context) + { + const driverInfo:String = _context.driverInfo; + return driverInfo != null && driverInfo != "" && driverInfo != "Disposed"; + } + else return false; + } + + /** The Context3D profile of the current render context, or null + * if the context has not been created yet. */ + public function get profile():String + { + if (_context) return _context.profile; + else return null; + } + + /** A dictionary that can be used to save custom data related to the render context. + * If you need to share data that is bound to the render context (e.g. textures), use + * this dictionary instead of creating a static class variable. That way, the data will + * be available for all Starling instances that use this stage3D / context. */ + public function get sharedData():Dictionary + { + var data:Dictionary = sSharedData[stage3D] as Dictionary; + if (data == null) + { + data = new Dictionary(); + sSharedData[stage3D] = data; + } + return data; + } + + private function get programs():Dictionary + { + var programs:Dictionary = sharedData[PROGRAM_DATA_NAME] as Dictionary; + if (programs == null) + { + programs = new Dictionary(); + sharedData[PROGRAM_DATA_NAME] = programs; + } + return programs; + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/rendering/Program.as b/mobile_version/src/starling/rendering/Program.as new file mode 100644 index 00000000..0f0e0792 --- /dev/null +++ b/mobile_version/src/starling/rendering/Program.as @@ -0,0 +1,104 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.rendering +{ + import com.adobe.utils.AGALMiniAssembler; + + import flash.display3D.Context3D; + import flash.display3D.Context3DProgramType; + import flash.display3D.Program3D; + import flash.events.Event; + import flash.utils.ByteArray; + + import starling.core.Starling; + import starling.errors.MissingContextError; + + /** A Program represents a pair of a fragment- and vertex-shader. + * + *

This class is a convenient replacement for Stage3Ds "Program3D" class. Its main + * advantage is that it survives a context loss; furthermore, it makes it simple to + * create a program from AGAL source without having to deal with the assembler.

+ * + *

It is recommended to store programs in Starling's "Painter" instance via the methods + * registerProgram and getProgram. That way, your programs may + * be shared among different display objects or even Starling instances.

+ * + * @see Painter + */ + public class Program + { + private var _vertexShader:ByteArray; + private var _fragmentShader:ByteArray; + private var _program3D:Program3D; + + private static var sAssembler:AGALMiniAssembler = new AGALMiniAssembler(); + + /** Creates a program from the given AGAL (Adobe Graphics Assembly Language) bytecode. */ + public function Program(vertexShader:ByteArray, fragmentShader:ByteArray) + { + _vertexShader = vertexShader; + _fragmentShader = fragmentShader; + + // Handle lost context (using conventional Flash event for weak listener support) + Starling.current.stage3D.addEventListener(Event.CONTEXT3D_CREATE, + onContextCreated, false, 30, true); + } + + /** Disposes the internal Program3D instance. */ + public function dispose():void + { + Starling.current.stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated); + disposeProgram(); + } + + /** Creates a new Program instance from AGAL assembly language. */ + public static function fromSource(vertexShader:String, fragmentShader:String, + agalVersion:uint=1):Program + { + return new Program( + sAssembler.assemble(Context3DProgramType.VERTEX, vertexShader, agalVersion), + sAssembler.assemble(Context3DProgramType.FRAGMENT, fragmentShader, agalVersion)); + } + + /** Activates the program on the given context. If you don't pass a context, the current + * Starling context will be used. */ + public function activate(context:Context3D=null):void + { + if (context == null) + { + context = Starling.context; + if (context == null) throw new MissingContextError(); + } + + if (_program3D == null) + { + _program3D = context.createProgram(); + _program3D.upload(_vertexShader, _fragmentShader); + } + + context.setProgram(_program3D); + } + + private function onContextCreated(event:Event):void + { + disposeProgram(); + } + + private function disposeProgram():void + { + if (_program3D) + { + _program3D.dispose(); + _program3D = null; + } + } + } +} diff --git a/mobile_version/src/starling/rendering/RenderState.as b/mobile_version/src/starling/rendering/RenderState.as new file mode 100644 index 00000000..b09d6dd0 --- /dev/null +++ b/mobile_version/src/starling/rendering/RenderState.as @@ -0,0 +1,400 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.rendering +{ + import flash.display3D.Context3DTriangleFace; + import flash.display3D.textures.TextureBase; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Rectangle; + import flash.geom.Vector3D; + + import starling.display.BlendMode; + import starling.textures.Texture; + import starling.utils.MathUtil; + import starling.utils.MatrixUtil; + import starling.utils.Pool; + import starling.utils.RectangleUtil; + + /** The RenderState stores a combination of settings that are currently used for rendering. + * This includes modelview and transformation matrices as well as context3D related settings. + * + *

Starling's Painter instance stores a reference to the current RenderState. + * Via a stack mechanism, you can always save a specific state and restore it later. + * That makes it easy to write rendering code that doesn't have any side effects.

+ * + *

Beware that any context-related settings are not applied on the context + * right away, but only after calling painter.prepareToDraw(). + * However, the Painter recognizes changes to those settings and will finish the current + * batch right away if necessary.

+ * + * Matrix Magic + * + *

On rendering, Starling traverses the display tree, constantly moving from one + * coordinate system to the next. Each display object stores its vertex coordinates + * in its local coordinate system; on rendering, they must be moved to a global, + * 2D coordinate space (the so-called "clip-space"). To handle these calculations, + * the RenderState contains a set of matrices.

+ * + *

By multiplying vertex coordinates with the modelviewMatrix, you'll get the + * coordinates in "screen-space", or in other words: in stage coordinates. (Optionally, + * there's also a 3D version of this matrix. It comes into play when you're working with + * Sprite3D containers.)

+ * + *

By feeding the result of the previous transformation into the + * projectionMatrix, you'll end up with so-called "clipping coordinates", + * which are in the range [-1, 1] (just as needed by the graphics pipeline). + * If you've got vertices in the 3D space, this matrix will also execute a perspective + * projection.

+ * + *

Finally, there's the mvpMatrix, which is short for + * "modelviewProjectionMatrix". This is simply a combination of modelview- and + * projectionMatrix, combining the effects of both. Pass this matrix + * to the vertex shader and all your vertices will automatically end up at the right + * position.

+ * + * @see Painter + * @see starling.display.Sprite3D + */ + public class RenderState + { + /** @private */ internal var _alpha:Number; + /** @private */ internal var _blendMode:String; + /** @private */ internal var _modelviewMatrix:Matrix; + + private static const CULLING_VALUES:Vector. = new + [Context3DTriangleFace.NONE, Context3DTriangleFace.FRONT, + Context3DTriangleFace.BACK, Context3DTriangleFace.FRONT_AND_BACK]; + + private var _miscOptions:uint; + private var _clipRect:Rectangle; + private var _renderTarget:Texture; + private var _onDrawRequired:Function; + private var _modelviewMatrix3D:Matrix3D; + private var _projectionMatrix3D:Matrix3D; + private var _projectionMatrix3DRev:uint; + private var _mvpMatrix3D:Matrix3D; + + // helper objects + private static var sMatrix3D:Matrix3D = new Matrix3D(); + private static var sProjectionMatrix3DRev:uint = 0; + + /** Creates a new render state with the default settings. */ + public function RenderState() + { + reset(); + } + + /** Duplicates all properties of another instance on the current instance. */ + public function copyFrom(renderState:RenderState):void + { + if (_onDrawRequired != null) + { + var currentTarget:TextureBase = _renderTarget ? _renderTarget.base : null; + var nextTarget:TextureBase = renderState._renderTarget ? renderState._renderTarget.base : null; + var cullingChanges:Boolean = (_miscOptions & 0xf00) != (renderState._miscOptions & 0xf00); + var clipRectChanges:Boolean = _clipRect || renderState._clipRect ? + !RectangleUtil.compare(_clipRect, renderState._clipRect) : false; + + if (_blendMode != renderState._blendMode || + currentTarget != nextTarget || clipRectChanges || cullingChanges) + { + _onDrawRequired(); + } + } + + _alpha = renderState._alpha; + _blendMode = renderState._blendMode; + _renderTarget = renderState._renderTarget; + _miscOptions = renderState._miscOptions; + _modelviewMatrix.copyFrom(renderState._modelviewMatrix); + + if (_projectionMatrix3DRev != renderState._projectionMatrix3DRev) + { + _projectionMatrix3DRev = renderState._projectionMatrix3DRev; + _projectionMatrix3D.copyFrom(renderState._projectionMatrix3D); + } + + if (_modelviewMatrix3D || renderState._modelviewMatrix3D) + this.modelviewMatrix3D = renderState._modelviewMatrix3D; + + if (_clipRect || renderState._clipRect) + this.clipRect = renderState._clipRect; + } + + /** Resets the RenderState to the default settings. + * (Check each property documentation for its default value.) */ + public function reset():void + { + this.alpha = 1.0; + this.blendMode = BlendMode.NORMAL; + this.culling = Context3DTriangleFace.NONE; + this.modelviewMatrix3D = null; + this.renderTarget = null; + this.clipRect = null; + _projectionMatrix3DRev = 0; + + if (_modelviewMatrix) _modelviewMatrix.identity(); + else _modelviewMatrix = new Matrix(); + + if (_projectionMatrix3D) _projectionMatrix3D.identity(); + else _projectionMatrix3D = new Matrix3D(); + + if (_mvpMatrix3D == null) _mvpMatrix3D = new Matrix3D(); + } + + // matrix methods / properties + + /** Prepends the given matrix to the 2D modelview matrix. */ + public function transformModelviewMatrix(matrix:Matrix):void + { + MatrixUtil.prependMatrix(_modelviewMatrix, matrix); + } + + /** Prepends the given matrix to the 3D modelview matrix. + * The current contents of the 2D modelview matrix is stored in the 3D modelview matrix + * before doing so; the 2D modelview matrix is then reset to the identity matrix. + */ + public function transformModelviewMatrix3D(matrix:Matrix3D):void + { + if (_modelviewMatrix3D == null) + _modelviewMatrix3D = Pool.getMatrix3D(); + + _modelviewMatrix3D.prepend(MatrixUtil.convertTo3D(_modelviewMatrix, sMatrix3D)); + _modelviewMatrix3D.prepend(matrix); + _modelviewMatrix.identity(); + } + + /** Creates a perspective projection matrix suitable for 2D and 3D rendering. + * + *

The first 4 parameters define which area of the stage you want to view (the camera + * will 'zoom' to exactly this region). The final 3 parameters determine the perspective + * in which you're looking at the stage.

+ * + *

The stage is always on the rectangle that is spawned up between x- and y-axis (with + * the given size). All objects that are exactly on that rectangle (z equals zero) will be + * rendered in their true size, without any distortion.

+ * + *

If you pass only the first 4 parameters, the camera will be set up above the center + * of the stage, with a field of view of 1.0 rad.

+ */ + public function setProjectionMatrix(x:Number, y:Number, width:Number, height:Number, + stageWidth:Number=0, stageHeight:Number=0, + cameraPos:Vector3D=null):void + { + _projectionMatrix3DRev = ++sProjectionMatrix3DRev; + MatrixUtil.createPerspectiveProjectionMatrix( + x, y, width, height, stageWidth, stageHeight, cameraPos, _projectionMatrix3D); + } + + /** This method needs to be called whenever projectionMatrix3D was changed + * other than via setProjectionMatrix. + */ + public function setProjectionMatrixChanged():void + { + _projectionMatrix3DRev = ++sProjectionMatrix3DRev; + } + + /** Changes the modelview matrices (2D and, if available, 3D) to identity matrices. + * An object transformed an identity matrix performs no transformation. + */ + public function setModelviewMatricesToIdentity():void + { + _modelviewMatrix.identity(); + if (_modelviewMatrix3D) _modelviewMatrix3D.identity(); + } + + /** Returns the current 2D modelview matrix. + * CAUTION: Use with care! Each call returns the same instance. + * @default identity matrix */ + public function get modelviewMatrix():Matrix { return _modelviewMatrix; } + public function set modelviewMatrix(value:Matrix):void { _modelviewMatrix.copyFrom(value); } + + /** Returns the current 3D modelview matrix, if there have been 3D transformations. + * CAUTION: Use with care! Each call returns the same instance. + * @default null */ + public function get modelviewMatrix3D():Matrix3D { return _modelviewMatrix3D; } + public function set modelviewMatrix3D(value:Matrix3D):void + { + if (value) + { + if (_modelviewMatrix3D == null) _modelviewMatrix3D = Pool.getMatrix3D(false); + _modelviewMatrix3D.copyFrom(value); + } + else if (_modelviewMatrix3D) + { + Pool.putMatrix3D(_modelviewMatrix3D); + _modelviewMatrix3D = null; + } + } + + /** Returns the current projection matrix. You can use the method 'setProjectionMatrix3D' + * to set it up in an intuitive way. + * CAUTION: Use with care! Each call returns the same instance. If you modify the matrix + * in place, you have to call setProjectionMatrixChanged. + * @default identity matrix */ + public function get projectionMatrix3D():Matrix3D { return _projectionMatrix3D; } + public function set projectionMatrix3D(value:Matrix3D):void + { + setProjectionMatrixChanged(); + _projectionMatrix3D.copyFrom(value); + } + + /** Calculates the product of modelview and projection matrix and stores it in a 3D matrix. + * CAUTION: Use with care! Each call returns the same instance. */ + public function get mvpMatrix3D():Matrix3D + { + _mvpMatrix3D.copyFrom(_projectionMatrix3D); + if (_modelviewMatrix3D) _mvpMatrix3D.prepend(_modelviewMatrix3D); + _mvpMatrix3D.prepend(MatrixUtil.convertTo3D(_modelviewMatrix, sMatrix3D)); + return _mvpMatrix3D; + } + + // other methods + + /** Changes the the current render target. + * + * @param target Either a texture or null to render into the back buffer. + * @param enableDepthAndStencil Indicates if depth and stencil testing will be available. + * This parameter affects only texture targets. + * @param antiAlias The anti-aliasing quality (range: 0 - 16). + * This parameter affects only texture targets. Note that at the time + * of this writing, AIR supports anti-aliasing only on Desktop. + */ + public function setRenderTarget(target:Texture, enableDepthAndStencil:Boolean=true, + antiAlias:int=0):void + { + var currentTarget:TextureBase = _renderTarget ? _renderTarget.base : null; + var newTarget:TextureBase = target ? target.base : null; + var newOptions:uint = MathUtil.min(antiAlias, 16) | uint(enableDepthAndStencil) << 4; + var optionsChange:Boolean = newOptions != (_miscOptions & 0xff); + + if (currentTarget != newTarget || optionsChange) + { + if (_onDrawRequired != null) _onDrawRequired(); + + _renderTarget = target; + _miscOptions = (_miscOptions & 0xffffff00) | newOptions; + } + } + + // other properties + + /** The current, cumulated alpha value. Beware that, in a standard 'render' method, + * this already includes the current object! The value is the product of current object's + * alpha value and all its parents. @default 1.0 + */ + public function get alpha():Number { return _alpha; } + public function set alpha(value:Number):void { _alpha = value; } + + /** The blend mode to be used on rendering. A value of "auto" is ignored, since it + * means that the mode should remain unchanged. + * + * @default BlendMode.NORMAL + * @see starling.display.BlendMode + */ + public function get blendMode():String { return _blendMode; } + public function set blendMode(value:String):void + { + if (value != BlendMode.AUTO && _blendMode != value) + { + if (_onDrawRequired != null) _onDrawRequired(); + _blendMode = value; + } + } + + /** The texture that is currently being rendered into, or null + * to render into the back buffer. On assignment, calls setRenderTarget + * with its default parameters. */ + public function get renderTarget():Texture { return _renderTarget; } + public function set renderTarget(value:Texture):void { setRenderTarget(value); } + + /** @private */ + internal function get renderTargetBase():TextureBase + { + return _renderTarget ? _renderTarget.base : null; + } + + /** Sets the triangle culling mode. Allows to exclude triangles from rendering based on + * their orientation relative to the view plane. + * @default Context3DTriangleFace.NONE + */ + public function get culling():String + { + var index:int = (_miscOptions & 0xf00) >> 8; + return CULLING_VALUES[index]; + } + + public function set culling(value:String):void + { + if (this.culling != value) + { + if (_onDrawRequired != null) _onDrawRequired(); + + var index:int = CULLING_VALUES.indexOf(value); + if (index == -1) throw new ArgumentError("Invalid culling mode"); + + _miscOptions = (_miscOptions & 0xfffff0ff) | (index << 8); + } + } + + /** The clipping rectangle can be used to limit rendering in the current render target to + * a certain area. This method expects the rectangle in stage coordinates. To prevent + * any clipping, assign null. + * + * @default null + */ + public function get clipRect():Rectangle { return _clipRect; } + public function set clipRect(value:Rectangle):void + { + if (!RectangleUtil.compare(_clipRect, value)) + { + if (_onDrawRequired != null) _onDrawRequired(); + if (value) + { + if (_clipRect == null) _clipRect = Pool.getRectangle(); + _clipRect.copyFrom(value); + } + else if (_clipRect) + { + Pool.putRectangle(_clipRect); + _clipRect = null; + } + } + } + + /** The anti-alias setting used when setting the current render target + * via setRenderTarget. */ + public function get renderTargetAntiAlias():int + { + return _miscOptions & 0xf; + } + + /** Indicates if the render target (set via setRenderTarget) + * has its depth and stencil buffers enabled. */ + public function get renderTargetSupportsDepthAndStencil():Boolean + { + return (_miscOptions & 0xf0) != 0; + } + + /** Indicates if there have been any 3D transformations. + * Returns true if the 3D modelview matrix contains a value. */ + public function get is3D():Boolean { return _modelviewMatrix3D != null; } + + /** @private + * + * This callback is executed whenever a state change requires a draw operation. + * This is the case if blend mode, render target, culling or clipping rectangle + * are changing. */ + internal function get onDrawRequired():Function { return _onDrawRequired; } + internal function set onDrawRequired(value:Function):void { _onDrawRequired = value; } + } +} diff --git a/mobile_version/src/starling/rendering/VertexData.as b/mobile_version/src/starling/rendering/VertexData.as new file mode 100644 index 00000000..ec2c2f62 --- /dev/null +++ b/mobile_version/src/starling/rendering/VertexData.as @@ -0,0 +1,1129 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.rendering +{ + import flash.display3D.Context3D; + import flash.display3D.VertexBuffer3D; + import flash.errors.IllegalOperationError; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.geom.Vector3D; + import flash.utils.ByteArray; + import flash.utils.Endian; + + import starling.core.Starling; + import starling.errors.MissingContextError; + import starling.styles.MeshStyle; + import starling.utils.MathUtil; + import starling.utils.MatrixUtil; + import starling.utils.StringUtil; + + /** The VertexData class manages a raw list of vertex information, allowing direct upload + * to Stage3D vertex buffers. You only have to work with this class if you're writing + * your own rendering code (e.g. if you create custom display objects). + * + *

To render objects with Stage3D, you have to organize vertices and indices in so-called + * vertex- and index-buffers. Vertex buffers store the coordinates of the vertices that make + * up an object; index buffers reference those vertices to determine which vertices spawn + * up triangles. Those buffers reside in graphics memory and can be accessed very + * efficiently by the GPU.

+ * + *

Before you can move data into the buffers, you have to set it up in conventional + * memory — that is, in a Vector or a ByteArray. Since it's quite cumbersome to manually + * create and manipulate those data structures, the IndexData and VertexData classes provide + * a simple way to do just that. The data is stored sequentially (one vertex or index after + * the other) so that it can easily be uploaded to a buffer.

+ * + * Vertex Format + * + *

The VertexData class requires a custom format string on initialization, or an instance + * of the VertexDataFormat class. Here is an example:

+ * + * + * vertexData = new VertexData("position:float2, color:bytes4"); + * vertexData.setPoint(0, "position", 320, 480); + * vertexData.setColor(0, "color", 0xff00ff); + * + *

This instance is set up with two attributes: "position" and "color". The keywords + * after the colons depict the format and size of the data that each property uses; in this + * case, we store two floats for the position (for the x- and y-coordinates) and four + * bytes for the color. Please refer to the VertexDataFormat documentation for details.

+ * + *

The attribute names are then used to read and write data to the respective positions + * inside a vertex. Furthermore, they come in handy when copying data from one VertexData + * instance to another: attributes with equal name and data format may be transferred between + * different VertexData objects, even when they contain different sets of attributes or have + * a different layout.

+ * + * Colors + * + *

Always use the format bytes4 for color data. The color access methods + * expect that format, since it's the most efficient way to store color data. Furthermore, + * you should always include the string "color" (or "Color") in the name of color data; + * that way, it will be recognized as such and will always have its value pre-filled with + * pure white at full opacity.

+ * + * Premultiplied Alpha + * + *

Per default, color values are stored with premultiplied alpha values, which + * means that the rgb values were multiplied with the alpha values + * before saving them. You can change this behavior with the premultipliedAlpha + * property.

+ * + *

Beware: with premultiplied alpha, the alpha value always affects the resolution of + * the RGB channels. A small alpha value results in a lower accuracy of the other channels, + * and if the alpha value reaches zero, the color information is lost altogether.

+ * + * Tinting + * + *

Some low-end hardware is very sensitive when it comes to fragment shader complexity. + * Thus, Starling optimizes shaders for non-tinted meshes. The VertexData class keeps track + * of its tinted-state, at least at a basic level: whenever you change color + * or alpha value of a vertex to something different than white (0xffffff) with + * full alpha (1.0), the tinted property is enabled.

+ * + *

However, that value is not entirely accurate: when you restore the color of just a + * range of vertices, or copy just a subset of vertices to another instance, the property + * might wrongfully indicate a tinted mesh. If that's the case, you can either call + * updateTinted() or assign a custom value to the tinted-property. + *

+ * + * @see VertexDataFormat + * @see IndexData + */ + public class VertexData + { + private var _rawData:ByteArray; + private var _numVertices:int; + private var _format:VertexDataFormat; + private var _attributes:Vector.; + private var _numAttributes:int; + private var _premultipliedAlpha:Boolean; + private var _tinted:Boolean; + + private var _posOffset:int; // in bytes + private var _colOffset:int; // in bytes + private var _vertexSize:int; // in bytes + + // helper objects + private static var sHelperPoint:Point = new Point(); + private static var sHelperPoint3D:Vector3D = new Vector3D(); + private static var sBytes:ByteArray = new ByteArray(); + + /** Creates an empty VertexData object with the given format and initial capacity. + * + * @param format + * + * Either a VertexDataFormat instance or a String that describes the data format. + * Refer to the VertexDataFormat class for more information. If you don't pass a format, + * the default MeshStyle.VERTEX_FORMAT will be used. + * + * @param initialCapacity + * + * The initial capacity affects just the way the internal ByteArray is allocated, not the + * numIndices value, which will always be zero when the constructor returns. + * The reason for this behavior is the peculiar way in which ByteArrays organize their + * memory: + * + *

The first time you set the length of a ByteArray, it will adhere to that: + * a ByteArray with length 20 will take up 20 bytes (plus some overhead). When you change + * it to a smaller length, it will stick to the original value, e.g. with a length of 10 + * it will still take up 20 bytes. However, now comes the weird part: change it to + * anything above the original length, and it will allocate 4096 bytes!

+ * + *

Thus, be sure to always make a generous educated guess, depending on the planned + * usage of your VertexData instances.

+ */ + public function VertexData(format:*=null, initialCapacity:int=32) + { + if (format == null) _format = MeshStyle.VERTEX_FORMAT; + else if (format is VertexDataFormat) _format = format; + else if (format is String) _format = VertexDataFormat.fromString(format as String); + else throw new ArgumentError("'format' must be String or VertexDataFormat"); + + _attributes = _format.attributes; + _numAttributes = _attributes.length; + _posOffset = _format.hasAttribute("position") ? _format.getOffset("position") : 0; + _colOffset = _format.hasAttribute("color") ? _format.getOffset("color") : 0; + _vertexSize = _format.vertexSize; + _numVertices = 0; + _premultipliedAlpha = true; + _rawData = new ByteArray(); + _rawData.endian = sBytes.endian = Endian.LITTLE_ENDIAN; + _rawData.length = initialCapacity * _vertexSize; // just for the initial allocation + _rawData.length = 0; // changes length, but not memory! + } + + /** Explicitly frees up the memory used by the ByteArray. */ + public function clear():void + { + _rawData.clear(); + _numVertices = 0; + _tinted = false; + } + + /** Creates a duplicate of the vertex data object. */ + public function clone():VertexData + { + var clone:VertexData = new VertexData(_format, _numVertices); + clone._rawData.writeBytes(_rawData); + clone._numVertices = _numVertices; + clone._premultipliedAlpha = _premultipliedAlpha; + clone._tinted = _tinted; + return clone; + } + + /** Copies the vertex data (or a range of it, defined by 'vertexID' and 'numVertices') + * of this instance to another vertex data object, starting at a certain target index. + * If the target is not big enough, it will be resized to fit all the new vertices. + * + *

If you pass a non-null matrix, the 2D position of each vertex will be transformed + * by that matrix before storing it in the target object. (The position being either an + * attribute with the name "position" or, if such an attribute is not found, the first + * attribute of each vertex. It must consist of two float values containing the x- and + * y-coordinates of the vertex.)

+ * + *

Source and target do not need to have the exact same format. Only properties that + * exist in the target will be copied; others will be ignored. If a property with the + * same name but a different format exists in the target, an exception will be raised. + * Beware, though, that the copy-operation becomes much more expensive when the formats + * differ.

+ */ + public function copyTo(target:VertexData, targetVertexID:int=0, matrix:Matrix=null, + vertexID:int=0, numVertices:int=-1):void + { + if (numVertices < 0 || vertexID + numVertices > _numVertices) + numVertices = _numVertices - vertexID; + + if (_format === target._format) + { + if (target._numVertices < targetVertexID + numVertices) + target._numVertices = targetVertexID + numVertices; + + target._tinted ||= _tinted; + + // In this case, it's fastest to copy the complete range in one call + // and then overwrite only the transformed positions. + + var targetRawData:ByteArray = target._rawData; + targetRawData.position = targetVertexID * _vertexSize; + targetRawData.writeBytes(_rawData, vertexID * _vertexSize, numVertices * _vertexSize); + + if (matrix) + { + var x:Number, y:Number; + var pos:int = targetVertexID * _vertexSize + _posOffset; + var endPos:int = pos + (numVertices * _vertexSize); + + while (pos < endPos) + { + targetRawData.position = pos; + x = targetRawData.readFloat(); + y = targetRawData.readFloat(); + + targetRawData.position = pos; + targetRawData.writeFloat(matrix.a * x + matrix.c * y + matrix.tx); + targetRawData.writeFloat(matrix.d * y + matrix.b * x + matrix.ty); + + pos += _vertexSize; + } + } + } + else + { + if (target._numVertices < targetVertexID + numVertices) + target.numVertices = targetVertexID + numVertices; // ensure correct alphas! + + for (var i:int=0; i<_numAttributes; ++i) + { + var srcAttr:VertexDataAttribute = _attributes[i]; + var tgtAttr:VertexDataAttribute = target.getAttribute(srcAttr.name); + + if (tgtAttr) // only copy attributes that exist in the target, as well + { + if (srcAttr.offset == _posOffset) + copyAttributeTo_internal(target, targetVertexID, matrix, + srcAttr, tgtAttr, vertexID, numVertices); + else + copyAttributeTo_internal(target, targetVertexID, null, + srcAttr, tgtAttr, vertexID, numVertices); + } + } + } + } + + /** Copies a specific attribute of all contained vertices (or a range of them, defined by + * 'vertexID' and 'numVertices') to another VertexData instance. Beware that both name + * and format of the attribute must be identical in source and target. + * If the target is not big enough, it will be resized to fit all the new vertices. + * + *

If you pass a non-null matrix, the specified attribute will be transformed by + * that matrix before storing it in the target object. It must consist of two float + * values.

+ */ + public function copyAttributeTo(target:VertexData, targetVertexID:int, attrName:String, + matrix:Matrix=null, vertexID:int=0, numVertices:int=-1):void + { + var sourceAttribute:VertexDataAttribute = getAttribute(attrName); + var targetAttribute:VertexDataAttribute = target.getAttribute(attrName); + + if (sourceAttribute == null) + throw new ArgumentError("Attribute '" + attrName + "' not found in source data"); + + if (targetAttribute == null) + throw new ArgumentError("Attribute '" + attrName + "' not found in target data"); + + if (sourceAttribute.isColor) + target._tinted ||= _tinted; + + copyAttributeTo_internal(target, targetVertexID, matrix, + sourceAttribute, targetAttribute, vertexID, numVertices); + } + + private function copyAttributeTo_internal( + target:VertexData, targetVertexID:int, matrix:Matrix, + sourceAttribute:VertexDataAttribute, targetAttribute:VertexDataAttribute, + vertexID:int, numVertices:int):void + { + if (sourceAttribute.format != targetAttribute.format) + throw new IllegalOperationError("Attribute formats differ between source and target"); + + if (numVertices < 0 || vertexID + numVertices > _numVertices) + numVertices = _numVertices - vertexID; + + if (target._numVertices < targetVertexID + numVertices) + target._numVertices = targetVertexID + numVertices; + + var i:int, j:int, x:Number, y:Number; + var sourceData:ByteArray = _rawData; + var targetData:ByteArray = target._rawData; + var sourceDelta:int = _vertexSize - sourceAttribute.size; + var targetDelta:int = target._vertexSize - targetAttribute.size; + var attributeSizeIn32Bits:int = sourceAttribute.size / 4; + + sourceData.position = vertexID * _vertexSize + sourceAttribute.offset; + targetData.position = targetVertexID * target._vertexSize + targetAttribute.offset; + + if (matrix) + { + for (i=0; i> 8) & 0xffffff; + } + + /** Writes the RGB color to the specified vertex and attribute (alpha is not changed). */ + public function setColor(vertexID:int, attrName:String, color:uint):void + { + if (_numVertices < vertexID + 1) + numVertices = vertexID + 1; + + var alpha:Number = getAlpha(vertexID, attrName); + colorize(attrName, color, alpha, vertexID, 1); + } + + /** Reads the alpha value from the specified vertex and attribute. */ + public function getAlpha(vertexID:int, attrName:String="color"):Number + { + var offset:int = attrName == "color" ? _colOffset : getAttribute(attrName).offset; + _rawData.position = vertexID * _vertexSize + offset; + var rgba:uint = switchEndian(_rawData.readUnsignedInt()); + return (rgba & 0xff) / 255.0; + } + + /** Writes the given alpha value to the specified vertex and attribute (range 0-1). */ + public function setAlpha(vertexID:int, attrName:String, alpha:Number):void + { + if (_numVertices < vertexID + 1) + numVertices = vertexID + 1; + + var color:uint = getColor(vertexID, attrName); + colorize(attrName, color, alpha, vertexID, 1); + } + + // bounds helpers + + /** Calculates the bounds of the 2D vertex positions identified by the given name. + * The positions may optionally be transformed by a matrix before calculating the bounds. + * If you pass an 'out' Rectangle, the result will be stored in this rectangle + * instead of creating a new object. To use all vertices for the calculation, set + * 'numVertices' to '-1'. */ + public function getBounds(attrName:String="position", matrix:Matrix=null, + vertexID:int=0, numVertices:int=-1, out:Rectangle=null):Rectangle + { + if (out == null) out = new Rectangle(); + if (numVertices < 0 || vertexID + numVertices > _numVertices) + numVertices = _numVertices - vertexID; + + if (numVertices == 0) + { + if (matrix == null) + out.setEmpty(); + else + { + MatrixUtil.transformCoords(matrix, 0, 0, sHelperPoint); + out.setTo(sHelperPoint.x, sHelperPoint.y, 0, 0); + } + } + else + { + var minX:Number = Number.MAX_VALUE, maxX:Number = -Number.MAX_VALUE; + var minY:Number = Number.MAX_VALUE, maxY:Number = -Number.MAX_VALUE; + var offset:int = attrName == "position" ? _posOffset : getAttribute(attrName).offset; + var position:int = vertexID * _vertexSize + offset; + var x:Number, y:Number, i:int; + + if (matrix == null) + { + for (i=0; i x) minX = x; + if (maxX < x) maxX = x; + if (minY > y) minY = y; + if (maxY < y) maxY = y; + } + } + else + { + for (i=0; i sHelperPoint.x) minX = sHelperPoint.x; + if (maxX < sHelperPoint.x) maxX = sHelperPoint.x; + if (minY > sHelperPoint.y) minY = sHelperPoint.y; + if (maxY < sHelperPoint.y) maxY = sHelperPoint.y; + } + } + + out.setTo(minX, minY, maxX - minX, maxY - minY); + } + + return out; + } + + /** Calculates the bounds of the 2D vertex positions identified by the given name, + * projected into the XY-plane of a certain 3D space as they appear from the given + * camera position. Note that 'camPos' is expected in the target coordinate system + * (the same that the XY-plane lies in). + * + *

If you pass an 'out' Rectangle, the result will be stored in this rectangle + * instead of creating a new object. To use all vertices for the calculation, set + * 'numVertices' to '-1'.

*/ + public function getBoundsProjected(attrName:String, matrix:Matrix3D, + camPos:Vector3D, vertexID:int=0, numVertices:int=-1, + out:Rectangle=null):Rectangle + { + if (out == null) out = new Rectangle(); + if (camPos == null) throw new ArgumentError("camPos must not be null"); + if (numVertices < 0 || vertexID + numVertices > _numVertices) + numVertices = _numVertices - vertexID; + + if (numVertices == 0) + { + if (matrix) + MatrixUtil.transformCoords3D(matrix, 0, 0, 0, sHelperPoint3D); + else + sHelperPoint3D.setTo(0, 0, 0); + + MathUtil.intersectLineWithXYPlane(camPos, sHelperPoint3D, sHelperPoint); + out.setTo(sHelperPoint.x, sHelperPoint.y, 0, 0); + } + else + { + var minX:Number = Number.MAX_VALUE, maxX:Number = -Number.MAX_VALUE; + var minY:Number = Number.MAX_VALUE, maxY:Number = -Number.MAX_VALUE; + var offset:int = attrName == "position" ? _posOffset : getAttribute(attrName).offset; + var position:int = vertexID * _vertexSize + offset; + var x:Number, y:Number, i:int; + + for (i=0; i sHelperPoint.x) minX = sHelperPoint.x; + if (maxX < sHelperPoint.x) maxX = sHelperPoint.x; + if (minY > sHelperPoint.y) minY = sHelperPoint.y; + if (maxY < sHelperPoint.y) maxY = sHelperPoint.y; + } + + out.setTo(minX, minY, maxX - minX, maxY - minY); + } + + return out; + } + + /** Indicates if color attributes should be stored premultiplied with the alpha value. + * Changing this value does not modify any existing color data. + * If you want that, use the setPremultipliedAlpha method instead. + * @default true */ + public function get premultipliedAlpha():Boolean { return _premultipliedAlpha; } + public function set premultipliedAlpha(value:Boolean):void + { + setPremultipliedAlpha(value, false); + } + + /** Changes the way alpha and color values are stored. Optionally updates all existing + * vertices. */ + public function setPremultipliedAlpha(value:Boolean, updateData:Boolean):void + { + if (updateData && value != _premultipliedAlpha) + { + for (var i:int=0; i<_numAttributes; ++i) + { + var attribute:VertexDataAttribute = _attributes[i]; + if (attribute.isColor) + { + var pos:int = attribute.offset; + var oldColor:uint; + var newColor:uint; + + for (var j:int=0; j<_numVertices; ++j) + { + _rawData.position = pos; + oldColor = switchEndian(_rawData.readUnsignedInt()); + newColor = value ? premultiplyAlpha(oldColor) : unmultiplyAlpha(oldColor); + + _rawData.position = pos; + _rawData.writeUnsignedInt(switchEndian(newColor)); + + pos += _vertexSize; + } + } + } + } + + _premultipliedAlpha = value; + } + + /** Updates the tinted property from the actual color data. This might make + * sense after copying part of a tinted VertexData instance to another, since not each + * color value is checked in the process. An instance is tinted if any vertices have a + * non-white color or are not fully opaque. */ + public function updateTinted(attrName:String="color"):Boolean + { + var pos:int = attrName == "color" ? _colOffset : getAttribute(attrName).offset; + _tinted = false; + + for (var i:int=0; i<_numVertices; ++i) + { + _rawData.position = pos; + + if (_rawData.readUnsignedInt() != 0xffffffff) + { + _tinted = true; + break; + } + + pos += _vertexSize; + } + + return _tinted; + } + + // modify multiple attributes + + /** Transforms the 2D positions of subsequent vertices by multiplication with a + * transformation matrix. */ + public function transformPoints(attrName:String, matrix:Matrix, + vertexID:int=0, numVertices:int=-1):void + { + if (numVertices < 0 || vertexID + numVertices > _numVertices) + numVertices = _numVertices - vertexID; + + var x:Number, y:Number; + var offset:int = attrName == "position" ? _posOffset : getAttribute(attrName).offset; + var pos:int = vertexID * _vertexSize + offset; + var endPos:int = pos + numVertices * _vertexSize; + + while (pos < endPos) + { + _rawData.position = pos; + x = _rawData.readFloat(); + y = _rawData.readFloat(); + + _rawData.position = pos; + _rawData.writeFloat(matrix.a * x + matrix.c * y + matrix.tx); + _rawData.writeFloat(matrix.d * y + matrix.b * x + matrix.ty); + + pos += _vertexSize; + } + } + + /** Translates the 2D positions of subsequent vertices by a certain offset. */ + public function translatePoints(attrName:String, deltaX:Number, deltaY:Number, + vertexID:int=0, numVertices:int=-1):void + { + if (numVertices < 0 || vertexID + numVertices > _numVertices) + numVertices = _numVertices - vertexID; + + var x:Number, y:Number; + var offset:int = attrName == "position" ? _posOffset : getAttribute(attrName).offset; + var pos:int = vertexID * _vertexSize + offset; + var endPos:int = pos + numVertices * _vertexSize; + + while (pos < endPos) + { + _rawData.position = pos; + x = _rawData.readFloat(); + y = _rawData.readFloat(); + + _rawData.position = pos; + _rawData.writeFloat(x + deltaX); + _rawData.writeFloat(y + deltaY); + + pos += _vertexSize; + } + } + + /** Multiplies the alpha values of subsequent vertices by a certain factor. */ + public function scaleAlphas(attrName:String, factor:Number, + vertexID:int=0, numVertices:int=-1):void + { + if (factor == 1.0) return; + if (numVertices < 0 || vertexID + numVertices > _numVertices) + numVertices = _numVertices - vertexID; + + _tinted = true; // factor must be != 1, so there's definitely tinting. + + var i:int; + var offset:int = attrName == "color" ? _colOffset : getAttribute(attrName).offset; + var colorPos:int = vertexID * _vertexSize + offset; + var alphaPos:int, alpha:Number, rgba:uint; + + for (i=0; i 1.0) alpha = 1.0; + else if (alpha < 0.0) alpha = 0.0; + + if (alpha == 1.0 || !_premultipliedAlpha) + { + _rawData[alphaPos] = int(alpha * 255.0); + } + else + { + _rawData.position = colorPos; + rgba = unmultiplyAlpha(switchEndian(_rawData.readUnsignedInt())); + rgba = (rgba & 0xffffff00) | (int(alpha * 255.0) & 0xff); + rgba = premultiplyAlpha(rgba); + + _rawData.position = colorPos; + _rawData.writeUnsignedInt(switchEndian(rgba)); + } + + colorPos += _vertexSize; + } + } + + /** Writes the given RGB and alpha values to the specified vertices. */ + public function colorize(attrName:String="color", color:uint=0xffffff, alpha:Number=1.0, + vertexID:int=0, numVertices:int=-1):void + { + if (numVertices < 0 || vertexID + numVertices > _numVertices) + numVertices = _numVertices - vertexID; + + var offset:int = attrName == "color" ? _colOffset : getAttribute(attrName).offset; + var pos:int = vertexID * _vertexSize + offset; + var endPos:int = pos + (numVertices * _vertexSize); + + if (alpha > 1.0) alpha = 1.0; + else if (alpha < 0.0) alpha = 0.0; + + var rgba:uint = ((color << 8) & 0xffffff00) | (int(alpha * 255.0) & 0xff); + + if (rgba == 0xffffffff && numVertices == _numVertices) _tinted = false; + else if (rgba != 0xffffffff) _tinted = true; + + if (_premultipliedAlpha && alpha != 1.0) rgba = premultiplyAlpha(rgba); + + _rawData.position = vertexID * _vertexSize + offset; + _rawData.writeUnsignedInt(switchEndian(rgba)); + + while (pos < endPos) + { + _rawData.position = pos; + _rawData.writeUnsignedInt(switchEndian(rgba)); + pos += _vertexSize; + } + } + + // format helpers + + /** Returns the format of a certain vertex attribute, identified by its name. + * Typical values: float1, float2, float3, float4, bytes4. */ + public function getFormat(attrName:String):String + { + return getAttribute(attrName).format; + } + + /** Returns the size of a certain vertex attribute in bytes. */ + public function getSize(attrName:String):int + { + return getAttribute(attrName).size; + } + + /** Returns the size of a certain vertex attribute in 32 bit units. */ + public function getSizeIn32Bits(attrName:String):int + { + return getAttribute(attrName).size / 4; + } + + /** Returns the offset (in bytes) of an attribute within a vertex. */ + public function getOffset(attrName:String):int + { + return getAttribute(attrName).offset; + } + + /** Returns the offset (in 32 bit units) of an attribute within a vertex. */ + public function getOffsetIn32Bits(attrName:String):int + { + return getAttribute(attrName).offset / 4; + } + + /** Indicates if the VertexData instances contains an attribute with the specified name. */ + public function hasAttribute(attrName:String):Boolean + { + return getAttribute(attrName) != null; + } + + // VertexBuffer helpers + + /** Creates a vertex buffer object with the right size to fit the complete data. + * Optionally, the current data is uploaded right away. */ + public function createVertexBuffer(upload:Boolean=false, + bufferUsage:String="staticDraw"):VertexBuffer3D + { + var context:Context3D = Starling.context; + if (context == null) throw new MissingContextError(); + if (_numVertices == 0) return null; + + var buffer:VertexBuffer3D = context.createVertexBuffer( + _numVertices, _vertexSize / 4, bufferUsage); + + if (upload) uploadToVertexBuffer(buffer); + return buffer; + } + + /** Uploads the complete data (or a section of it) to the given vertex buffer. */ + public function uploadToVertexBuffer(buffer:VertexBuffer3D, vertexID:int=0, numVertices:int=-1):void + { + if (numVertices < 0 || vertexID + numVertices > _numVertices) + numVertices = _numVertices - vertexID; + + if (numVertices > 0) + buffer.uploadFromByteArray(_rawData, 0, vertexID, numVertices); + } + + [Inline] + private final function getAttribute(attrName:String):VertexDataAttribute + { + var i:int, attribute:VertexDataAttribute; + + for (i=0; i<_numAttributes; ++i) + { + attribute = _attributes[i]; + if (attribute.name == attrName) return attribute; + } + + return null; + } + + [Inline] + private static function switchEndian(value:uint):uint + { + return ( value & 0xff) << 24 | + ((value >> 8) & 0xff) << 16 | + ((value >> 16) & 0xff) << 8 | + ((value >> 24) & 0xff); + } + + private static function premultiplyAlpha(rgba:uint):uint + { + var alpha:uint = rgba & 0xff; + + if (alpha == 0xff) return rgba; + else + { + var factor:Number = alpha / 255.0; + var r:uint = ((rgba >> 24) & 0xff) * factor; + var g:uint = ((rgba >> 16) & 0xff) * factor; + var b:uint = ((rgba >> 8) & 0xff) * factor; + + return (r & 0xff) << 24 | + (g & 0xff) << 16 | + (b & 0xff) << 8 | alpha; + } + } + + private static function unmultiplyAlpha(rgba:uint):uint + { + var alpha:uint = rgba & 0xff; + + if (alpha == 0xff || alpha == 0x0) return rgba; + else + { + var factor:Number = alpha / 255.0; + var r:uint = ((rgba >> 24) & 0xff) / factor; + var g:uint = ((rgba >> 16) & 0xff) / factor; + var b:uint = ((rgba >> 8) & 0xff) / factor; + + return (r & 0xff) << 24 | + (g & 0xff) << 16 | + (b & 0xff) << 8 | alpha; + } + } + + // properties + + /** The total number of vertices. If you make the object bigger, it will be filled up with + * 1.0 for all alpha values and zero for everything else. */ + public function get numVertices():int { return _numVertices; } + public function set numVertices(value:int):void + { + if (value > _numVertices) + { + var oldLength:int = _numVertices * vertexSize; + var newLength:int = value * _vertexSize; + + if (_rawData.length > oldLength) + { + _rawData.position = oldLength; + while (_rawData.bytesAvailable) _rawData.writeUnsignedInt(0); + } + + if (_rawData.length < newLength) + _rawData.length = newLength; + + for (var i:int=0; i<_numAttributes; ++i) + { + var attribute:VertexDataAttribute = _attributes[i]; + if (attribute.isColor) // initialize color values with "white" and full alpha + { + var pos:int = _numVertices * _vertexSize + attribute.offset; + for (var j:int=_numVertices; jfalse (and the value wasn't modified manually), the result is 100% + * accurate; true represents just an educated guess. To be entirely sure, + * you may call updateTinted(). + */ + public function get tinted():Boolean { return _tinted; } + public function set tinted(value:Boolean):void { _tinted = value; } + + /** The format string that describes the attributes of each vertex. */ + public function get formatString():String + { + return _format.formatString; + } + + /** The size (in bytes) of each vertex. */ + public function get vertexSize():int + { + return _vertexSize; + } + + /** The size (in 32 bit units) of each vertex. */ + public function get vertexSizeIn32Bits():int + { + return _vertexSize / 4; + } + + /** The size (in bytes) of the raw vertex data. */ + public function get size():int + { + return _numVertices * _vertexSize; + } + + /** The size (in 32 bit units) of the raw vertex data. */ + public function get sizeIn32Bits():int + { + return _numVertices * _vertexSize / 4; + } + } +} diff --git a/mobile_version/src/starling/rendering/VertexDataAttribute.as b/mobile_version/src/starling/rendering/VertexDataAttribute.as new file mode 100644 index 00000000..c5a1887c --- /dev/null +++ b/mobile_version/src/starling/rendering/VertexDataAttribute.as @@ -0,0 +1,47 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.rendering +{ + /** Holds the properties of a single attribute in a VertexDataFormat instance. + * The member variables must never be changed; they are only public + * for performance reasons. */ + internal class VertexDataAttribute + { + private static const FORMAT_SIZES:Object = { + "bytes4": 4, + "float1": 4, + "float2": 8, + "float3": 12, + "float4": 16 + }; + + public var name:String; + public var format:String; + public var isColor:Boolean; + public var offset:int; // in bytes + public var size:int; // in bytes + + /** Creates a new instance with the given properties. */ + public function VertexDataAttribute(name:String, format:String, offset:int) + { + if (!(format in FORMAT_SIZES)) + throw new ArgumentError( + "Invalid attribute format: " + format + ". " + + "Use one of the following: 'float1'-'float4', 'bytes4'"); + + this.name = name; + this.format = format; + this.offset = offset; + this.size = FORMAT_SIZES[format]; + this.isColor = name.indexOf("color") != -1 || name.indexOf("Color") != -1 + } + } +} diff --git a/mobile_version/src/starling/rendering/VertexDataFormat.as b/mobile_version/src/starling/rendering/VertexDataFormat.as new file mode 100644 index 00000000..7c5396a0 --- /dev/null +++ b/mobile_version/src/starling/rendering/VertexDataFormat.as @@ -0,0 +1,275 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.rendering +{ + import flash.display3D.VertexBuffer3D; + import flash.utils.Dictionary; + + import starling.core.Starling; + import starling.utils.StringUtil; + + /** Describes the memory layout of VertexData instances, as used for every single vertex. + * + *

The format is set up via a simple String. Here is an example:

+ * + * + * format = VertexDataFormat.fromString("position:float2, color:bytes4"); + * + *

This String describes two attributes: "position" and "color". The keywords after + * the colons depict the format and size of the data that each attribute uses; in this + * case, we store two floats for the position (taking up the x- and y-coordinates) and four + * bytes for the color. (The available formats are the same as those defined in the + * Context3DVertexBufferFormat class: + * float1, float2, float3, float4, bytes4.)

+ * + *

You cannot create a VertexData instance with its constructor; instead, you must use the + * static fromString-method. The reason for this behavior: the class maintains + * a cache, and a call to fromString will return an existing instance if an + * equivalent format has already been created in the past. That saves processing time and + * memory.

+ * + *

VertexDataFormat instances are immutable, i.e. they are solely defined by their format + * string and cannot be changed later.

+ * + * @see VertexData + */ + public class VertexDataFormat + { + private var _format:String; + private var _vertexSize:int; + private var _attributes:Vector.; + + // format cache + private static var sFormats:Dictionary = new Dictionary(); + + /** Don't use the constructor, but call VertexDataFormat.fromString instead. + * This allows for efficient format caching. */ + public function VertexDataFormat() + { + _attributes = new Vector.(); + } + + /** Creates a new VertexDataFormat instance from the given String, or returns one from + * the cache (if an equivalent String has already been used before). + * + * @param format + * + * Describes the attributes of each vertex, consisting of a comma-separated + * list of attribute names and their format, e.g.: + * + *
"position:float2, texCoords:float2, color:bytes4"
+ * + *

This set of attributes will be allocated for each vertex, and they will be + * stored in exactly the given order.

+ * + *
    + *
  • Names are used to access the specific attributes of a vertex. They are + * completely arbitrary.
  • + *
  • The available formats can be found in the Context3DVertexBufferFormat + * class in the flash.display3D package.
  • + *
  • Both names and format strings are case-sensitive.
  • + *
  • Always use bytes4 for color data that you want to access with the + * respective methods.
  • + *
  • Furthermore, the attribute names of colors should include the string "color" + * (or the uppercase variant). If that's the case, the "alpha" channel of the color + * will automatically be initialized with "1.0" when the VertexData object is + * created or resized.
  • + *
+ */ + public static function fromString(format:String):VertexDataFormat + { + if (format in sFormats) return sFormats[format]; + else + { + var instance:VertexDataFormat = new VertexDataFormat(); + instance.parseFormat(format); + + var normalizedFormat:String = instance._format; + + if (normalizedFormat in sFormats) + instance = sFormats[normalizedFormat]; + + sFormats[format] = instance; + sFormats[normalizedFormat] = instance; + + return instance; + } + } + + /** Creates a new VertexDataFormat instance by appending the given format string + * to the current instance's format. */ + public function extend(format:String):VertexDataFormat + { + return fromString(_format + ", " + format); + } + + // query methods + + /** Returns the size of a certain vertex attribute in bytes. */ + public function getSize(attrName:String):int + { + return getAttribute(attrName).size; + } + + /** Returns the size of a certain vertex attribute in 32 bit units. */ + public function getSizeIn32Bits(attrName:String):int + { + return getAttribute(attrName).size / 4; + } + + /** Returns the offset (in bytes) of an attribute within a vertex. */ + public function getOffset(attrName:String):int + { + return getAttribute(attrName).offset; + } + + /** Returns the offset (in 32 bit units) of an attribute within a vertex. */ + public function getOffsetIn32Bits(attrName:String):int + { + return getAttribute(attrName).offset / 4; + } + + /** Returns the format of a certain vertex attribute, identified by its name. + * Typical values: float1, float2, float3, float4, bytes4. */ + public function getFormat(attrName:String):String + { + return getAttribute(attrName).format; + } + + /** Returns the name of the attribute at the given position within the vertex format. */ + public function getName(attrIndex:int):String + { + return _attributes[attrIndex].name; + } + + /** Indicates if the format contains an attribute with the given name. */ + public function hasAttribute(attrName:String):Boolean + { + var numAttributes:int = _attributes.length; + + for (var i:int=0; iContext3D-method with the same name, + * automatically replacing attrName with the corresponding values for + * bufferOffset and format. */ + public function setVertexBufferAt(index:int, buffer:VertexBuffer3D, attrName:String):void + { + var attribute:VertexDataAttribute = getAttribute(attrName); + Starling.context.setVertexBufferAt(index, buffer, attribute.offset / 4, attribute.format); + } + + // parsing + + private function parseFormat(format:String):void + { + if (format != null && format != "") + { + _attributes.length = 0; + _format = ""; + + var parts:Array = format.split(","); + var numParts:int = parts.length; + var offset:int = 0; + + for (var i:int=0; i + { + return _attributes; + } + + // properties + + /** Returns the normalized format string. */ + public function get formatString():String + { + return _format; + } + + /** The size (in bytes) of each vertex. */ + public function get vertexSize():int + { + return _vertexSize; + } + + /** The size (in 32 bit units) of each vertex. */ + public function get vertexSizeIn32Bits():int + { + return _vertexSize / 4; + } + + /** The number of attributes per vertex. */ + public function get numAttributes():int + { + return _attributes.length; + } + } +} diff --git a/mobile_version/src/starling/styles/DistanceFieldStyle.as b/mobile_version/src/starling/styles/DistanceFieldStyle.as new file mode 100644 index 00000000..81353f77 --- /dev/null +++ b/mobile_version/src/starling/styles/DistanceFieldStyle.as @@ -0,0 +1,684 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.styles +{ + import flash.geom.Matrix; + + import starling.display.Mesh; + import starling.rendering.MeshEffect; + import starling.rendering.RenderState; + import starling.rendering.VertexData; + import starling.rendering.VertexDataFormat; + import starling.utils.Color; + import starling.utils.MathUtil; + + /** Provides support for signed distance fields to Starling meshes. + * + *

Signed distance field rendering allows bitmap fonts and other single colored shapes to + * be drawn without jagged edges, even at high magnifications. The technique was introduced in + * the SIGGRAPH paper Improved + * Alpha-Tested Magnification for Vector Textures and Special Effects by Valve Software. + *

+ * + *

While bitmap fonts are a great solution to render text in a GPU-friendly way, they + * don't scale well. For best results, one has to embed the font in all the sizes used within + * the app. The distance field style solves this issue: instead of providing a standard + * black and white image of the font, it uses a signed distance field texture as + * its input (a texture that encodes, for each pixel, the distance to the closest edge of a + * vector shape). With this data, the shape can be rendered smoothly at almost any scale.

+ * + *

Here are some tools that support creation of such distance field textures:

+ * + *
    + *
  • Littera - a free online bitmap font + * generator.
  • + *
  • Hiero - a cross platform + * tool.
  • + *
  • BMFont - Windows-only, from + * AngelCode.
  • + *
+ * + * Special effects + * + *

Another advantage of this rendering technique: it supports very efficient rendering of + * some popular filter effects, in just one pass, directly on the GPU. You can add an + * outline around the shape, let it glow in an arbitrary color, or add + * a drop shadow.

+ * + *

The type of effect currently used is called the 'mode'. + * Meshes with the same mode will be batched together on rendering.

+ */ + public class DistanceFieldStyle extends MeshStyle + { + /** The vertex format expected by this style. */ + public static const VERTEX_FORMAT:VertexDataFormat = + MeshStyle.VERTEX_FORMAT.extend( + "basic:bytes4, extended:bytes4, outerColor:bytes4"); + + /** Basic distance field rendering, without additional effects. */ + public static const MODE_BASIC:String = "basic"; + + /** Adds an outline around the edge of the shape. */ + public static const MODE_OUTLINE:String = "outline"; + + /** Adds a smooth glow effect around the shape. */ + public static const MODE_GLOW:String = "glow"; + + /** Adds a drop shadow behind the shape. */ + public static const MODE_SHADOW:String = "shadow"; + + private var _mode:String; + + // basic + private var _threshold:Number; + private var _alpha:Number; + private var _softness:Number; + + // extended + private var _outerThreshold:Number; + private var _outerAlphaEnd:Number; + private var _shadowOffsetX:Number; + private var _shadowOffsetY:Number; + + // outerColor + private var _outerColor:uint; + private var _outerAlphaStart:Number; + + /** Creates a new distance field style. + * + * @param softness adds a soft transition between the inside and the outside. + * This should typically be 1.0 divided by the spread used when + * creating the distance field texture. + * @param threshold the value separating the inside from the outside of the shape. + * Range: 0 - 1. + */ + public function DistanceFieldStyle(softness:Number=0.125, threshold:Number=0.5) + { + _mode = MODE_BASIC; + _threshold = threshold; + _softness = softness; + _alpha = 1.0; + + _outerThreshold = _outerAlphaEnd = 0.0; + _shadowOffsetX = _shadowOffsetY = 0.0; + + _outerColor = 0x0; + _outerAlphaStart = 0.0; + } + + /** @private */ + override public function copyFrom(meshStyle:MeshStyle):void + { + var otherStyle:DistanceFieldStyle = meshStyle as DistanceFieldStyle; + if (otherStyle) + { + _mode = otherStyle._mode; + _threshold = otherStyle._threshold; + _softness = otherStyle._softness; + _alpha = otherStyle._alpha; + + _outerThreshold = otherStyle._outerThreshold; + _outerAlphaEnd = otherStyle._outerAlphaEnd; + _shadowOffsetX = otherStyle._shadowOffsetX; + _shadowOffsetY = otherStyle._shadowOffsetY; + + _outerColor = otherStyle._outerColor; + _outerAlphaStart = otherStyle._outerAlphaStart; + } + + super.copyFrom(meshStyle); + } + + /** @private */ + override public function createEffect():MeshEffect + { + return new DistanceFieldEffect(); + } + + /** @private */ + override public function get vertexFormat():VertexDataFormat + { + return VERTEX_FORMAT; + } + + /** @private */ + override protected function onTargetAssigned(target:Mesh):void + { + updateVertices(); + } + + private function updateVertices():void + { + if (vertexData == null) return; + + // To save space, all settings are stored in 'bytes4' format; this means we write + // values in the range 0-255 into the bytes and receive floats in the range 0-1 in the + // shaders. Since the 'scale' and 'outerOffset' values require a different range, + // they are encoded with a scale factor and/or offset. The color is stored manually + // (not via 'setColor') to avoid PMA processing. + + var numVertices:int = vertexData.numVertices; + var maxScale:int = DistanceFieldEffect.MAX_SCALE; + var maxOuterOffset:int = DistanceFieldEffect.MAX_OUTER_OFFSET; + var encodedOuterOffsetX:Number = (_shadowOffsetX + maxOuterOffset) / (2 * maxOuterOffset); + var encodedOuterOffsetY:Number = (_shadowOffsetY + maxOuterOffset) / (2 * maxOuterOffset); + + var basic:uint = (uint(_threshold * 255) ) | + (uint(_alpha * 255) << 8) | + (uint(_softness / 2.0 * 255) << 16) | + (uint(1.0 / maxScale * 255) << 24); + var extended:uint = (uint(_outerThreshold * 255) ) | + (uint(_outerAlphaEnd * 255) << 8) | + (uint(encodedOuterOffsetX * 255) << 16) | + (uint(encodedOuterOffsetY * 255) << 24); + var outerColor:uint = (Color.getRed(_outerColor) ) | + (Color.getGreen(_outerColor) << 8) | + (Color.getBlue(_outerColor) << 16) | + (uint(_outerAlphaStart * 255) << 24); + + for (var i:int=0; i> 24) & 0xff) / 255.0 * maxScale; + var tgtScale:Number = MathUtil.clamp(srcScale * scale, minScale, maxScale); + var tgtAttr:uint = + (srcAttr & 0x00ffffff) | (uint(tgtScale / maxScale * 255) << 24); + + targetVertexData.setUnsignedInt(targetVertexID + i, "basic", tgtAttr); + } + } + } + } + + /** @private */ + override public function updateEffect(effect:MeshEffect, state:RenderState):void + { + var dfEffect:DistanceFieldEffect = effect as DistanceFieldEffect; + dfEffect.mode = _mode; + + if (state.is3D) dfEffect.scale = 1.0; + else + { + // The softness is adapted automatically with the total scale of the object. + // However, this only works for 2D objects. + + var matrix:Matrix = state.modelviewMatrix; + var scale:Number = Math.sqrt(matrix.a * matrix.a + matrix.c * matrix.c); + dfEffect.scale = scale; + } + + super.updateEffect(effect, state); + } + + /** @private */ + override public function canBatchWith(meshStyle:MeshStyle):Boolean + { + var dfStyle:DistanceFieldStyle = meshStyle as DistanceFieldStyle; + if (dfStyle && super.canBatchWith(meshStyle)) return dfStyle.mode == _mode; + else return false; + } + + // simplified setup + + /** Restores basic render mode, i.e. smooth rendering of the shape. */ + public function setupBasic():void + { + _mode = MODE_BASIC; + + setRequiresRedraw(); + } + + /** Sets up outline rendering mode. The 'width' determines the threshold where the + * outline ends; 'width + threshold' must not exceed '1.0'. + */ + public function setupOutline(width:Number=0.25, color:uint=0x0, alpha:Number=1.0):void + { + _mode = MODE_OUTLINE; + _outerThreshold = MathUtil.clamp(_threshold - width, 0, _threshold); + _outerColor = color; + _outerAlphaStart = _outerAlphaEnd = MathUtil.clamp(alpha, 0, 1); + _shadowOffsetX = _shadowOffsetY = 0.0; + + updateVertices(); + } + + /** Sets up glow rendering mode. The 'blur' determines the threshold where the + * blur ends; 'blur + threshold' must not exceed '1.0'. + */ + public function setupGlow(blur:Number=0.2, color:uint=0xffff00, alpha:Number=0.5):void + { + _mode = MODE_GLOW; + _outerThreshold = MathUtil.clamp(_threshold - blur, 0, _threshold); + _outerColor = color; + _outerAlphaStart = MathUtil.clamp(alpha, 0, 1); + _outerAlphaEnd = 0.0; + _shadowOffsetX = _shadowOffsetY = 0.0; + + updateVertices(); + } + + /** Sets up shadow rendering mode. The 'blur' determines the threshold where the drop + * shadow ends; 'offsetX' and 'offsetY' are expected in points. + * + *

Beware that the style can only act within the limits of the mesh's vertices. + * This means that not all combinations of blur and offset are possible; too high values + * will cause the shadow to be cut off on the sides. Reduce either blur or offset to + * compensate.

+ */ + public function setupDropShadow(blur:Number=0.2, offsetX:Number=2, offsetY:Number=2, + color:uint=0x0, alpha:Number=0.5):void + { + const maxOffset:Number = DistanceFieldEffect.MAX_OUTER_OFFSET; + + _mode = MODE_SHADOW; + _outerThreshold = MathUtil.clamp(_threshold - blur, 0, _threshold); + _outerColor = color; + _outerAlphaStart = MathUtil.clamp(alpha, 0, 1); + _outerAlphaEnd = 0.0; + _shadowOffsetX = MathUtil.clamp(offsetX, -maxOffset, maxOffset); + _shadowOffsetY = MathUtil.clamp(offsetY, -maxOffset, maxOffset); + + updateVertices(); + } + + // properties + + /** The current render mode. It's recommended to use one of the 'setup...'-methods to + * change the mode, as those provide useful standard settings, as well. @default basic */ + public function get mode():String { return _mode; } + public function set mode(value:String):void + { + _mode = value; + setRequiresRedraw(); + } + + /** The threshold that will separate the inside from the outside of the shape. On the + * distance field texture, '0' means completely outside, '1' completely inside; the + * actual edge runs along '0.5'. @default 0.5 */ + public function get threshold():Number { return _threshold; } + public function set threshold(value:Number):void + { + value = MathUtil.clamp(value, 0, 1); + + if (_threshold != value) + { + _threshold = value; + updateVertices(); + } + } + + /** Indicates how soft the transition between inside and outside should be rendered. + * A value of '0' will lead to a hard, jagged edge; '1' will be just as blurry as the + * actual distance field texture. The recommend value should be 1.0 / spread + * (you determine the spread when creating the distance field texture). @default 0.125 */ + public function get softness():Number { return _softness; } + public function set softness(value:Number):void + { + value = MathUtil.clamp(value, 0, 1); + + if (_softness != value) + { + _softness = value; + updateVertices(); + } + } + + /** The alpha value with which the inner area (what's rendered in 'basic' mode) is drawn. + * @default 1.0 */ + public function get alpha():Number { return _alpha; } + public function set alpha(value:Number):void + { + value = MathUtil.clamp(value, 0, 1); + + if (_alpha != value) + { + _alpha = value; + updateVertices(); + } + } + + /** The threshold that determines where the outer area (outline, glow, or drop shadow) + * ends. Ignored in 'basic' mode. */ + public function get outerThreshold():Number { return _outerThreshold; } + public function set outerThreshold(value:Number):void + { + value = MathUtil.clamp(value, 0, 1); + + if (_outerThreshold != value) + { + _outerThreshold = value; + updateVertices(); + } + } + + /** The alpha value on the inner side of the outer area's gradient. + * Used for outline, glow, and drop shadow modes. */ + public function get outerAlphaStart():Number { return _outerAlphaStart; } + public function set outerAlphaStart(value:Number):void + { + value = MathUtil.clamp(value, 0, 1); + + if (_outerAlphaStart != value) + { + _outerAlphaStart = value; + updateVertices(); + } + } + + /** The alpha value on the outer side of the outer area's gradient. + * Used for outline, glow, and drop shadow modes. */ + public function get outerAlphaEnd():Number { return _outerAlphaEnd; } + public function set outerAlphaEnd(value:Number):void + { + value = MathUtil.clamp(value, 0, 1); + + if (_outerAlphaEnd != value) + { + _outerAlphaEnd = value; + updateVertices(); + } + } + + /** The color with which the outer area (outline, glow, or drop shadow) will be filled. + * Ignored in 'basic' mode. */ + public function get outerColor():uint { return _outerColor; } + public function set outerColor(value:uint):void + { + if (_outerColor != value) + { + _outerColor = value; + updateVertices(); + } + } + + /** The x-offset of the shadow in points. Note that certain combinations of offset and + * blur value can lead the shadow to be cut off at the edges. Reduce blur or offset to + * counteract. */ + public function get shadowOffsetX():Number { return _shadowOffsetX; } + public function set shadowOffsetX(value:Number):void + { + const max:Number = DistanceFieldEffect.MAX_OUTER_OFFSET; + value = MathUtil.clamp(value, -max, max); + + if (_shadowOffsetX != value) + { + _shadowOffsetX = value; + updateVertices(); + } + } + + /** The y-offset of the shadow in points. Note that certain combinations of offset and + * blur value can lead the shadow to be cut off at the edges. Reduce blur or offset to + * counteract. */ + public function get shadowOffsetY():Number { return _shadowOffsetY; } + public function set shadowOffsetY(value:Number):void + { + const max:Number = DistanceFieldEffect.MAX_OUTER_OFFSET; + value = MathUtil.clamp(value, -max, max); + + if (_shadowOffsetY != value) + { + _shadowOffsetY = value; + updateVertices(); + } + } + } +} + +import flash.display3D.Context3D; +import flash.display3D.Context3DProgramType; + +import starling.rendering.MeshEffect; +import starling.rendering.Program; +import starling.rendering.VertexDataFormat; +import starling.styles.DistanceFieldStyle; +import starling.utils.StringUtil; + +class DistanceFieldEffect extends MeshEffect +{ + public static const VERTEX_FORMAT:VertexDataFormat = DistanceFieldStyle.VERTEX_FORMAT; + public static const MAX_OUTER_OFFSET:int = 8; + public static const MAX_SCALE:int = 8; + + private var _mode:String; + private var _scale:Number; + + private static const sVector:Vector. = new Vector.(4, true); + + public function DistanceFieldEffect() + { + _scale = 1.0; + _mode = DistanceFieldStyle.MODE_BASIC; + } + + override protected function createProgram():Program + { + if (texture) + { + // va0 - position + // va1 - tex coords + // va2 - color + // va3 - basic settings (threshold, alpha, softness, local scale [encoded]) + // va4 - outer settings (outerThreshold, outerAlphaEnd, outerOffsetX/Y) + // va5 - outer color (rgb, outerAlphaStart) + // vc5 - shadow offset multiplier (x, y), max local scale (z), global scale (w) + + var isBasicMode:Boolean = _mode == DistanceFieldStyle.MODE_BASIC; + var isShadowMode:Boolean = _mode == DistanceFieldStyle.MODE_SHADOW; + + /// *** VERTEX SHADER *** + + var vertexShader:Vector. = new [ + "m44 op, va0, vc0", // 4x4 matrix transform to output clip-space + "mov v0, va1 ", // pass texture coordinates to fragment program + "mul v1, va2, vc4", // multiply alpha (vc4) with color (va2), pass to fp + "mov v3, va3 ", + "mov v4, va4 ", + "mov v5, va5 ", + + // update softness to take current scale into account + "mul vt0.x, va3.w, vc5.z", // vt0.x = local scale [decoded] + "mul vt0.x, vt0.x, vc5.w", // vt0.x *= global scale + "div vt0.x, va3.z, vt0.x", // vt0.x = softness / total scale + + // calculate min-max of threshold + "mov vt1, vc4", // initialize vt1 with something (anything) + "sub vt1.x, va3.x, vt0.x", // vt1.x = thresholdMin + "add vt1.y, va3.x, vt0.x" // vt1.y = thresholdMax + ]; + + if (!isBasicMode) + { + vertexShader.push( + // calculate min-max of outer threshold + "sub vt1.z, va4.x, vt0.x", // vt1.z = outerThresholdMin + "add vt1.w, va4.x, vt0.x" // vt1.w = outerThresholdMax + ); + } + + vertexShader.push("sat v6, vt1"); // v6.xyzw = thresholdMin/Max, outerThresholdMin/Max + + if (isShadowMode) + { + vertexShader.push( + // calculate shadow offset + "mul vt0.xy, va4.zw, vc6.zz", // vt0.x/y = outerOffsetX/Y * 2 + "sub vt0.xy, vt0.xy, vc6.yy", // vt0.x/y -= 1 -> range -1, 1 + "mul vt0.xy, vt0.xy, vc5.xy", // vt0.x/y = outerOffsetX/Y in point size + "sub v7, va1, vt0.xyxy", // v7.xy = shadow tex coords + + // on shadows, the inner threshold is further inside than on glow & outline + "sub vt0.z, va3.x, va4.x", // get delta between threshold and outer threshold + "add v7.z, va3.x, vt0.z" // v7.z = inner threshold of shadow + ); + } + + /// *** FRAGMENT SHADER *** + + var fragmentShader:Vector. = new [ + // create basic inner area + tex("ft0", "v0", 0, texture), // ft0 = texture color + "mov ft1, ft0", // ft1 = texture color + step("ft1.w", "v6.x", "v6.y"), // make soft inner mask + "mov ft3, ft1", // store copy of inner mask in ft3 (for outline) + "mul ft1, v1, ft1.wwww" // multiply with color + ]; + + if (isShadowMode) + { + fragmentShader.push( + tex("ft0", "v7", 0, texture), // sample at shadow tex coords + "mov ft5.x, v7.z" // ft5.x = inner threshold of shadow + ); + } + else if (!isBasicMode) + { + fragmentShader.push( + "mov ft5.x, v6.x" // ft5.x = inner threshold of outer area + ); + } + + if (!isBasicMode) + { + fragmentShader.push( + // outer area + "mov ft2, ft0", // ft2 = texture color + step("ft2.w", "v6.z", "v6.w"), // make soft outer mask + "sub ft2.w, ft2.w, ft3.w", // subtract inner area + "sat ft2.w, ft2.w", // but stay within 0-1 + + // add alpha gradient to outer area + "mov ft4, ft0", // ft4 = texture color + step("ft4.w", "v6.z", "ft5.x"), // make soft mask ranging between thresholds + "sub ft6.w, v5.w, v4.y", // ft6.w = alpha range (outerAlphaStart - End) + "mul ft4.w, ft4.w, ft6.w", // ft4.w *= alpha range + "add ft4.w, ft4.w, v4.y", // ft4.w += alpha end + + // colorize outer area + "mul ft2.w, ft2.w, ft4.w", // get final outline alpha at this position + "mul ft2.xyz, v5.xyz, ft2.www" // multiply with outerColor + ); + } + + if (isBasicMode) fragmentShader.push("mov oc, ft1"); + else fragmentShader.push("add oc, ft1, ft2"); + + return Program.fromSource(vertexShader.join("\n"), fragmentShader.join("\n")); + } + else return super.createProgram(); + } + + private static function step(inOutReg:String, minReg:String, maxReg:String, + tmpReg:String="ft6"):String + { + var ops:Vector. = new [ + StringUtil.format("sub {0}, {1}, {2}", tmpReg, maxReg, minReg), // tmpReg = range + StringUtil.format("rcp {0}, {0}", tmpReg), // tmpReg = scale + StringUtil.format("sub {0}, {0}, {1}", inOutReg, minReg), // inOut -= minimum + StringUtil.format("mul {0}, {0}, {1}", inOutReg, tmpReg), // inOut *= scale + StringUtil.format("sat {0}, {0}", inOutReg) // clamp to 0-1 + ]; + + return ops.join("\n"); + } + + override protected function beforeDraw(context:Context3D):void + { + super.beforeDraw(context); + + if (texture) + { + vertexFormat.setVertexBufferAt(3, vertexBuffer, "basic"); + vertexFormat.setVertexBufferAt(4, vertexBuffer, "extended"); + vertexFormat.setVertexBufferAt(5, vertexBuffer, "outerColor"); + + var pixelWidth:Number = 1.0 / (texture.root.nativeWidth / texture.scale); + var pixelHeight:Number = 1.0 / (texture.root.nativeHeight / texture.scale); + + sVector[0] = MAX_OUTER_OFFSET * pixelWidth; + sVector[1] = MAX_OUTER_OFFSET * pixelHeight; + sVector[2] = MAX_SCALE; + sVector[3] = _scale; + + context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 5, sVector); + + sVector[0] = 0.0; + sVector[1] = 1.0; + sVector[2] = 2.0; + + context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 6, sVector); + } + } + + override protected function afterDraw(context:Context3D):void + { + if (texture) + { + context.setVertexBufferAt(3, null); + context.setVertexBufferAt(4, null); + context.setVertexBufferAt(5, null); + } + super.afterDraw(context); + } + + override public function get vertexFormat():VertexDataFormat + { + return VERTEX_FORMAT; + } + + override protected function get programVariantName():uint + { + var modeBits:uint; + + switch (_mode) + { + case DistanceFieldStyle.MODE_SHADOW: modeBits = 3; break; + case DistanceFieldStyle.MODE_GLOW: modeBits = 2; break; + case DistanceFieldStyle.MODE_OUTLINE: modeBits = 1; break; + default: modeBits = 0; + } + + return super.programVariantName | (modeBits << 8); + } + + public function get scale():Number { return _scale; } + public function set scale(value:Number):void { _scale = value; } + + public function get mode():String { return _mode; } + public function set mode(value:String):void { _mode = value; } +} diff --git a/mobile_version/src/starling/styles/MeshStyle.as b/mobile_version/src/starling/styles/MeshStyle.as new file mode 100644 index 00000000..54c477e0 --- /dev/null +++ b/mobile_version/src/starling/styles/MeshStyle.as @@ -0,0 +1,436 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.styles +{ + import flash.display3D.textures.TextureBase; + import flash.geom.Matrix; + import flash.geom.Point; + + import starling.core.starling_internal; + import starling.display.Mesh; + import starling.events.Event; + import starling.events.EventDispatcher; + import starling.rendering.*; + import starling.textures.Texture; + import starling.textures.TextureSmoothing; + + /** Dispatched every frame on styles assigned to display objects connected to the stage. */ + [Event(name="enterFrame", type="starling.events.EnterFrameEvent")] + + /** MeshStyles provide a means to completely modify the way a mesh is rendered. + * The base class provides Starling's standard mesh rendering functionality: colored and + * (optionally) textured meshes. Subclasses may add support for additional features like + * color transformations, normal mapping, etc. + * + *

Using styles

+ * + *

First, create an instance of the desired style. Configure the style by updating its + * properties, then assign it to the mesh. Here is an example that uses a fictitious + * ColorStyle:

+ * + * + * var image:Image = new Image(heroTexture); + * var colorStyle:ColorStyle = new ColorStyle(); + * colorStyle.redOffset = 0.5; + * colorStyle.redMultiplier = 2.0; + * image.style = colorStyle; + * + *

Beware:

+ * + *
    + *
  • A style instance may only be used on one object at a time.
  • + *
  • A style might require the use of a specific vertex format; + * when the style is assigned, the mesh is converted to that format.
  • + *
+ * + *

Creating your own styles

+ * + *

To create custom rendering code in Starling, you need to extend two classes: + * MeshStyle and MeshEffect. While the effect class contains + * the actual AGAL rendering code, the style provides the API that other developers will + * interact with.

+ * + *

Subclasses of MeshStyle will add specific properties that configure the + * style's outcome, like the redOffset and redMultiplier properties + * in the sample above. Here's how to properly create such a class:

+ * + *
    + *
  • Always provide a constructor that can be called without any arguments.
  • + *
  • Override copyFrom — that's necessary for batching.
  • + *
  • Override createEffect — this method must return the + * MeshEffect that will do the actual Stage3D rendering.
  • + *
  • Override updateEffect — this configures the effect created above + * right before rendering.
  • + *
  • Override canBatchWith if necessary — this method figures out if one + * instance of the style can be batched with another. If they all can, you can leave + * this out.
  • + *
+ * + *

If the style requires a custom vertex format, you must also:

+ * + *
    + *
  • add a static constant called VERTEX_FORMAT to the class and
  • + *
  • override get vertexFormat and let it return exactly that format.
  • + *
+ * + *

When that's done, you can turn to the implementation of your MeshEffect; + * the createEffect-override will return an instance of this class. + * Directly before rendering begins, Starling will then call updateEffect + * to set it up.

+ * + * @see MeshEffect + * @see VertexDataFormat + * @see starling.display.Mesh + */ + public class MeshStyle extends EventDispatcher + { + /** The vertex format expected by this style (the same as found in the MeshEffect-class). */ + public static const VERTEX_FORMAT:VertexDataFormat = MeshEffect.VERTEX_FORMAT; + + private var _type:Class; + private var _target:Mesh; + private var _texture:Texture; + private var _textureBase:TextureBase; + private var _textureSmoothing:String; + private var _textureRepeat:Boolean; + private var _vertexData:VertexData; // just a reference to the target's vertex data + private var _indexData:IndexData; // just a reference to the target's index data + + // helper objects + private static var sPoint:Point = new Point(); + + /** Creates a new MeshStyle instance. + * Subclasses must provide a constructor that can be called without any arguments. */ + public function MeshStyle() + { + _textureSmoothing = TextureSmoothing.BILINEAR; + _type = Object(this).constructor as Class; + } + + /** Copies all properties of the given style to the current instance (or a subset, if the + * classes don't match). Must be overridden by all subclasses! + */ + public function copyFrom(meshStyle:MeshStyle):void + { + _texture = meshStyle._texture; + _textureBase = meshStyle._textureBase; + _textureRepeat = meshStyle._textureRepeat; + _textureSmoothing = meshStyle._textureSmoothing; + } + + /** Creates a clone of this instance. The method will work for subclasses automatically, + * no need to override it. */ + public function clone():MeshStyle + { + var clone:MeshStyle = new _type(); + clone.copyFrom(this); + return clone; + } + + /** Creates the effect that does the actual, low-level rendering. + * To be overridden by subclasses! + */ + public function createEffect():MeshEffect + { + return new MeshEffect(); + } + + /** Updates the settings of the given effect to match the current style. + * The given effect will always match the class returned by + * createEffect. + * + *

To be overridden by subclasses!

+ */ + public function updateEffect(effect:MeshEffect, state:RenderState):void + { + effect.texture = _texture; + effect.textureRepeat = _textureRepeat; + effect.textureSmoothing = _textureSmoothing; + effect.mvpMatrix3D = state.mvpMatrix3D; + effect.alpha = state.alpha; + effect.tinted = _vertexData.tinted; + } + + /** Indicates if the current instance can be batched with the given style. + * To be overridden by subclasses if default behavior is not sufficient. + * The base implementation just checks if the styles are of the same type + * and if the textures are compatible. + */ + public function canBatchWith(meshStyle:MeshStyle):Boolean + { + if (_type == meshStyle._type) + { + var newTexture:Texture = meshStyle._texture; + + if (_texture == null && newTexture == null) return true; + else if (_texture && newTexture) + return _textureBase == meshStyle._textureBase && + _textureSmoothing == meshStyle._textureSmoothing && + _textureRepeat == meshStyle._textureRepeat; + else return false; + } + else return false; + } + + /** Copies the vertex data of the style's current target to the target of another style. + * If you pass a matrix, all vertices will be transformed during the process. + * + *

This method is used when batching meshes together for rendering. The parameter + * targetStyle will point to the style of a MeshBatch (a + * subclass of Mesh). Subclasses may override this method if they need + * to modify the vertex data in that process.

+ */ + public function batchVertexData(targetStyle:MeshStyle, targetVertexID:int=0, + matrix:Matrix=null, vertexID:int=0, numVertices:int=-1):void + { + _vertexData.copyTo(targetStyle._vertexData, targetVertexID, matrix, vertexID, numVertices); + } + + /** Copies the index data of the style's current target to the target of another style. + * The given offset value will be added to all indices during the process. + * + *

This method is used when batching meshes together for rendering. The parameter + * targetStyle will point to the style of a MeshBatch (a + * subclass of Mesh). Subclasses may override this method if they need + * to modify the index data in that process.

+ */ + public function batchIndexData(targetStyle:MeshStyle, targetIndexID:int=0, offset:int=0, + indexID:int=0, numIndices:int=-1):void + { + _indexData.copyTo(targetStyle._indexData, targetIndexID, offset, indexID, numIndices); + } + + /** Call this method if the target needs to be redrawn. + * The call is simply forwarded to the target mesh. */ + protected function setRequiresRedraw():void + { + if (_target) _target.setRequiresRedraw(); + } + + /** Call this method when the vertex data changed. + * The call is simply forwarded to the target mesh. */ + protected function setVertexDataChanged():void + { + if (_target) _target.setVertexDataChanged(); + } + + /** Call this method when the index data changed. + * The call is simply forwarded to the target mesh. */ + protected function setIndexDataChanged():void + { + if (_target) _target.setIndexDataChanged(); + } + + /** Called when assigning a target mesh. Override to plug in class-specific logic. */ + protected function onTargetAssigned(target:Mesh):void + { } + + // enter frame event + + override public function addEventListener(type:String, listener:Function):void + { + if (type == Event.ENTER_FRAME && _target) + _target.addEventListener(Event.ENTER_FRAME, onEnterFrame); + + super.addEventListener(type, listener); + } + + override public function removeEventListener(type:String, listener:Function):void + { + if (type == Event.ENTER_FRAME && _target) + _target.removeEventListener(type, onEnterFrame); + + super.removeEventListener(type, listener); + } + + private function onEnterFrame(event:Event):void + { + dispatchEvent(event); + } + + // internal methods + + /** @private */ + starling_internal function setTarget(target:Mesh=null, vertexData:VertexData=null, + indexData:IndexData=null):void + { + if (_target != target) + { + if (_target) _target.removeEventListener(Event.ENTER_FRAME, onEnterFrame); + if (vertexData) vertexData.format = vertexFormat; + + _target = target; + _vertexData = vertexData; + _indexData = indexData; + + if (target) + { + if (hasEventListener(Event.ENTER_FRAME)) + target.addEventListener(Event.ENTER_FRAME, onEnterFrame); + + onTargetAssigned(target); + } + } + } + + // vertex manipulation + + /** The position of the vertex at the specified index, in the mesh's local coordinate + * system. + * + *

Only modify the position of a vertex if you know exactly what you're doing, as + * some classes might not work correctly when their vertices are moved. E.g. the + * Quad class expects its vertices to spawn up a perfectly rectangular + * area; some of its optimized methods won't work correctly if that premise is no longer + * fulfilled or the original bounds change.

+ */ + public function getVertexPosition(vertexID:int, out:Point=null):Point + { + return _vertexData.getPoint(vertexID, "position", out); + } + + public function setVertexPosition(vertexID:int, x:Number, y:Number):void + { + _vertexData.setPoint(vertexID, "position", x, y); + setVertexDataChanged(); + } + + /** Returns the alpha value of the vertex at the specified index. */ + public function getVertexAlpha(vertexID:int):Number + { + return _vertexData.getAlpha(vertexID); + } + + /** Sets the alpha value of the vertex at the specified index to a certain value. */ + public function setVertexAlpha(vertexID:int, alpha:Number):void + { + _vertexData.setAlpha(vertexID, "color", alpha); + setVertexDataChanged(); + } + + /** Returns the RGB color of the vertex at the specified index. */ + public function getVertexColor(vertexID:int):uint + { + return _vertexData.getColor(vertexID); + } + + /** Sets the RGB color of the vertex at the specified index to a certain value. */ + public function setVertexColor(vertexID:int, color:uint):void + { + _vertexData.setColor(vertexID, "color", color); + setVertexDataChanged(); + } + + /** Returns the texture coordinates of the vertex at the specified index. */ + public function getTexCoords(vertexID:int, out:Point = null):Point + { + if (_texture) return _texture.getTexCoords(_vertexData, vertexID, "texCoords", out); + else return _vertexData.getPoint(vertexID, "texCoords", out); + } + + /** Sets the texture coordinates of the vertex at the specified index to the given values. */ + public function setTexCoords(vertexID:int, u:Number, v:Number):void + { + if (_texture) _texture.setTexCoords(_vertexData, vertexID, "texCoords", u, v); + else _vertexData.setPoint(vertexID, "texCoords", u, v); + + setVertexDataChanged(); + } + + // properties + + /** Returns a reference to the vertex data of the assigned target (or null + * if there is no target). Beware: the style itself does not own any vertices; + * it is limited to manipulating those of the target mesh. */ + protected function get vertexData():VertexData { return _vertexData; } + + /** Returns a reference to the index data of the assigned target (or null + * if there is no target). Beware: the style itself does not own any indices; + * it is limited to manipulating those of the target mesh. */ + protected function get indexData():IndexData { return _indexData; } + + /** The actual class of this style. */ + public function get type():Class { return _type; } + + /** Changes the color of all vertices to the same value. + * The getter simply returns the color of the first vertex. */ + public function get color():uint + { + if (_vertexData.numVertices > 0) return _vertexData.getColor(0); + else return 0x0; + } + + public function set color(value:uint):void + { + var i:int; + var numVertices:int = _vertexData.numVertices; + + for (i=0; inull, if there is none). */ + public function get texture():Texture { return _texture; } + public function set texture(value:Texture):void + { + if (value != _texture) + { + if (value) + { + var i:int; + var numVertices:int = _vertexData ? _vertexData.numVertices : 0; + + for (i = 0; i < numVertices; ++i) + { + getTexCoords(i, sPoint); + value.setTexCoords(_vertexData, i, "texCoords", sPoint.x, sPoint.y); + } + + setVertexDataChanged(); + } + else setRequiresRedraw(); + + _texture = value; + _textureBase = value ? value.base : null; + } + } + + /** The smoothing filter that is used for the texture. @default bilinear */ + public function get textureSmoothing():String { return _textureSmoothing; } + public function set textureSmoothing(value:String):void + { + if (value != _textureSmoothing) + { + _textureSmoothing = value; + setRequiresRedraw(); + } + } + + /** Indicates if pixels at the edges will be repeated or clamped. + * Only works for power-of-two textures. @default false */ + public function get textureRepeat():Boolean { return _textureRepeat; } + public function set textureRepeat(value:Boolean):void { _textureRepeat = value; } + + /** The target the style is currently assigned to. */ + public function get target():Mesh { return _target; } + } +} diff --git a/mobile_version/src/starling/text/BitmapChar.as b/mobile_version/src/starling/text/BitmapChar.as new file mode 100644 index 00000000..ac639bb3 --- /dev/null +++ b/mobile_version/src/starling/text/BitmapChar.as @@ -0,0 +1,86 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.text +{ + import flash.utils.Dictionary; + + import starling.display.Image; + import starling.textures.Texture; + + /** A BitmapChar contains the information about one char of a bitmap font. + * You don't have to use this class directly in most cases. + * The TextField class contains methods that handle bitmap fonts for you. + */ + public class BitmapChar + { + private var _texture:Texture; + private var _charID:int; + private var _xOffset:Number; + private var _yOffset:Number; + private var _xAdvance:Number; + private var _kernings:Dictionary; + + /** Creates a char with a texture and its properties. */ + public function BitmapChar(id:int, texture:Texture, + xOffset:Number, yOffset:Number, xAdvance:Number) + { + _charID = id; + _texture = texture; + _xOffset = xOffset; + _yOffset = yOffset; + _xAdvance = xAdvance; + _kernings = null; + } + + /** Adds kerning information relative to a specific other character ID. */ + public function addKerning(charID:int, amount:Number):void + { + if (_kernings == null) + _kernings = new Dictionary(); + + _kernings[charID] = amount; + } + + /** Retrieve kerning information relative to the given character ID. */ + public function getKerning(charID:int):Number + { + if (_kernings == null || _kernings[charID] == undefined) return 0.0; + else return _kernings[charID]; + } + + /** Creates an image of the char. */ + public function createImage():Image + { + return new Image(_texture); + } + + /** The unicode ID of the char. */ + public function get charID():int { return _charID; } + + /** The number of points to move the char in x direction on character arrangement. */ + public function get xOffset():Number { return _xOffset; } + + /** The number of points to move the char in y direction on character arrangement. */ + public function get yOffset():Number { return _yOffset; } + + /** The number of points the cursor has to be moved to the right for the next char. */ + public function get xAdvance():Number { return _xAdvance; } + + /** The texture of the character. */ + public function get texture():Texture { return _texture; } + + /** The width of the character in points. */ + public function get width():Number { return _texture.width; } + + /** The height of the character in points. */ + public function get height():Number { return _texture.height; } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/text/BitmapFont.as b/mobile_version/src/starling/text/BitmapFont.as new file mode 100644 index 00000000..56872d75 --- /dev/null +++ b/mobile_version/src/starling/text/BitmapFont.as @@ -0,0 +1,548 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.text +{ + import flash.geom.Rectangle; + import flash.utils.Dictionary; + + import starling.display.Image; + import starling.display.MeshBatch; + import starling.display.Sprite; + import starling.textures.Texture; + import starling.textures.TextureSmoothing; + import starling.utils.Align; + import starling.utils.StringUtil; + + /** The BitmapFont 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 or + * the Glyph Designer. + * This is what the file format looks like: + * + *
 
+     *  <font>
+     *    <info face="BranchingMouse" size="40" />
+     *    <common lineHeight="40" />
+     *    <pages>  <!-- currently, only one page is supported -->
+     *      <page id="0" file="texture.png" />
+     *    </pages>
+     *    <chars>
+     *      <char id="32" x="60" y="29" width="1" height="1" xoffset="0" yoffset="27" xadvance="8" />
+     *      <char id="33" x="155" y="144" width="9" height="21" xoffset="0" yoffset="6" xadvance="9" />
+     *    </chars>
+     *    <kernings> <!-- Kerning is optional -->
+     *      <kerning first="83" second="83" amount="-4"/>
+     *    </kernings>
+     *  </font>
+     *  
+ * + * Pass an instance of this class to the method registerBitmapFont of the + * TextField class. Then, set the fontName property of the text field to the + * name value of the bitmap font. This will make the text field use the bitmap + * font. + */ + public class BitmapFont implements ITextCompositor + { + /** Use this constant for the fontSize property of the TextField class to + * render the bitmap font in exactly the size it was created. */ + public static const NATIVE_SIZE:int = -1; + + /** The font name of the embedded minimal bitmap font. Use this e.g. for debug output. */ + public static const MINI:String = "mini"; + + private static const CHAR_SPACE:int = 32; + private static const CHAR_TAB:int = 9; + private static const CHAR_NEWLINE:int = 10; + private static const CHAR_CARRIAGE_RETURN:int = 13; + + private var _texture:Texture; + private var _chars:Dictionary; + private var _name:String; + private var _size:Number; + private var _lineHeight:Number; + private var _baseline:Number; + private var _offsetX:Number; + private var _offsetY:Number; + private var _padding:Number; + private var _helperImage:Image; + + // helper objects + private static var sLines:Array = []; + private static var sDefaultOptions:TextOptions = new TextOptions(); + + /** Creates a bitmap font by parsing an XML file and uses the specified texture. + * If you don't pass any data, the "mini" font will be created. */ + public function BitmapFont(texture:Texture=null, fontXml:XML=null) + { + // if no texture is passed in, we create the minimal, embedded font + if (texture == null && fontXml == null) + { + texture = MiniBitmapFont.texture; + fontXml = MiniBitmapFont.xml; + } + else if (texture == null || fontXml == null) + { + throw new ArgumentError("Set both of the 'texture' and 'fontXml' arguments to valid objects or leave both of them null."); + } + + _name = "unknown"; + _lineHeight = _size = _baseline = 14; + _offsetX = _offsetY = _padding = 0.0; + _texture = texture; + _chars = new Dictionary(); + _helperImage = new Image(texture); + + parseFontXml(fontXml); + } + + /** Disposes the texture of the bitmap font. */ + public function dispose():void + { + if (_texture) + _texture.dispose(); + } + + private function parseFontXml(fontXml:XML):void + { + var scale:Number = _texture.scale; + var frame:Rectangle = _texture.frame; + var frameX:Number = frame ? frame.x : 0; + var frameY:Number = frame ? frame.y : 0; + + _name = StringUtil.clean(fontXml.info.@face); + _size = parseFloat(fontXml.info.@size) / scale; + _lineHeight = parseFloat(fontXml.common.@lineHeight) / scale; + _baseline = parseFloat(fontXml.common.@base) / scale; + + if (fontXml.info.@smooth.toString() == "0") + smoothing = TextureSmoothing.NONE; + + if (_size <= 0) + { + trace("[Starling] Warning: invalid font size in '" + _name + "' font."); + _size = (_size == 0.0 ? 16.0 : _size * -1.0); + } + + for each (var charElement:XML in fontXml.chars.char) + { + var id:int = parseInt(charElement.@id); + var xOffset:Number = parseFloat(charElement.@xoffset) / scale; + var yOffset:Number = parseFloat(charElement.@yoffset) / scale; + var xAdvance:Number = parseFloat(charElement.@xadvance) / scale; + + var region:Rectangle = new Rectangle(); + region.x = parseFloat(charElement.@x) / scale + frameX; + region.y = parseFloat(charElement.@y) / scale + frameY; + region.width = parseFloat(charElement.@width) / scale; + region.height = parseFloat(charElement.@height) / scale; + + var texture:Texture = Texture.fromTexture(_texture, region); + var bitmapChar:BitmapChar = new BitmapChar(id, texture, xOffset, yOffset, xAdvance); + addChar(id, bitmapChar); + } + + for each (var kerningElement:XML in fontXml.kernings.kerning) + { + var first:int = parseInt(kerningElement.@first); + var second:int = parseInt(kerningElement.@second); + var amount:Number = parseFloat(kerningElement.@amount) / scale; + if (second in _chars) getChar(second).addKerning(first, amount); + } + } + + /** Returns a single bitmap char with a certain character ID. */ + public function getChar(charID:int):BitmapChar + { + return _chars[charID]; + } + + /** Adds a bitmap char with a certain character ID. */ + public function addChar(charID:int, bitmapChar:BitmapChar):void + { + _chars[charID] = bitmapChar; + } + + /** Returns a vector containing all the character IDs that are contained in this font. */ + public function getCharIDs(out:Vector.=null):Vector. + { + if (out == null) out = new []; + + for(var key:* in _chars) + out[out.length] = int(key); + + return out; + } + + /** Checks whether a provided string can be displayed with the font. */ + public function hasChars(text:String):Boolean + { + if (text == null) return true; + + var charID:int; + var numChars:int = text.length; + + for (var i:int=0; i = arrangeChars(width, height, text, format, options); + var numChars:int = charLocations.length; + var smoothing:String = this.smoothing; + var sprite:Sprite = new Sprite(); + + for (var i:int=0; i = arrangeChars( + width, height, text, format, options); + var numChars:int = charLocations.length; + _helperImage.color = format.color; + + for (var i:int=0; i + { + if (text == null || text.length == 0) return CharLocation.vectorFromPool(); + if (options == null) options = sDefaultOptions; + + var kerning:Boolean = format.kerning; + var leading:Number = format.leading; + var hAlign:String = format.horizontalAlign; + var vAlign:String = format.verticalAlign; + var fontSize:Number = format.size; + var autoScale:Boolean = options.autoScale; + var wordWrap:Boolean = options.wordWrap; + + var finished:Boolean = false; + var charLocation:CharLocation; + var numChars:int; + var containerWidth:Number; + var containerHeight:Number; + var scale:Number; + var i:int, j:int; + + if (fontSize < 0) fontSize *= -_size; + + while (!finished) + { + sLines.length = 0; + scale = fontSize / _size; + containerWidth = (width - 2 * _padding) / scale; + containerHeight = (height - 2 * _padding) / scale; + + if (_lineHeight <= containerHeight) + { + var lastWhiteSpace:int = -1; + var lastCharID:int = -1; + var currentX:Number = 0; + var currentY:Number = 0; + var currentLine:Vector. = CharLocation.vectorFromPool(); + + numChars = text.length; + for (i=0; i containerWidth) + { + if (wordWrap) + { + // when autoscaling, we must not split a word in half -> restart + if (autoScale && lastWhiteSpace == -1) + break; + + // remove characters and add them again to next line + var numCharsToRemove:int = lastWhiteSpace == -1 ? 1 : i - lastWhiteSpace; + + for (j=0; j 3) + fontSize -= 1; + else + finished = true; + } // while (!finished) + + var finalLocations:Vector. = CharLocation.vectorFromPool(); + var numLines:int = sLines.length; + var bottom:Number = currentY + _lineHeight; + var yOffset:int = 0; + + if (vAlign == Align.BOTTOM) yOffset = containerHeight - bottom; + else if (vAlign == Align.CENTER) yOffset = (containerHeight - bottom) / 2; + + for (var lineID:int=0; lineID = sLines[lineID]; + numChars = line.length; + + if (numChars == 0) continue; + + var xOffset:int = 0; + var lastLocation:CharLocation = line[line.length-1]; + var right:Number = lastLocation.x - lastLocation.char.xOffset + + lastLocation.char.xAdvance; + + if (hAlign == Align.RIGHT) xOffset = containerWidth - right; + else if (hAlign == Align.CENTER) xOffset = (containerWidth - right) / 2; + + for (var c:int=0; c 0 && charLocation.char.height > 0) + finalLocations[finalLocations.length] = charLocation; + } + } + + return finalLocations; + } + + /** The name of the font as it was parsed from the font file. */ + public function get name():String { return _name; } + + /** The native size of the font. */ + public function get size():Number { return _size; } + + /** The height of one line in points. */ + public function get lineHeight():Number { return _lineHeight; } + public function set lineHeight(value:Number):void { _lineHeight = value; } + + /** The smoothing filter that is used for the texture. */ + public function get smoothing():String { return _helperImage.textureSmoothing; } + public function set smoothing(value:String):void { _helperImage.textureSmoothing = value; } + + /** The baseline of the font. This property does not affect text rendering; + * it's just an information that may be useful for exact text placement. */ + public function get baseline():Number { return _baseline; } + public function set baseline(value:Number):void { _baseline = value; } + + /** An offset that moves any generated text along the x-axis (in points). + * Useful to make up for incorrect font data. @default 0. */ + public function get offsetX():Number { return _offsetX; } + public function set offsetX(value:Number):void { _offsetX = value; } + + /** An offset that moves any generated text along the y-axis (in points). + * Useful to make up for incorrect font data. @default 0. */ + public function get offsetY():Number { return _offsetY; } + public function set offsetY(value:Number):void { _offsetY = value; } + + /** The width of a "gutter" around the composed text area, in points. + * This can be used to bring the output more in line with standard TrueType rendering: + * Flash always draws them with 2 pixels of padding. @default 0.0 */ + public function get padding():Number { return _padding; } + public function set padding(value:Number):void { _padding = value; } + + /** The underlying texture that contains all the chars. */ + public function get texture():Texture { return _texture; } + } +} + +import starling.text.BitmapChar; + +class CharLocation +{ + public var char:BitmapChar; + public var scale:Number; + public var x:Number; + public var y:Number; + + public function CharLocation(char:BitmapChar) + { + reset(char); + } + + private function reset(char:BitmapChar):CharLocation + { + this.char = char; + return this; + } + + // pooling + + private static var sInstancePool:Vector. = new []; + private static var sVectorPool:Array = []; + + private static var sInstanceLoan:Vector. = new []; + private static var sVectorLoan:Array = []; + + public static function instanceFromPool(char:BitmapChar):CharLocation + { + var instance:CharLocation = sInstancePool.length > 0 ? + sInstancePool.pop() : new CharLocation(char); + + instance.reset(char); + sInstanceLoan[sInstanceLoan.length] = instance; + + return instance; + } + + public static function vectorFromPool():Vector. + { + var vector:Vector. = sVectorPool.length > 0 ? + sVectorPool.pop() : new []; + + vector.length = 0; + sVectorLoan[sVectorLoan.length] = vector; + + return vector; + } + + public static function rechargePool():void + { + var instance:CharLocation; + var vector:Vector.; + + while (sInstanceLoan.length > 0) + { + instance = sInstanceLoan.pop(); + instance.char = null; + sInstancePool[sInstancePool.length] = instance; + } + + while (sVectorLoan.length > 0) + { + vector = sVectorLoan.pop(); + vector.length = 0; + sVectorPool[sVectorPool.length] = vector; + } + } +} diff --git a/mobile_version/src/starling/text/ITextCompositor.as b/mobile_version/src/starling/text/ITextCompositor.as new file mode 100644 index 00000000..e438868b --- /dev/null +++ b/mobile_version/src/starling/text/ITextCompositor.as @@ -0,0 +1,29 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.text +{ + import starling.display.MeshBatch; + + /** A text compositor arranges letters for Starling's TextField. */ + public interface ITextCompositor + { + /** Draws the given text into a MeshBatch, using the supplied format and options. */ + function fillMeshBatch(meshBatch:MeshBatch, width:Number, height:Number, text:String, + format:TextFormat, options:TextOptions=null):void; + + /** Clears the MeshBatch (filled by the same class) and disposes any resources that + * are no longer needed. */ + function clearMeshBatch(meshBatch:MeshBatch):void; + + /** Frees all resources allocated by the compositor. */ + function dispose():void; + } +} diff --git a/mobile_version/src/starling/text/MiniBitmapFont.as b/mobile_version/src/starling/text/MiniBitmapFont.as new file mode 100644 index 00000000..084faed8 --- /dev/null +++ b/mobile_version/src/starling/text/MiniBitmapFont.as @@ -0,0 +1,314 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.text +{ + import flash.display.BitmapData; + import flash.display3D.Context3DTextureFormat; + import flash.geom.Rectangle; + import flash.utils.ByteArray; + + import starling.textures.Texture; + + /** @private + * This class contains constants for the 'MINI' bitmap font. It's done that way to avoid + * a dependency on the 'mx.core' library (which is required for the 'Embed' statement). + * + *

The font is based on "uni05_53.ttf" from Craig Kroeger (http://www.miniml.com) and was + * converted to a Bitmap Font with "GlyphDesigner" from 71squared (http://www.71squared.com). + *

*/ + internal class MiniBitmapFont + { + private static const BITMAP_WIDTH:int = 128; + private static const BITMAP_HEIGHT:int = 64; + private static const BITMAP_DATA:Array = [ + 2027613533, 3413039936, 202148514, 2266925598, 4206886452, 4286853117, 2034947, + 3202703399, 352977282, 2957757964, 3113652880, 2158068882, 1468709156, 2268063717, + 2779310143, 2101025806, 3416509055, 4215794539, 3602168838, 1038056207, 1932393374, + 3182285627, 3086802234, 1741291262, 2017257123, 3395280843, 984074419, 3049693147, + 3986077023, 1055013549, 1806563255, 1754714962, 1577746187, 1124058786, 3888759258, + 2482229043, 2916583666, 3743065328, 866060213, 1695195001, 2401582068, 3113347901, + 2616521596, 1053798161, 2093370968, 4229025683, 560451479, 854767518, 2610241322, + 4279041348, 4181572480, 4031244973, 587139110, 1081376765, 962217926, 783603325, + 3605526425, 4102001916, 289204733, 2635140255, 3453981695, 3487854373, 2132197241, + 3164775074, 4257640328, 770238970, 144664537, 707141570, 2934433071, 871272893, + 512964596, 808491899, 481894297, 3095982481, 3598364156, 1710636358, 2904016319, + 1751040139, 596966466, 1363963692, 465815609, 315567311, 4290666159, 4086022551, + 179721458, 2221734970, 3942224988, 1519355876, 3292323782, 3933427230, 3314199893, + 3736227348, 3846038425, 603088884, 2677349227, 3207069327, 3555275967, 3063054283, + 3064577213, 3412044179, 693642210, 4280513949, 762928717, 1802215333, 3774849674, + 4221155330, 970959395, 557220237, 2107226136, 3509822982, 3403284788, 4265820019, + 898597576, 991077243, 2091615904, 3334716888, 633599866, 4218780109, 2216000376, + 834870947, 2118009742, 1362731961, 236280636, 1274945142, 1458729366, 797960805, + 3289369720, 2103717340, 3946406003, 2676522889, 1624104606, 1156993903, 3186170404, + 2254499071, 1204911924, 1314218830, 3307086392, 2824275959, 3839865679, 2073394964, + 1873329433, 1754205930, 1528429545, 1631106062, 2263272465, 4220497047, 3522893765, + 3641376303, 707451487, 3452496787, 1390653868, 2620555793, 1027328684, 3419683476, + 3662193703, 765701986, 3808279132, 786403271, 3824435837, 713234896, 4261856399, + 3471930731, 3993492879, 1447960461, 1398434593, 1914230187, 2398643285, 4156374464, + 3859339207, 3220700061, 3373248762, 3186030434, 1315917060, 2809852481, 4008553903, + 4105611953, 1599499652, 3513857591, 877854499, 4198259455, 3648560077, 2838035419, + 3255594190, 2465578457, 4263505201, 534904657, 2889261598, 1358214576, 1069250354, + 3870010557, 2628896583, 3448610878, 442343309, 1024736866, 4015119133, 3250867279, + 1513359261, 2442089596, 1944476762, 735490552, 426990058, 4234106111, 1204305707, + 3330995265, 2398649368, 4221048123, 1724669255, 3801115709, 3489328790, 3896402933, + 3696936939, 2836983295, 3656750393, 3349724512, 3810416287, 3654997608, 4284455103, + 2294939563, 4207697932, 642748805, 2476981639, 2319419898, 572956615, 3833238940, + 964924880, 2081600351, 3572458416, 2056247513, 1951368808, 2133449703, 2783728628, + 512866577, 913279200, 1678129016, 3488578991, 3373952929, 2562996951, 3666058925, + 1664169178, 1943591935, 750675303, 154399903, 2571590890, 852654952, 4117307766, + 1971649621, 4180195820, 1222535348, 4283953215, 2880662236, 2717410980, 1175907705, + 1157322027, 505963121, 2631540616, 3661227656, 3591803353, 2624126821, 1948662907, + 3596065103, 1147387734, 256773959, 1173572460, 2361957471, 4210876076, 3080180620, + 3464801210, 3821654259, 1465302035, 2851185457, 3143266144, 3793180414, 3368833103, + 4274670712, 3473819108, 3487569332, 773123355, 1618635668, 2570176190, 2075248691, + 1740805534, 288646743, 1837597401, 603556968, 3182536872, 673184603, 3088757053, + 2897054404, 3192651316, 2885335802, 1057233368, 1118437241, 4182126463, 3110464775, + 3313191614, 2360987274, 735505357, 2992631425, 2360928811, 4187834527, 279183208, + 1586420003, 1174008423, 4062987589, 1162167621, 1162167621, 1162167621, 1162167621, + 1174119799, 787274608 + ]; + + private static const XML_DATA:XML = + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ; + + public static function get texture():Texture + { + var bitmapData:BitmapData = getBitmapData(); + var format:String = Context3DTextureFormat.BGRA_PACKED; + var texture:Texture = Texture.fromBitmapData(bitmapData, false, false, 1, format); + bitmapData.dispose(); + bitmapData = null; + + texture.root.onRestore = function():void + { + bitmapData = getBitmapData(); + texture.root.uploadBitmapData(bitmapData); + bitmapData.dispose(); + bitmapData = null; + }; + + return texture; + } + + private static function getBitmapData():BitmapData + { + var bmpData:BitmapData = new BitmapData(BITMAP_WIDTH, BITMAP_HEIGHT); + var bmpBytes:ByteArray = new ByteArray(); + var numBytes:int = BITMAP_DATA.length; + + for (var i:int=0; iAccess the format property to modify the appearance of the text, like the + * font name and size, a color, the horizontal and vertical alignment, etc. The border property + * is useful during development, because it lets you see the bounds of the TextField.

+ * + *

There are several types of fonts that can be displayed:

+ * + *
    + *
  • Standard TrueType fonts. This renders the text just like a conventional Flash + * TextField. It is recommended to embed the font, since you cannot be sure which fonts + * are available on the client system, and since this enhances rendering quality. + * 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 registerBitmapFont, and then pass + * the font name to the corresponding property of the text field.
  • + *
  • Custom text compositors. Any class implementing the ITextCompositor + * interface can be used to render text. If the two standard options are not sufficient + * for your needs, such a compositor might do the trick.
  • + *
+ * + *

For bitmap fonts, we recommend one of the following tools:

+ * + *
    + *
  • Windows: Bitmap Font Generator + * from Angel Code (free). Export the font data as an XML file and the texture as a png + * with white characters on a transparent background (32 bit).
  • + *
  • Mac OS: Glyph Designer from + * 71squared or bmGlyph (both commercial). + * They support Starling natively.
  • + *
  • Cross-Platform: Littera or + * ShoeBox are great tools, as well. + * Both are free to use and were built with Adobe AIR.
  • + *
+ * + *

When using a bitmap font, the 'color' property is used to tint the font texture. This + * works by multiplying the RGB values of that property with those of the texture's pixel. + * If your font contains just a single color, export it in plain white and change the 'color' + * property to any value you like (it defaults to zero, which means black). If your font + * contains multiple colors, change the 'color' property to Color.WHITE to get + * the intended result.

+ * + * Batching of TextFields + * + *

Normally, TextFields will require exactly one draw call. For TrueType fonts, you cannot + * avoid that; bitmap fonts, however, may be batched if you enable the "batchable" property. + * This makes sense if you have several TextFields with short texts that are rendered one + * after the other (e.g. subsequent children of the same sprite), or if your bitmap font + * texture is in your main texture atlas.

+ * + *

The recommendation is to activate "batchable" if it reduces your draw calls (use the + * StatsDisplay to check this) AND if the text fields contain no more than about 15-20 + * characters. For longer texts, the batching would take up more CPU time than what is saved + * by avoiding the draw calls.

+ */ + public class TextField extends DisplayObjectContainer + { + // the name of the "sharedData" container with the registered compositors + private static const COMPOSITOR_DATA_NAME:String = "starling.display.TextField.compositors"; + + private var _text:String; + private var _options:TextOptions; + private var _format:TextFormat; + private var _textBounds:Rectangle; + private var _hitArea:Rectangle; + private var _compositor:ITextCompositor; + private var _requiresRecomposition:Boolean; + private var _border:DisplayObjectContainer; + private var _meshBatch:MeshBatch; + private var _style:MeshStyle; + + // helper objects + private static var sMatrix:Matrix = new Matrix(); + private static var sDefaultCompositor:ITextCompositor = new TrueTypeCompositor(); + private static var sDefaultTextureFormat:String = Context3DTextureFormat.BGRA_PACKED; + private var _helperFormat:TextFormat = new TextFormat(); + + /** Create a new text field with the given properties. */ + public function TextField(width:int, height:int, text:String="", format:TextFormat=null) + { + _text = text ? text : ""; + _hitArea = new Rectangle(0, 0, width, height); + _requiresRecomposition = true; + _compositor = sDefaultCompositor; + _options = new TextOptions(); + + _format = format ? format.clone() : new TextFormat(); + _format.addEventListener(Event.CHANGE, setRequiresRecomposition); + + _meshBatch = new MeshBatch(); + _meshBatch.touchable = false; + _meshBatch.pixelSnapping = true; + addChild(_meshBatch); + } + + /** Disposes the underlying texture data. */ + public override function dispose():void + { + _format.removeEventListener(Event.CHANGE, setRequiresRecomposition); + _compositor.clearMeshBatch(_meshBatch); + + super.dispose(); + } + + /** @inheritDoc */ + public override function render(painter:Painter):void + { + if (_requiresRecomposition) recompose(); + super.render(painter); + } + + /** Forces the text contents to be composed right away. + * Normally, it will only do so lazily, i.e. before being rendered. */ + private function recompose():void + { + if (_requiresRecomposition) + { + _compositor.clearMeshBatch(_meshBatch); + + var fontName:String = _format.font; + var compositor:ITextCompositor = getCompositor(fontName); + + if (compositor == null && fontName == BitmapFont.MINI) + { + compositor = new BitmapFont(); + registerCompositor(compositor, fontName); + } + + _compositor = compositor ? compositor : sDefaultCompositor; + + updateText(); + updateBorder(); + + _requiresRecomposition = false; + } + } + + // font and border rendering + + private function updateText():void + { + var width:Number = _hitArea.width; + var height:Number = _hitArea.height; + var format:TextFormat = _helperFormat; + + // By working on a copy of the TextFormat, we make sure that modifications done + // within the 'fillMeshBatch' method do not cause any side effects. + // + // (We cannot use a static variable, because that might lead to problems when + // recreating textures after a context loss.) + + format.copyFrom(_format); + + // Horizontal autoSize does not work for HTML text, since it supports custom alignment. + // What should we do if one line is aligned to the left, another to the right? + + if (isHorizontalAutoSize && !_options.isHtmlText) width = 100000; + if (isVerticalAutoSize) height = 100000; + + _meshBatch.x = _meshBatch.y = 0; + _options.textureScale = Starling.contentScaleFactor; + _options.textureFormat = sDefaultTextureFormat; + _compositor.fillMeshBatch(_meshBatch, width, height, _text, format, _options); + + if (_style) _meshBatch.style = _style; + if (_options.autoSize != TextFieldAutoSize.NONE) + { + _textBounds = _meshBatch.getBounds(_meshBatch, _textBounds); + + if (isHorizontalAutoSize) + { + _meshBatch.x = _textBounds.x = -_textBounds.x; + _hitArea.width = _textBounds.width; + _textBounds.x = 0; + } + + if (isVerticalAutoSize) + { + _meshBatch.y = _textBounds.y = -_textBounds.y; + _hitArea.height = _textBounds.height; + _textBounds.y = 0; + } + } + else + { + // hit area doesn't change, and text bounds can be created on demand + _textBounds = null; + } + } + + private function updateBorder():void + { + if (_border == null) return; + + var width:Number = _hitArea.width; + var height:Number = _hitArea.height; + + var topLine:Quad = _border.getChildAt(0) as Quad; + var rightLine:Quad = _border.getChildAt(1) as Quad; + var bottomLine:Quad = _border.getChildAt(2) as Quad; + var leftLine:Quad = _border.getChildAt(3) as Quad; + + topLine.width = width; topLine.height = 1; + bottomLine.width = width; bottomLine.height = 1; + leftLine.width = 1; leftLine.height = height; + rightLine.width = 1; rightLine.height = height; + rightLine.x = width - 1; + bottomLine.y = height - 1; + topLine.color = rightLine.color = bottomLine.color = leftLine.color = _format.color; + } + + /** Forces the text to be recomposed before rendering it in the upcoming frame. */ + protected function setRequiresRecomposition():void + { + _requiresRecomposition = true; + setRequiresRedraw(); + } + + // properties + + private function get isHorizontalAutoSize():Boolean + { + return _options.autoSize == TextFieldAutoSize.HORIZONTAL || + _options.autoSize == TextFieldAutoSize.BOTH_DIRECTIONS; + } + + private function get isVerticalAutoSize():Boolean + { + return _options.autoSize == TextFieldAutoSize.VERTICAL || + _options.autoSize == TextFieldAutoSize.BOTH_DIRECTIONS; + } + + /** Returns the bounds of the text within the text field. */ + public function get textBounds():Rectangle + { + if (_requiresRecomposition) recompose(); + if (_textBounds == null) _textBounds = _meshBatch.getBounds(this); + return _textBounds.clone(); + } + + /** @inheritDoc */ + public override function getBounds(targetSpace:DisplayObject, out:Rectangle=null):Rectangle + { + if (_requiresRecomposition) recompose(); + getTransformationMatrix(targetSpace, sMatrix); + return RectangleUtil.getBounds(_hitArea, sMatrix, out); + } + + /** @inheritDoc */ + public override function hitTest(localPoint:Point):DisplayObject + { + if (!visible || !touchable || !hitTestMask(localPoint)) return null; + else if (_hitArea.containsPoint(localPoint)) return this; + else return null; + } + + /** @inheritDoc */ + public override function set width(value:Number):void + { + // different to ordinary display objects, changing the size of the text field should + // not change the scaling, but make the texture bigger/smaller, while the size + // of the text/font stays the same (this applies to the height, as well). + + _hitArea.width = value / (scaleX || 1.0); + setRequiresRecomposition(); + } + + /** @inheritDoc */ + public override function set height(value:Number):void + { + _hitArea.height = value / (scaleY || 1.0); + setRequiresRecomposition(); + } + + /** The displayed text. */ + public function get text():String { return _text; } + public function set text(value:String):void + { + if (value == null) value = ""; + if (_text != value) + { + _text = value; + setRequiresRecomposition(); + } + } + + /** The format describes how the text will be rendered, describing the font name and size, + * color, alignment, etc. + * + *

Note that you can edit the font properties directly; there's no need to reassign + * the format for the changes to show up.

+ * + * + * var textField:TextField = new TextField(100, 30, "Hello Starling"); + * textField.format.font = "Arial"; + * textField.format.color = Color.RED; + * + * @default Verdana, 12 pt, black, centered + */ + public function get format():TextFormat { return _format; } + public function set format(value:TextFormat):void + { + if (value == null) throw new ArgumentError("format cannot be null"); + _format.copyFrom(value); + } + + /** Draws a border around the edges of the text field. Useful for visual debugging. + * @default false */ + public function get border():Boolean { return _border != null; } + public function set border(value:Boolean):void + { + if (value && _border == null) + { + _border = new Sprite(); + addChild(_border); + + for (var i:int=0; i<4; ++i) + _border.addChild(new Quad(1.0, 1.0)); + + updateBorder(); + } + else if (!value && _border != null) + { + _border.removeFromParent(true); + _border = null; + } + } + + /** Indicates whether the font size is automatically reduced if the complete text does + * not fit into the TextField. @default false */ + public function get autoScale():Boolean { return _options.autoScale; } + public function set autoScale(value:Boolean):void + { + if (_options.autoScale != value) + { + _options.autoScale = value; + setRequiresRecomposition(); + } + } + + /** Specifies the type of auto-sizing the TextField will do. + * Note that any auto-sizing will implicitly deactivate all auto-scaling. + * @default none */ + public function get autoSize():String { return _options.autoSize; } + public function set autoSize(value:String):void + { + if (_options.autoSize != value) + { + _options.autoSize = value; + setRequiresRecomposition(); + } + } + + /** Indicates if the text should be wrapped at word boundaries if it does not fit into + * the TextField otherwise. @default true */ + public function get wordWrap():Boolean { return _options.wordWrap; } + public function set wordWrap(value:Boolean):void + { + if (value != _options.wordWrap) + { + _options.wordWrap = value; + setRequiresRecomposition(); + } + } + + /** Indicates if TextField should be batched on rendering. + * + *

This works only with bitmap fonts, and it makes sense only for TextFields with no + * more than 10-15 characters. Otherwise, the CPU costs will exceed any gains you get + * from avoiding the additional draw call.

+ * + * @default false + */ + public function get batchable():Boolean { return _meshBatch.batchable; } + public function set batchable(value:Boolean):void + { + _meshBatch.batchable = value; + } + + /** Indicates if text should be interpreted as HTML code. For a description + * of the supported HTML subset, refer to the classic Flash 'TextField' documentation. + * Clickable hyperlinks and external images are not supported. Only works for + * TrueType fonts! @default false */ + public function get isHtmlText():Boolean { return _options.isHtmlText; } + public function set isHtmlText(value:Boolean):void + { + if (_options.isHtmlText != value) + { + _options.isHtmlText = value; + setRequiresRecomposition(); + } + } + + /** Controls whether or not the instance snaps to the nearest pixel. This can prevent the + * object from looking blurry when it's not exactly aligned with the pixels of the screen. + * @default true */ + public function get pixelSnapping():Boolean { return _meshBatch.pixelSnapping; } + public function set pixelSnapping(value:Boolean):void { _meshBatch.pixelSnapping = value } + + /** The style that is used to render the text's mesh. */ + public function get style():MeshStyle { return _meshBatch.style; } + public function set style(value:MeshStyle):void + { + _meshBatch.style = _style = value; + setRequiresRecomposition(); + } + + /** The Context3D texture format that is used for rendering of all TrueType texts. + * The default provides a good compromise between quality and memory consumption; + * use
Context3DTextureFormat.BGRA
for the highest quality. + * + * @default Context3DTextureFormat.BGRA_PACKED */ + public static function get defaultTextureFormat():String { return sDefaultTextureFormat; } + public static function set defaultTextureFormat(value:String):void + { + sDefaultTextureFormat = value; + } + + /** The default compositor used to arrange the letters of the text. + * If a specific compositor was registered for a font, it takes precedence. + * + * @default TrueTypeCompositor + */ + public static function get defaultCompositor():ITextCompositor { return sDefaultCompositor; } + public static function set defaultCompositor(value:ITextCompositor):void + { + sDefaultCompositor = value; + } + + /** Updates the list of embedded fonts. Call this method when you loaded a TrueType font + * at runtime so that Starling can recognize it as such. */ + public static function updateEmbeddedFonts():void + { + SystemUtil.updateEmbeddedFonts(); + } + + // compositor registration + + /** Makes a text compositor (like a BitmapFont) available to any TextField in + * the current stage3D context. The font is identified by its name (not + * case sensitive). */ + public static function registerCompositor(compositor:ITextCompositor, name:String):void + { + if (name == null) throw new ArgumentError("name must not be null"); + compositors[convertToLowerCase(name)] = compositor; + } + + /** Unregisters the text compositor and, optionally, disposes it. */ + public static function unregisterCompositor(name:String, dispose:Boolean=true):void + { + name = convertToLowerCase(name); + + if (dispose && compositors[name] != undefined) + compositors[name].dispose(); + + delete compositors[name]; + } + + /** Returns a registered text compositor (or null, if the font has not been registered). + * The name is not case sensitive. */ + public static function getCompositor(name:String):ITextCompositor + { + return compositors[convertToLowerCase(name)]; + } + + /** Makes a bitmap font available at any TextField in the current stage3D context. + * The font is identified by its name (not case sensitive). + * Per default, the name property of the bitmap font will be used, but you + * can pass a custom name, as well. @return the name of the font. */ + [Deprecated(replacement="registerCompositor")] + public static function registerBitmapFont(bitmapFont:BitmapFont, name:String=null):String + { + if (name == null) name = bitmapFont.name; + registerCompositor(bitmapFont, name); + return name; + } + + /** Unregisters the bitmap font and, optionally, disposes it. */ + [Deprecated(replacement="unregisterCompositor")] + public static function unregisterBitmapFont(name:String, dispose:Boolean=true):void + { + unregisterCompositor(name, dispose); + } + + /** Returns a registered bitmap font compositor (or null, if no compositor has been + * registered with that name, or if it's not a bitmap font). The name is not case + * sensitive. */ + public static function getBitmapFont(name:String):BitmapFont + { + return getCompositor(name) as BitmapFont; + } + + /** Stores the currently available text compositors. Since compositors will only work + * in one Stage3D context, they are saved in Starling's 'contextData' property. */ + private static function get compositors():Dictionary + { + var compositors:Dictionary = Starling.painter.sharedData[COMPOSITOR_DATA_NAME] as Dictionary; + + if (compositors == null) + { + compositors = new Dictionary(); + Starling.painter.sharedData[COMPOSITOR_DATA_NAME] = compositors; + } + + return compositors; + } + + // optimization for 'toLowerCase' calls + + private static var sStringCache:Dictionary = new Dictionary(); + + private static function convertToLowerCase(string:String):String + { + var result:String = sStringCache[string]; + if (result == null) + { + result = string.toLowerCase(); + sStringCache[string] = result; + } + return result; + } + } +} diff --git a/mobile_version/src/starling/text/TextFieldAutoSize.as b/mobile_version/src/starling/text/TextFieldAutoSize.as new file mode 100644 index 00000000..268c5696 --- /dev/null +++ b/mobile_version/src/starling/text/TextFieldAutoSize.as @@ -0,0 +1,36 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.text +{ + import starling.errors.AbstractClassError; + + /** This class is an enumeration of constant values used in setting the + * autoSize property of the TextField class. */ + public class TextFieldAutoSize + { + /** @private */ + public function TextFieldAutoSize() { throw new AbstractClassError(); } + + /** No auto-sizing will happen. */ + public static const NONE:String = "none"; + + /** The text field will grow/shrink sidewards; no line-breaks will be added. + * The height of the text field remains unchanged. Not supported for HTML text! */ + public static const HORIZONTAL:String = "horizontal"; + + /** The text field will grow/shrink downwards, adding line-breaks when necessary. + * The width of the text field remains unchanged. */ + public static const VERTICAL:String = "vertical"; + + /** The text field will grow to the right and bottom; no line-breaks will be added. */ + public static const BOTH_DIRECTIONS:String = "bothDirections"; + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/text/TextFormat.as b/mobile_version/src/starling/text/TextFormat.as new file mode 100644 index 00000000..50f28d28 --- /dev/null +++ b/mobile_version/src/starling/text/TextFormat.as @@ -0,0 +1,236 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.text +{ + import flash.text.TextFormat; + + import starling.events.Event; + import starling.events.EventDispatcher; + import starling.utils.Align; + + /** Dispatched when any property of the instance changes. */ + [Event(name="change", type="starling.events.Event")] + + /** The TextFormat class represents character formatting information. It is used by the + * TextField and BitmapFont classes to characterize the way the glyphs will be rendered. + * + *

Note that not all properties are used by all font renderers: bitmap fonts ignore + * the "bold", "italic", and "underline" values.

+ */ + public class TextFormat extends EventDispatcher + { + private var _font:String; + private var _size:Number; + private var _color:uint; + private var _bold:Boolean; + private var _italic:Boolean; + private var _underline:Boolean; + private var _horizontalAlign:String; + private var _verticalAlign:String; + private var _kerning:Boolean; + private var _leading:Number; + + /** Creates a new TextFormat instance with the given properties. */ + public function TextFormat(font:String="Verdana", size:Number=12, color:uint=0x0, + horizontalAlign:String="center", verticalAlign:String="center") + { + _font = font; + _size = size; + _color = color; + _horizontalAlign = horizontalAlign; + _verticalAlign = verticalAlign; + _kerning = true; + _leading = 0.0; + } + + /** Copies all properties from another TextFormat instance. */ + public function copyFrom(format:starling.text.TextFormat):void + { + _font = format._font; + _size = format._size; + _color = format._color; + _bold = format._bold; + _italic = format._italic; + _underline = format._underline; + _horizontalAlign = format._horizontalAlign; + _verticalAlign = format._verticalAlign; + _kerning = format._kerning; + _leading = format._leading; + + dispatchEventWith(Event.CHANGE); + } + + /** Creates a clone of this instance. */ + public function clone():starling.text.TextFormat + { + var clone:starling.text.TextFormat = new starling.text.TextFormat(); + clone.copyFrom(this); + return clone; + } + + /** Sets the most common properties at once. */ + public function setTo(font:String="Verdana", size:Number=12, color:uint=0x0, + horizontalAlign:String="center", verticalAlign:String="center"):void + { + _font = font; + _size = size; + _color = color; + _horizontalAlign = horizontalAlign; + _verticalAlign = verticalAlign; + + dispatchEventWith(Event.CHANGE); + } + + /** Converts the Starling TextFormat instance to a Flash TextFormat. */ + public function toNativeFormat(out:flash.text.TextFormat=null):flash.text.TextFormat + { + if (out == null) out = new flash.text.TextFormat(); + + out.font = _font; + out.size = _size; + out.color = _color; + out.bold = _bold; + out.italic = _italic; + out.underline = _underline; + out.align = _horizontalAlign; + out.kerning = _kerning; + out.leading = _leading; + + return out; + } + + /** The name of the font. TrueType fonts will be looked up from embedded fonts and + * system fonts; bitmap fonts must be registered at the TextField class first. + * Beware: If you loaded an embedded font at runtime, you must call + * TextField.updateEmbeddedFonts() for Starling to recognize it. + */ + public function get font():String { return _font; } + public function set font(value:String):void + { + if (value != _font) + { + _font = value; + dispatchEventWith(Event.CHANGE); + } + } + + /** The size of the font. For bitmap fonts, use BitmapFont.NATIVE_SIZE for + * the original size. */ + public function get size():Number { return _size; } + public function set size(value:Number):void + { + if (value != _size) + { + _size = value; + dispatchEventWith(Event.CHANGE); + } + } + + /** The color of the text. Note that bitmap fonts should be exported in plain white so + * that tinting works correctly. If your bitmap font contains colors, set this property + * to Color.WHITE to get the desired result. @default black */ + public function get color():uint { return _color; } + public function set color(value:uint):void + { + if (value != _color) + { + _color = value; + dispatchEventWith(Event.CHANGE); + } + } + + /** Indicates whether the text is bold. @default false */ + public function get bold():Boolean { return _bold; } + public function set bold(value:Boolean):void + { + if (value != _bold) + { + _bold = value; + dispatchEventWith(Event.CHANGE); + } + } + + /** Indicates whether the text is italicized. @default false */ + public function get italic():Boolean { return _italic; } + public function set italic(value:Boolean):void + { + if (value != _italic) + { + _italic = value; + dispatchEventWith(Event.CHANGE); + } + } + + /** Indicates whether the text is underlined. @default false */ + public function get underline():Boolean { return _underline; } + public function set underline(value:Boolean):void + { + if (value != _underline) + { + _underline = value; + dispatchEventWith(Event.CHANGE); + } + } + + /** The horizontal alignment of the text. @default center + * @see starling.utils.Align */ + public function get horizontalAlign():String { return _horizontalAlign; } + public function set horizontalAlign(value:String):void + { + if (!Align.isValidHorizontal(value)) + throw new ArgumentError("Invalid horizontal alignment"); + + if (value != _horizontalAlign) + { + _horizontalAlign = value; + dispatchEventWith(Event.CHANGE); + } + } + + /** The vertical alignment of the text. @default center + * @see starling.utils.Align */ + public function get verticalAlign():String { return _verticalAlign; } + public function set verticalAlign(value:String):void + { + if (!Align.isValidVertical(value)) + throw new ArgumentError("Invalid vertical alignment"); + + if (value != _verticalAlign) + { + _verticalAlign = value; + dispatchEventWith(Event.CHANGE); + } + } + + /** Indicates whether kerning is enabled. Kerning adjusts the pixels between certain + * character pairs to improve readability. @default true */ + public function get kerning():Boolean { return _kerning; } + public function set kerning(value:Boolean):void + { + if (value != _kerning) + { + _kerning = value; + dispatchEventWith(Event.CHANGE); + } + } + + /** The amount of vertical space (called 'leading') between lines. @default 0 */ + public function get leading():Number { return _leading; } + public function set leading(value:Number):void + { + if (value != _leading) + { + _leading = value; + dispatchEventWith(Event.CHANGE); + } + } + } +} diff --git a/mobile_version/src/starling/text/TextOptions.as b/mobile_version/src/starling/text/TextOptions.as new file mode 100644 index 00000000..3b44d1d2 --- /dev/null +++ b/mobile_version/src/starling/text/TextOptions.as @@ -0,0 +1,87 @@ +/** + * Created by redge on 16.12.15. + */ +package starling.text +{ + import flash.display3D.Context3DTextureFormat; + + import starling.core.Starling; + + /** The TextOptions class contains data that describes how the letters of a text should + * be assembled on text composition. + * + *

Note that not all properties are supported by all text compositors.

+ */ + public class TextOptions + { + private var _wordWrap:Boolean; + private var _autoScale:Boolean; + private var _autoSize:String; + private var _isHtmlText:Boolean; + private var _textureScale:Number; + private var _textureFormat:String; + + /** Creates a new TextOptions instance with the given properties. */ + public function TextOptions(wordWrap:Boolean=true, autoScale:Boolean=false) + { + _wordWrap = wordWrap; + _autoScale = autoScale; + _autoSize = TextFieldAutoSize.NONE; + _textureScale = Starling.contentScaleFactor; + _textureFormat = Context3DTextureFormat.BGR_PACKED; + _isHtmlText = false; + } + + /** Copies all properties from another TextOptions instance. */ + public function copyFrom(options:TextOptions):void + { + _wordWrap = options._wordWrap; + _autoScale = options._autoScale; + _autoSize = options._autoSize; + _isHtmlText = options._isHtmlText; + _textureScale = options._textureScale; + _textureFormat = options._textureFormat; + } + + /** Creates a clone of this instance. */ + public function clone():TextOptions + { + var clone:TextOptions = new TextOptions(); + clone.copyFrom(this); + return clone; + } + + /** Indicates if the text should be wrapped at word boundaries if it does not fit into + * the TextField otherwise. @default true */ + public function get wordWrap():Boolean { return _wordWrap; } + public function set wordWrap(value:Boolean):void { _wordWrap = value; } + + /** Specifies the type of auto-sizing set on the TextField. Custom text compositors may + * take this into account, though the basic implementation (done by the TextField itself) + * is often sufficient: it passes a very big size to the fillMeshBatch + * method and then trims the result to the actually used area. @default none */ + public function get autoSize():String { return _autoSize; } + public function set autoSize(value:String):void { _autoSize = value; } + + /** Indicates whether the font size is automatically reduced if the complete text does + * not fit into the TextField. @default false */ + public function get autoScale():Boolean { return _autoScale; } + public function set autoScale(value:Boolean):void { _autoScale = value; } + + /** Indicates if text should be interpreted as HTML code. For a description + * of the supported HTML subset, refer to the classic Flash 'TextField' documentation. + * Beware: Only supported for TrueType fonts. @default false */ + public function get isHtmlText():Boolean { return _isHtmlText; } + public function set isHtmlText(value:Boolean):void { _isHtmlText = value; } + + /** The scale factor of any textures that are created during text composition. + * @default Starling.contentScaleFactor */ + public function get textureScale():Number { return _textureScale; } + public function set textureScale(value:Number):void { _textureScale = value; } + + /** The Context3DTextureFormat of any textures that are created during text composition. + * @default Context3DTextureFormat.BGRA_PACKED */ + public function get textureFormat():String { return _textureFormat; } + public function set textureFormat(value:String):void { _textureFormat = value; } + } +} diff --git a/mobile_version/src/starling/text/TrueTypeCompositor.as b/mobile_version/src/starling/text/TrueTypeCompositor.as new file mode 100644 index 00000000..cf8de3ff --- /dev/null +++ b/mobile_version/src/starling/text/TrueTypeCompositor.as @@ -0,0 +1,187 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.text +{ + import flash.geom.Matrix; + import flash.text.AntiAliasType; + import flash.text.TextField; + + import starling.display.MeshBatch; + import starling.display.Quad; + import starling.textures.Texture; + import starling.utils.Align; + import starling.utils.SystemUtil; + + /** This text compositor uses a Flash TextField to render system- or embedded fonts into + * a texture. + * + *

You typically don't have to instantiate this class. It will be used internally by + * Starling's text fields.

+ */ + public class TrueTypeCompositor implements ITextCompositor + { + // helpers + private static var sHelperMatrix:Matrix = new Matrix(); + private static var sHelperQuad:Quad = new Quad(100, 100); + private static var sNativeTextField:flash.text.TextField = new flash.text.TextField(); + private static var sNativeFormat:flash.text.TextFormat = new flash.text.TextFormat(); + + /** Creates a new TrueTypeCompositor instance. */ + public function TrueTypeCompositor() + { } + + /** @inheritDoc */ + public function dispose():void + {} + + /** @inheritDoc */ + public function fillMeshBatch(meshBatch:MeshBatch, width:Number, height:Number, text:String, + format:TextFormat, options:TextOptions=null):void + { + if (text == null || text == "") return; + + var texture:Texture; + var textureFormat:String = options.textureFormat; + var bitmapData:BitmapDataEx = renderText(width, height, text, format, options); + + texture = Texture.fromBitmapData(bitmapData, false, false, bitmapData.scale, textureFormat); + texture.root.onRestore = function():void + { + bitmapData = renderText(width, height, text, format, options); + texture.root.uploadBitmapData(bitmapData); + bitmapData.dispose(); + bitmapData = null; + }; + + bitmapData.dispose(); + bitmapData = null; + + sHelperQuad.texture = texture; + sHelperQuad.readjustSize(); + + if (format.horizontalAlign == Align.LEFT) sHelperQuad.x = 0; + else if (format.horizontalAlign == Align.CENTER) sHelperQuad.x = int((width - texture.width) / 2); + else sHelperQuad.x = width - texture.width; + + if (format.verticalAlign == Align.TOP) sHelperQuad.y = 0; + else if (format.verticalAlign == Align.CENTER) sHelperQuad.y = int((height - texture.height) / 2); + else sHelperQuad.y = height - texture.height; + + meshBatch.addMesh(sHelperQuad); + + sHelperQuad.texture = null; + } + + /** @inheritDoc */ + public function clearMeshBatch(meshBatch:MeshBatch):void + { + meshBatch.clear(); + if (meshBatch.texture) meshBatch.texture.dispose(); + } + + private function renderText(width:Number, height:Number, text:String, + format:TextFormat, options:TextOptions):BitmapDataEx + { + var scaledWidth:Number = width * options.textureScale; + var scaledHeight:Number = height * options.textureScale; + var hAlign:String = format.horizontalAlign; + + format.toNativeFormat(sNativeFormat); + + sNativeFormat.size = Number(sNativeFormat.size) * options.textureScale; + sNativeTextField.embedFonts = SystemUtil.isEmbeddedFont(format.font, format.bold, format.italic); + sNativeTextField.defaultTextFormat = sNativeFormat; + sNativeTextField.width = scaledWidth; + sNativeTextField.height = scaledHeight; + sNativeTextField.antiAliasType = AntiAliasType.ADVANCED; + sNativeTextField.selectable = false; + sNativeTextField.multiline = true; + sNativeTextField.wordWrap = options.wordWrap; + + if (options.isHtmlText) sNativeTextField.htmlText = text; + else sNativeTextField.text = text; + + if (options.autoScale) + autoScaleNativeTextField(sNativeTextField, text, options.isHtmlText); + + var textWidth:Number = sNativeTextField.textWidth; + var textHeight:Number = sNativeTextField.textHeight; + var bitmapWidth:int = Math.ceil(textWidth) + 4; + var bitmapHeight:int = Math.ceil(textHeight) + 4; + var maxTextureSize:int = Texture.maxSize; + var minTextureSize:int = 1; + var offsetX:Number = 0.0; + + // HTML text may have its own alignment -> use the complete width + if (options.isHtmlText) textWidth = bitmapWidth = scaledWidth; + + // check for invalid texture sizes + if (bitmapWidth < minTextureSize) bitmapWidth = 1; + if (bitmapHeight < minTextureSize) bitmapHeight = 1; + if (bitmapHeight > maxTextureSize || bitmapWidth > maxTextureSize) + { + options.textureScale *= maxTextureSize / Math.max(bitmapWidth, bitmapHeight); + return renderText(width, height, text, format, options); + } + else + { + if (!options.isHtmlText) + { + if (hAlign == Align.RIGHT) offsetX = scaledWidth - textWidth - 4; + else if (hAlign == Align.CENTER) offsetX = (scaledWidth - textWidth - 4) / 2.0; + } + + // finally: draw TextField to bitmap data + var bitmapData:BitmapDataEx = new BitmapDataEx(bitmapWidth, bitmapHeight); + sHelperMatrix.setTo(1, 0, 0, 1, -offsetX, 0); + bitmapData.draw(sNativeTextField, sHelperMatrix); + bitmapData.scale = options.textureScale; + sNativeTextField.text = ""; + return bitmapData; + } + } + + private function autoScaleNativeTextField(textField:flash.text.TextField, + text:String, isHtmlText:Boolean):void + { + var textFormat:flash.text.TextFormat = textField.defaultTextFormat; + var maxTextWidth:int = textField.width - 4; + var maxTextHeight:int = textField.height - 4; + var size:Number = Number(textFormat.size); + + while (textField.textWidth > maxTextWidth || textField.textHeight > maxTextHeight) + { + if (size <= 4) break; + + textFormat.size = size--; + textField.defaultTextFormat = textFormat; + + if (isHtmlText) textField.htmlText = text; + else textField.text = text; + } + } + } +} + +import flash.display.BitmapData; + +class BitmapDataEx extends BitmapData +{ + private var _scale:Number = 1.0; + + function BitmapDataEx(width:int, height:int, transparent:Boolean=true, fillColor:uint=0x0) + { + super(width, height, transparent, fillColor); + } + + public function get scale():Number { return _scale; } + public function set scale(value:Number):void { _scale = value; } +} diff --git a/mobile_version/src/starling/textures/AtfData.as b/mobile_version/src/starling/textures/AtfData.as new file mode 100644 index 00000000..bc31b4a9 --- /dev/null +++ b/mobile_version/src/starling/textures/AtfData.as @@ -0,0 +1,94 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.textures +{ + import flash.display3D.Context3DTextureFormat; + import flash.utils.ByteArray; + + /** A parser for the ATF data format. */ + public class AtfData + { + private var _format:String; + private var _width:int; + private var _height:int; + private var _numTextures:int; + private var _isCubeMap:Boolean; + private var _data:ByteArray; + + /** Create a new instance by parsing the given byte array. */ + public function AtfData(data:ByteArray) + { + if (!isAtfData(data)) throw new ArgumentError("Invalid ATF data"); + + if (data[6] == 255) data.position = 12; // new file version + else data.position = 6; // old file version + + var format:uint = data.readUnsignedByte(); + switch (format & 0x7f) + { + case 0: + case 1: _format = Context3DTextureFormat.BGRA; break; + case 12: + case 2: + case 3: _format = Context3DTextureFormat.COMPRESSED; break; + case 13: + case 4: + case 5: _format = "compressedAlpha"; break; // explicit string for compatibility + default: throw new Error("Invalid ATF format"); + } + + _width = Math.pow(2, data.readUnsignedByte()); + _height = Math.pow(2, data.readUnsignedByte()); + _numTextures = data.readUnsignedByte(); + _isCubeMap = (format & 0x80) != 0; + _data = data; + + // version 2 of the new file format contains information about + // the "-e" and "-n" parameters of png2atf + + if (data[5] != 0 && data[6] == 255) + { + var emptyMipmaps:Boolean = (data[5] & 0x01) == 1; + var numTextures:int = data[5] >> 1 & 0x7f; + _numTextures = emptyMipmaps ? 1 : numTextures; + } + } + + /** Checks the first 3 bytes of the data for the 'ATF' signature. */ + public static function isAtfData(data:ByteArray):Boolean + { + if (data.length < 3) return false; + else + { + var signature:String = String.fromCharCode(data[0], data[1], data[2]); + return signature == "ATF"; + } + } + + /** The texture format. @see flash.display3D.textures.Context3DTextureFormat */ + public function get format():String { return _format; } + + /** The width of the texture in pixels. */ + public function get width():int { return _width; } + + /** The height of the texture in pixels. */ + public function get height():int { return _height; } + + /** The number of encoded textures. '1' means that there are no mip maps. */ + public function get numTextures():int { return _numTextures; } + + /** Indicates if the ATF data encodes a cube map. Not supported by Starling! */ + public function get isCubeMap():Boolean { return _isCubeMap; } + + /** The actual byte data, including header. */ + public function get data():ByteArray { return _data; } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/textures/ConcretePotTexture.as b/mobile_version/src/starling/textures/ConcretePotTexture.as new file mode 100644 index 00000000..cfa2435f --- /dev/null +++ b/mobile_version/src/starling/textures/ConcretePotTexture.as @@ -0,0 +1,140 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.textures +{ + import flash.display.BitmapData; + import flash.display3D.textures.TextureBase; + import flash.events.Event; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.utils.ByteArray; + + import starling.core.Starling; + import starling.utils.MathUtil; + import starling.utils.execute; + + /** @private + * + * A concrete texture that wraps a Texture base. + * For internal use only. */ + internal class ConcretePotTexture extends ConcreteTexture + { + private var _textureReadyCallback:Function; + + private static var sMatrix:Matrix = new Matrix(); + private static var sRectangle:Rectangle = new Rectangle(); + private static var sOrigin:Point = new Point(); + + /** Creates a new instance with the given parameters. */ + public function ConcretePotTexture(base:flash.display3D.textures.Texture, format:String, + width:int, height:int, mipMapping:Boolean, + premultipliedAlpha:Boolean, + optimizedForRenderTexture:Boolean=false, scale:Number=1) + { + super(base, format, width, height, mipMapping, premultipliedAlpha, + optimizedForRenderTexture, scale); + + if (width != MathUtil.getNextPowerOfTwo(width)) + throw new ArgumentError("width must be a power of two"); + + if (height != MathUtil.getNextPowerOfTwo(height)) + throw new ArgumentError("height must be a power of two"); + } + + /** @inheritDoc */ + override public function dispose():void + { + base.removeEventListener(Event.TEXTURE_READY, onTextureReady); + super.dispose(); + } + + /** @inheritDoc */ + override protected function createBase():TextureBase + { + return Starling.context.createTexture( + nativeWidth, nativeHeight, format, optimizedForRenderTexture); + } + + /** @inheritDoc */ + override public function uploadBitmapData(data:BitmapData):void + { + potBase.uploadFromBitmapData(data); + + var buffer:BitmapData = null; + + if (data.width != nativeWidth || data.height != nativeHeight) + { + buffer = new BitmapData(nativeWidth, nativeHeight, true, 0); + buffer.copyPixels(data, data.rect, sOrigin); + data = buffer; + } + + if (mipMapping && data.width > 1 && data.height > 1) + { + var currentWidth:int = data.width >> 1; + var currentHeight:int = data.height >> 1; + var level:int = 1; + var canvas:BitmapData = new BitmapData(currentWidth, currentHeight, true, 0); + var bounds:Rectangle = sRectangle; + var matrix:Matrix = sMatrix; + matrix.setTo(0.5, 0.0, 0.0, 0.5, 0.0, 0.0); + + while (currentWidth >= 1 || currentHeight >= 1) + { + bounds.setTo(0, 0, currentWidth, currentHeight); + canvas.fillRect(bounds, 0); + canvas.draw(data, matrix, null, null, null, true); + potBase.uploadFromBitmapData(canvas, level++); + matrix.scale(0.5, 0.5); + currentWidth = currentWidth >> 1; + currentHeight = currentHeight >> 1; + } + + canvas.dispose(); + } + + if (buffer) buffer.dispose(); + + setDataUploaded(); + } + + /** @inheritDoc */ + override public function get isPotTexture():Boolean { return true; } + + /** @inheritDoc */ + override public function uploadAtfData(data:ByteArray, offset:int = 0, async:* = null):void + { + var isAsync:Boolean = async is Function || async === true; + + if (async is Function) + { + _textureReadyCallback = async as Function; + base.addEventListener(Event.TEXTURE_READY, onTextureReady); + } + + potBase.uploadCompressedTextureFromByteArray(data, offset, isAsync); + setDataUploaded(); + } + + private function onTextureReady(event:Event):void + { + base.removeEventListener(Event.TEXTURE_READY, onTextureReady); + execute(_textureReadyCallback, this); + _textureReadyCallback = null; + } + + private function get potBase():flash.display3D.textures.Texture + { + return base as flash.display3D.textures.Texture; + } + } +} diff --git a/mobile_version/src/starling/textures/ConcreteRectangleTexture.as b/mobile_version/src/starling/textures/ConcreteRectangleTexture.as new file mode 100644 index 00000000..658d87a9 --- /dev/null +++ b/mobile_version/src/starling/textures/ConcreteRectangleTexture.as @@ -0,0 +1,54 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.textures +{ + import flash.display.BitmapData; + import flash.display3D.textures.RectangleTexture; + import flash.display3D.textures.TextureBase; + + import starling.core.Starling; + + /** @private + * + * A concrete texture that wraps a RectangleTexture base. + * For internal use only. */ + internal class ConcreteRectangleTexture extends ConcreteTexture + { + /** Creates a new instance with the given parameters. */ + public function ConcreteRectangleTexture(base:RectangleTexture, format:String, + width:int, height:int, premultipliedAlpha:Boolean, + optimizedForRenderTexture:Boolean=false, + scale:Number=1) + { + super(base, format, width, height, false, premultipliedAlpha, + optimizedForRenderTexture, scale); + } + + /** @inheritDoc */ + override public function uploadBitmapData(data:BitmapData):void + { + rectangleBase.uploadFromBitmapData(data); + setDataUploaded(); + } + + /** @inheritDoc */ + override protected function createBase():TextureBase + { + return Starling.context.createRectangleTexture( + nativeWidth, nativeHeight, format, optimizedForRenderTexture); + } + + private function get rectangleBase():RectangleTexture + { + return base as RectangleTexture; + } + } +} diff --git a/mobile_version/src/starling/textures/ConcreteTexture.as b/mobile_version/src/starling/textures/ConcreteTexture.as new file mode 100644 index 00000000..a5359443 --- /dev/null +++ b/mobile_version/src/starling/textures/ConcreteTexture.as @@ -0,0 +1,269 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.textures +{ + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display3D.textures.TextureBase; + import flash.media.Camera; + import flash.net.NetStream; + import flash.system.Capabilities; + import flash.utils.ByteArray; + import flash.utils.getQualifiedClassName; + + import starling.core.Starling; + import starling.core.starling_internal; + import starling.errors.AbstractClassError; + import starling.errors.AbstractMethodError; + import starling.errors.NotSupportedError; + import starling.events.Event; + import starling.rendering.Painter; + import starling.utils.Color; + import starling.utils.execute; + + /** A ConcreteTexture wraps a Stage3D texture object, storing the properties of the texture + * and providing utility methods for data upload, etc. + * + *

This class cannot be instantiated directly; create instances using + * Texture.fromTextureBase instead. However, that's only necessary when + * you need to wrap a TextureBase object in a Starling texture; + * the preferred way of creating textures is to use one of the other + * Texture.from... factory methods in the Texture class.

+ * + * @see Texture + */ + public class ConcreteTexture extends Texture + { + private var _base:TextureBase; + private var _format:String; + private var _width:int; + private var _height:int; + private var _mipMapping:Boolean; + private var _premultipliedAlpha:Boolean; + private var _optimizedForRenderTexture:Boolean; + private var _scale:Number; + private var _onRestore:Function; + private var _dataUploaded:Boolean; + + /** @private + * + * Creates a ConcreteTexture object from a TextureBase, storing information about size, + * mip-mapping, and if the channels contain premultiplied alpha values. May only be + * called from subclasses. + * + *

Note that width and height are expected in pixels, + * i.e. they do not take the scale factor into account.

+ */ + public function ConcreteTexture(base:TextureBase, format:String, width:int, height:int, + mipMapping:Boolean, premultipliedAlpha:Boolean, + optimizedForRenderTexture:Boolean=false, scale:Number=1) + { + if (Capabilities.isDebugger && + getQualifiedClassName(this) == "starling.textures::ConcreteTexture") + { + throw new AbstractClassError(); + } + + _scale = scale <= 0 ? 1.0 : scale; + _base = base; + _format = format; + _width = width; + _height = height; + _mipMapping = mipMapping; + _premultipliedAlpha = premultipliedAlpha; + _optimizedForRenderTexture = optimizedForRenderTexture; + _onRestore = null; + _dataUploaded = false; + } + + /** Disposes the TextureBase object. */ + public override function dispose():void + { + if (_base) _base.dispose(); + + this.onRestore = null; // removes event listener + super.dispose(); + } + + // texture data upload + + /** Uploads a bitmap to the texture. The existing contents will be replaced. + * If the size of the bitmap does not match the size of the texture, the bitmap will be + * cropped or filled up with transparent pixels */ + public function uploadBitmap(bitmap:Bitmap):void + { + uploadBitmapData(bitmap.bitmapData); + } + + /** Uploads bitmap data to the texture. The existing contents will be replaced. + * If the size of the bitmap does not match the size of the texture, the bitmap will be + * cropped or filled up with transparent pixels */ + public function uploadBitmapData(data:BitmapData):void + { + throw new NotSupportedError(); + } + + /** Uploads ATF data from a ByteArray to the texture. Note that the size of the + * ATF-encoded data must be exactly the same as the original texture size. + * + *

The 'async' parameter may be either a boolean value or a callback function. + * If it's false or null, the texture will be decoded + * synchronously and will be visible right away. If it's true or a function, + * the data will be decoded asynchronously. The texture will remain unchanged until the + * upload is complete, at which time the callback function will be executed. This is the + * expected function definition: function(texture:Texture):void;

+ */ + public function uploadAtfData(data:ByteArray, offset:int=0, async:*=null):void + { + throw new NotSupportedError(); + } + + /** Specifies a video stream to be rendered within the texture. */ + public function attachNetStream(netStream:NetStream, onComplete:Function=null):void + { + attachVideo("NetStream", netStream, onComplete); + } + + /** Specifies a video stream from a camera to be rendered within the texture. */ + public function attachCamera(camera:Camera, onComplete:Function=null):void + { + attachVideo("Camera", camera, onComplete); + } + + /** @private */ + internal function attachVideo(type:String, attachment:Object, onComplete:Function=null):void + { + throw new NotSupportedError(); + } + + // texture backup (context loss) + + private function onContextCreated():void + { + _dataUploaded = false; + _base = createBase(); // recreate the underlying texture + execute(_onRestore, this); // restore contents + + // if no texture has been uploaded above, we init the texture with transparent pixels. + if (!_dataUploaded) clear(); + } + + /** Recreates the underlying Stage3D texture object with the same dimensions and attributes + * as the one that was passed to the constructor. You have to upload new data before the + * texture becomes usable again. Beware: this method does not dispose + * the current base. */ + protected function createBase():TextureBase + { + throw new AbstractMethodError(); + } + + /** Recreates the underlying Stage3D texture. May be used to manually restore a texture. + * Beware that new data needs to be uploaded to the texture before it can be used. */ + starling_internal function recreateBase():void + { + _base = createBase(); + } + + /** Clears the texture with a certain color and alpha value. The previous contents of the + * texture is wiped out. */ + public function clear(color:uint=0x0, alpha:Number=0.0):void + { + if (_premultipliedAlpha && alpha < 1.0) + color = Color.rgb(Color.getRed(color) * alpha, + Color.getGreen(color) * alpha, + Color.getBlue(color) * alpha); + + var painter:Painter = Starling.painter; + painter.pushState(); + painter.state.renderTarget = this; + + // we wrap the clear call in a try/catch block as a workaround for a problem of + // FP 11.8 plugin/projector: calling clear on a compressed texture doesn't work there + // (while it *does* work on iOS + Android). + + try { painter.clear(color, alpha); } + catch (e:Error) {} + + painter.popState(); + setDataUploaded(); + } + + /** Notifies the instance that the base texture may now be used for rendering. */ + protected function setDataUploaded():void + { + _dataUploaded = true; + } + + // properties + + /** Indicates if the base texture was optimized for being used in a render texture. */ + public function get optimizedForRenderTexture():Boolean { return _optimizedForRenderTexture; } + + /** Indicates if the base texture is a standard power-of-two dimensioned texture of type + * flash.display3D.textures.Texture. */ + public function get isPotTexture():Boolean { return false; } + + /** The function that you provide here will be called after a context loss. + * On execution, a new base texture will already have been created; however, + * it will be empty. Call one of the "upload..." methods from within the callback + * to restore the actual texture data. + * + * + * var texture:Texture = Texture.fromBitmap(new EmbeddedBitmap()); + * texture.root.onRestore = function():void + * { + * texture.root.uploadFromBitmap(new EmbeddedBitmap()); + * }; + */ + public function get onRestore():Function { return _onRestore; } + public function set onRestore(value:Function):void + { + Starling.current.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated); + + if (value != null) + { + _onRestore = value; + Starling.current.addEventListener(Event.CONTEXT3D_CREATE, onContextCreated); + } + else _onRestore = null; + } + + /** @inheritDoc */ + public override function get base():TextureBase { return _base; } + + /** @inheritDoc */ + public override function get root():ConcreteTexture { return this; } + + /** @inheritDoc */ + public override function get format():String { return _format; } + + /** @inheritDoc */ + public override function get width():Number { return _width / _scale; } + + /** @inheritDoc */ + public override function get height():Number { return _height / _scale; } + + /** @inheritDoc */ + public override function get nativeWidth():Number { return _width; } + + /** @inheritDoc */ + public override function get nativeHeight():Number { return _height; } + + /** @inheritDoc */ + public override function get scale():Number { return _scale; } + + /** @inheritDoc */ + public override function get mipMapping():Boolean { return _mipMapping; } + + /** @inheritDoc */ + public override function get premultipliedAlpha():Boolean { return _premultipliedAlpha; } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/textures/ConcreteVideoTexture.as b/mobile_version/src/starling/textures/ConcreteVideoTexture.as new file mode 100644 index 00000000..1d6d4161 --- /dev/null +++ b/mobile_version/src/starling/textures/ConcreteVideoTexture.as @@ -0,0 +1,98 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.textures +{ + import flash.display3D.Context3DTextureFormat; + import flash.display3D.textures.TextureBase; + import flash.display3D.textures.VideoTexture; + import flash.events.Event; + + import starling.core.Starling; + import starling.utils.execute; + + /** @private + * + * A concrete texture that wraps a VideoTexture base. + * For internal use only. */ + internal class ConcreteVideoTexture extends ConcreteTexture + { + private var _textureReadyCallback:Function; + + /** Creates a new instance with the given parameters. + * base must be of type flash.display3D.textures.VideoTexture. + */ + public function ConcreteVideoTexture(base:VideoTexture, scale:Number=1) + { + super(base, Context3DTextureFormat.BGRA, base.videoWidth, base.videoHeight, false, + false, false, scale); + } + + /** @inheritDoc */ + override public function dispose():void + { + base.removeEventListener(Event.TEXTURE_READY, onTextureReady); + super.dispose(); + } + + /** @inheritDoc */ + override protected function createBase():TextureBase + { + return Starling.context.createVideoTexture(); + } + + /** @private */ + override internal function attachVideo(type:String, attachment:Object, + onComplete:Function=null):void + { + _textureReadyCallback = onComplete; + base["attach" + type](attachment); + base.addEventListener(Event.TEXTURE_READY, onTextureReady); + + setDataUploaded(); + } + + private function onTextureReady(event:Event):void + { + base.removeEventListener(Event.TEXTURE_READY, onTextureReady); + execute(_textureReadyCallback, this); + _textureReadyCallback = null; + } + + /** The actual width of the video in pixels. */ + override public function get nativeWidth():Number + { + return videoBase.videoWidth; + } + + /** The actual height of the video in pixels. */ + override public function get nativeHeight():Number + { + return videoBase.videoHeight; + } + + /** @inheritDoc */ + override public function get width():Number + { + return nativeWidth / scale; + } + + /** @inheritDoc */ + override public function get height():Number + { + return nativeHeight / scale; + } + + private function get videoBase():VideoTexture + { + return base as VideoTexture; + } + } +} diff --git a/mobile_version/src/starling/textures/RenderTexture.as b/mobile_version/src/starling/textures/RenderTexture.as new file mode 100644 index 00000000..7279dc2d --- /dev/null +++ b/mobile_version/src/starling/textures/RenderTexture.as @@ -0,0 +1,336 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.textures +{ + import flash.display3D.textures.TextureBase; + import flash.errors.IllegalOperationError; + import flash.geom.Matrix; + import flash.geom.Rectangle; + import flash.utils.Dictionary; + + import starling.core.Starling; + import starling.display.BlendMode; + import starling.display.DisplayObject; + import starling.display.Image; + import starling.filters.FragmentFilter; + import starling.rendering.Painter; + import starling.rendering.RenderState; + import starling.utils.execute; + + /** A RenderTexture is a dynamic texture onto 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 onto 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 drawBundled method, like shown below. That will speed it up + * immensely, allowing you to draw hundreds of objects very quickly.

+ * + *
+     *  renderTexture.drawBundled(function():void
+     *  {
+     *     for (var i:int=0; i<numDrawings; ++i)
+     *     {
+     *         image.rotation = (2 * Math.PI / numDrawings) * i;
+     *         renderTexture.draw(image);
+     *     }   
+     *  });
+     *  
+ * + *

To erase parts of a render texture, you can use any display object like a "rubber" by + * setting its blending mode to BlendMode.ERASE. To wipe it completely clean, + * use the clear method.

+ * + * Persistence + * + *

Older devices may require double buffering to support persistent render textures. Thus, + * you should disable the persistent parameter in the constructor if you only + * need to make one draw operation on the texture. The static useDoubleBuffering + * property allows you to customize if new textures will be created with or without double + * buffering.

+ * + * Context Loss + * + *

Unfortunately, render textures are wiped clean when the render context is lost. + * This means that you need to manually recreate all their contents in such a case. + * One way to do that is by using the root.onRestore callback, like here:

+ * + * + * renderTexture.root.onRestore = function():void + * { + * var quad:Quad = new Quad(100, 100, 0xff00ff); + * renderTexture.clear(); // required on texture restoration + * renderTexture.draw(quad); + * }); + * + *

For example, a drawing app would need to store information about all draw operations + * when they occur, and then recreate them inside onRestore on a context loss + * (preferably using drawBundled instead).

+ * + *

However, there is one problem: when that callback is executed, it's very likely that + * not all of your textures are already available, since they need to be restored, too (and + * that might take a while). You probably loaded your textures with the "AssetManager". + * In that case, you can listen to its TEXTURES_RESTORED event instead:

+ * + * + * assetManager.addEventListener(Event.TEXTURES_RESTORED, function():void + * { + * var brush:Image = new Image(assetManager.getTexture("brush")); + * renderTexture.draw(brush); + * }); + * + *

[Note that this time, there is no need to call clear, because that's the + * default behavior of onRestore, anyway — and we didn't modify that.]

+ * + */ + public class RenderTexture extends SubTexture + { + private static const USE_DOUBLE_BUFFERING_DATA_NAME:String = + "starling.textures.RenderTexture.useDoubleBuffering"; + + private var _activeTexture:Texture; + private var _bufferTexture:Texture; + private var _helperImage:Image; + private var _drawing:Boolean; + private var _bufferReady:Boolean; + private var _isPersistent:Boolean; + + // helper object + private static var sClipRect:Rectangle = new Rectangle(); + + /** Creates a new RenderTexture with a certain size (in points). If the texture is + * persistent, its contents remains intact after each draw call, allowing you to use the + * texture just like a canvas. If it is not, it will be cleared before each draw call. + * + *

Non-persistent textures can be used more efficiently on older devices; on modern + * hardware, it does not make a difference. For more information, have a look at the + * documentation of the useDoubleBuffering property.

+ */ + public function RenderTexture(width:int, height:int, persistent:Boolean=true, + scale:Number=-1, format:String="bgra") + { + _isPersistent = persistent; + _activeTexture = Texture.empty(width, height, true, false, true, scale, format); + _activeTexture.root.onRestore = _activeTexture.root.clear; + + super(_activeTexture, new Rectangle(0, 0, width, height), true, null, false); + + if (persistent && useDoubleBuffering) + { + _bufferTexture = Texture.empty(width, height, true, false, true, scale, format); + _bufferTexture.root.onRestore = _bufferTexture.root.clear; + _helperImage = new Image(_bufferTexture); + _helperImage.textureSmoothing = TextureSmoothing.NONE; // solves some aliasing-issues + } + } + + /** @inheritDoc */ + public override function dispose():void + { + _activeTexture.dispose(); + + if (isDoubleBuffered) + { + _bufferTexture.dispose(); + _helperImage.dispose(); + } + + super.dispose(); + } + + /** Draws an object into the texture. Note that any filters on the object will currently + * be ignored. + * + * @param object The object to draw. + * @param matrix If 'matrix' is null, the object will be drawn adhering its + * properties for position, scale, and rotation. If it is not null, + * the object will be drawn in the orientation depicted by the matrix. + * @param alpha The object's alpha value will be multiplied with this value. + * @param antiAliasing Values range from 0 (no antialiasing) to 4 (best quality). + * Beginning with AIR 22, this feature is supported on all platforms + * (except for software rendering mode). + */ + public function draw(object:DisplayObject, matrix:Matrix=null, alpha:Number=1.0, + antiAliasing:int=0):void + { + if (object == null) return; + + if (_drawing) + render(object, matrix, alpha); + else + renderBundled(render, object, matrix, alpha, antiAliasing); + } + + /** Bundles several calls to draw together in a block. This avoids buffer + * switches and allows you to draw multiple objects into a non-persistent texture. + * Note that the 'antiAliasing' setting provided here overrides those provided in + * individual 'draw' calls. + * + * @param drawingBlock a callback with the form:
function():void;
+ * @param antiAliasing Values range from 0 (no antialiasing) to 4 (best quality). + * Beginning with AIR 22, this feature is supported on all platforms + * (except for software rendering mode). + */ + public function drawBundled(drawingBlock:Function, antiAliasing:int=0):void + { + renderBundled(drawingBlock, null, null, 1.0, antiAliasing); + } + + private function render(object:DisplayObject, matrix:Matrix=null, alpha:Number=1.0):void + { + var painter:Painter = Starling.painter; + var state:RenderState = painter.state; + var wasCacheEnabled:Boolean = painter.cacheEnabled; + var filter:FragmentFilter = object.filter; + var mask:DisplayObject = object.mask; + + painter.cacheEnabled = false; + painter.pushState(); + + state.alpha = object.alpha * alpha; + state.setModelviewMatricesToIdentity(); + state.blendMode = object.blendMode == BlendMode.AUTO ? + BlendMode.NORMAL : object.blendMode; + + if (matrix) state.transformModelviewMatrix(matrix); + else state.transformModelviewMatrix(object.transformationMatrix); + + if (mask) painter.drawMask(mask); + + if (filter) filter.render(painter); + else object.render(painter); + + if (mask) painter.eraseMask(mask); + + painter.popState(); + painter.cacheEnabled = wasCacheEnabled; + } + + private function renderBundled(renderBlock:Function, object:DisplayObject=null, + matrix:Matrix=null, alpha:Number=1.0, + antiAliasing:int=0):void + { + var painter:Painter = Starling.painter; + var state:RenderState = painter.state; + + if (!Starling.current.contextValid) return; + + // switch buffers + if (isDoubleBuffered) + { + var tmpTexture:Texture = _activeTexture; + _activeTexture = _bufferTexture; + _bufferTexture = tmpTexture; + _helperImage.texture = _bufferTexture; + } + + painter.pushState(); + + var rootTexture:Texture = _activeTexture.root; + state.setProjectionMatrix(0, 0, rootTexture.width, rootTexture.height, width, height); + + // limit drawing to relevant area + sClipRect.setTo(0, 0, _activeTexture.width, _activeTexture.height); + + state.clipRect = sClipRect; + state.setRenderTarget(_activeTexture, true, antiAliasing); + painter.prepareToDraw(); + + if (isDoubleBuffered || !isPersistent || !_bufferReady) + painter.clear(); + + // draw buffer + if (isDoubleBuffered && _bufferReady) + _helperImage.render(painter); + else + _bufferReady = true; + + try + { + _drawing = true; + execute(renderBlock, object, matrix, alpha); + } + finally + { + _drawing = false; + painter.popState(); + } + } + + /** Clears the render texture with a certain color and alpha value. Call without any + * arguments to restore full transparency. */ + public function clear(color:uint=0, alpha:Number=0.0):void + { + _activeTexture.root.clear(color, alpha); + _bufferReady = true; + } + + // properties + + /** Indicates if the render texture is using double buffering. This might be necessary for + * persistent textures, depending on the runtime version and the value of + * 'forceDoubleBuffering'. */ + private function get isDoubleBuffered():Boolean { return _bufferTexture != null; } + + /** Indicates if the texture is persistent over multiple draw calls. */ + public function get isPersistent():Boolean { return _isPersistent; } + + /** @inheritDoc */ + public override function get base():TextureBase { return _activeTexture.base; } + + /** @inheritDoc */ + public override function get root():ConcreteTexture { return _activeTexture.root; } + + /** Indicates if new persistent textures should use double buffering. Single buffering + * is faster and requires less memory, but is not supported on all hardware. + * + *

By default, applications running with the profile "baseline" or "baselineConstrained" + * will use double buffering; all others use just a single buffer. You can override this + * behavior, though, by assigning a different value at runtime.

+ * + * @default true for "baseline" and "baselineConstrained", false otherwise + */ + public static function get useDoubleBuffering():Boolean + { + if (Starling.current) + { + var painter:Painter = Starling.painter; + var sharedData:Dictionary = painter.sharedData; + + if (USE_DOUBLE_BUFFERING_DATA_NAME in sharedData) + { + return sharedData[USE_DOUBLE_BUFFERING_DATA_NAME]; + } + else + { + var profile:String = painter.profile ? painter.profile : "baseline"; + var value:Boolean = profile == "baseline" || profile == "baselineConstrained"; + sharedData[USE_DOUBLE_BUFFERING_DATA_NAME] = value; + return value; + } + } + else return false; + } + + public static function set useDoubleBuffering(value:Boolean):void + { + if (Starling.current == null) + throw new IllegalOperationError("Starling not yet initialized"); + else + Starling.painter.sharedData[USE_DOUBLE_BUFFERING_DATA_NAME] = value; + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/textures/SubTexture.as b/mobile_version/src/starling/textures/SubTexture.as new file mode 100644 index 00000000..8aa3f25d --- /dev/null +++ b/mobile_version/src/starling/textures/SubTexture.as @@ -0,0 +1,183 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.textures +{ + import flash.display3D.textures.TextureBase; + import flash.geom.Matrix; + import flash.geom.Rectangle; + + import starling.core.starling_internal; + + /** A SubTexture represents a section of another texture. This is achieved solely by + * manipulation of texture coordinates, making the class very efficient. + * + *

Note that it is OK to create subtextures of subtextures.

+ */ + public class SubTexture extends Texture + { + private var _parent:Texture; + private var _ownsParent:Boolean; + private var _region:Rectangle; + private var _frame:Rectangle; + private var _rotated:Boolean; + private var _width:Number; + private var _height:Number; + private var _scale:Number; + private var _transformationMatrix:Matrix; + private var _transformationMatrixToRoot:Matrix; + + /** Creates a new SubTexture containing the specified region of a parent texture. + * + * @param parent The texture you want to create a SubTexture from. + * @param region The region of the parent texture that the SubTexture will show + * (in points). If null, the complete area of the parent. + * @param ownsParent If true, the parent texture will be disposed + * automatically when the SubTexture is disposed. + * @param frame If the texture was trimmed, the frame rectangle can be used to restore + * the trimmed area. + * @param rotated If true, the SubTexture will show the parent region rotated by + * 90 degrees (CCW). + * @param scaleModifier The scale factor of the SubTexture will be calculated by + * multiplying the parent texture's scale factor with this value. + */ + public function SubTexture(parent:Texture, region:Rectangle=null, + ownsParent:Boolean=false, frame:Rectangle=null, + rotated:Boolean=false, scaleModifier:Number=1) + { + starling_internal::setTo(parent, region, ownsParent, frame, rotated, scaleModifier); + } + + /** @private + * + *

Textures are supposed to be immutable, and Starling uses this assumption for + * optimizations and simplifications all over the place. However, in some situations where + * the texture is not accessible to the outside, this can be overruled in order to avoid + * allocations.

+ */ + starling_internal function setTo(parent:Texture, region:Rectangle=null, + ownsParent:Boolean=false, frame:Rectangle=null, + rotated:Boolean=false, scaleModifier:Number=1):void + { + if (_region == null) _region = new Rectangle(); + if (region) _region.copyFrom(region); + else _region.setTo(0, 0, parent.width, parent.height); + + if (frame) + { + if (_frame) _frame.copyFrom(frame); + else _frame = frame.clone(); + } + else _frame = null; + + _parent = parent; + _ownsParent = ownsParent; + _rotated = rotated; + _width = (rotated ? _region.height : _region.width) / scaleModifier; + _height = (rotated ? _region.width : _region.height) / scaleModifier; + _scale = _parent.scale * scaleModifier; + + if (_frame && (_frame.x > 0 || _frame.y > 0 || + _frame.right < _width || _frame.bottom < _height)) + { + trace("[Starling] Warning: frames inside the texture's region are unsupported."); + } + + updateMatrices(); + } + + private function updateMatrices():void + { + if (_transformationMatrix) _transformationMatrix.identity(); + else _transformationMatrix = new Matrix(); + + if (_transformationMatrixToRoot) _transformationMatrixToRoot.identity(); + else _transformationMatrixToRoot = new Matrix(); + + if (_rotated) + { + _transformationMatrix.translate(0, -1); + _transformationMatrix.rotate(Math.PI / 2.0); + } + + _transformationMatrix.scale(_region.width / _parent.width, + _region.height / _parent.height); + _transformationMatrix.translate(_region.x / _parent.width, + _region.y / _parent.height); + + var texture:SubTexture = this; + while (texture) + { + _transformationMatrixToRoot.concat(texture._transformationMatrix); + texture = texture.parent as SubTexture; + } + } + + /** Disposes the parent texture if this texture owns it. */ + public override function dispose():void + { + if (_ownsParent) _parent.dispose(); + super.dispose(); + } + + /** The texture which the SubTexture is based on. */ + public function get parent():Texture { return _parent; } + + /** Indicates if the parent texture is disposed when this object is disposed. */ + public function get ownsParent():Boolean { return _ownsParent; } + + /** If true, the SubTexture will show the parent region rotated by 90 degrees (CCW). */ + public function get rotated():Boolean { return _rotated; } + + /** The region of the parent texture that the SubTexture is showing (in points). + * + *

CAUTION: not a copy, but the actual object! Do not modify!

*/ + public function get region():Rectangle { return _region; } + + /** @inheritDoc */ + public override function get transformationMatrix():Matrix { return _transformationMatrix; } + + /** @inheritDoc */ + public override function get transformationMatrixToRoot():Matrix { return _transformationMatrixToRoot; } + + /** @inheritDoc */ + public override function get base():TextureBase { return _parent.base; } + + /** @inheritDoc */ + public override function get root():ConcreteTexture { return _parent.root; } + + /** @inheritDoc */ + public override function get format():String { return _parent.format; } + + /** @inheritDoc */ + public override function get width():Number { return _width; } + + /** @inheritDoc */ + public override function get height():Number { return _height; } + + /** @inheritDoc */ + public override function get nativeWidth():Number { return _width * _scale; } + + /** @inheritDoc */ + public override function get nativeHeight():Number { return _height * _scale; } + + /** @inheritDoc */ + public override function get mipMapping():Boolean { return _parent.mipMapping; } + + /** @inheritDoc */ + public override function get premultipliedAlpha():Boolean { return _parent.premultipliedAlpha; } + + /** @inheritDoc */ + public override function get scale():Number { return _scale; } + + /** @inheritDoc */ + public override function get frame():Rectangle { return _frame; } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/textures/Texture.as b/mobile_version/src/starling/textures/Texture.as new file mode 100644 index 00000000..fab68f23 --- /dev/null +++ b/mobile_version/src/starling/textures/Texture.as @@ -0,0 +1,753 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.textures +{ + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display3D.Context3D; + import flash.display3D.Context3DTextureFormat; + import flash.display3D.textures.RectangleTexture; + import flash.display3D.textures.TextureBase; + import flash.display3D.textures.VideoTexture; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.media.Camera; + import flash.net.NetStream; + import flash.system.Capabilities; + import flash.utils.ByteArray; + import flash.utils.getQualifiedClassName; + + import starling.core.Starling; + import starling.errors.AbstractClassError; + import starling.errors.MissingContextError; + import starling.errors.NotSupportedError; + import starling.rendering.VertexData; + import starling.utils.MathUtil; + import starling.utils.MatrixUtil; + import starling.utils.SystemUtil; + + /**

A texture stores the information that represents an image. It cannot be added to the + * display list directly; instead it has to be mapped onto a display object. In Starling, + * the most probably candidate for this job is the Image class.

+ * + * Creating a texture + * + *

The Texture class is abstract, i.e. you cannot create instance of this + * class through its constructor. Instead, it offers a variety of factory methods, like + * fromBitmapData or fromEmbeddedAsset.

+ * + * Texture Formats + * + *

Since textures can be created from a "BitmapData" object, Starling supports any bitmap + * format that is supported by Flash. And since you can render any Flash display object into + * a BitmapData object, you can use this to display non-Starling content in Starling - e.g. + * Shape objects.

+ * + *

Starling also supports ATF textures (Adobe Texture Format), which is a container for + * compressed texture formats that can be rendered very efficiently by the GPU. Refer to + * the Flash documentation for more information about this format.

+ * + *

Beginning with AIR 17, you can use Starling textures to show video content (if the + * current platform supports it; see "SystemUtil.supportsVideoTexture"). + * The two factory methods "fromCamera" and "fromNetStream" allow you to make use of + * this feature.

+ * + * Mip Mapping + * + *

MipMaps are scaled down versions of a texture. When an image is displayed smaller than + * its natural size, the GPU may display the mip maps instead of the original texture. This + * reduces aliasing and accelerates rendering. It does, however, also need additional memory; + * for that reason, mipmapping is disabled by default.

+ * + * Texture Frame + * + *

The frame property of a texture allows you to let a texture appear inside the bounds of + * an image, leaving a transparent border around the texture. The frame rectangle is specified + * in the coordinate system of the texture (not the image):

+ * + * + * var frame:Rectangle = new Rectangle(-10, -10, 30, 30); + * var texture:Texture = Texture.fromTexture(anotherTexture, null, frame); + * var image:Image = new Image(texture); + * + *

This code would create an image with a size of 30x30, with the texture placed at + * x=10, y=10 within that image (assuming that 'anotherTexture' has a width and + * height of 10 pixels, it would appear in the middle of the image).

+ * + *

The texture atlas makes use of this feature, as it allows to crop transparent edges + * of a texture and making up for the changed size by specifying the original texture frame. + * Tools like TexturePacker use this to + * optimize the atlas.

+ * + * Texture Coordinates + * + *

If, on the other hand, you want to show only a part of the texture in an image + * (i.e. to crop the the texture), you can either create a subtexture (with the method + * 'Texture.fromTexture()' and specifying a rectangle for the region), or you can manipulate + * the texture coordinates of the image object. The method image.setTexCoords + * allows you to do that.

+ * + * Context Loss + * + *

When the current rendering context is lost (which can happen on all platforms, but is + * especially common on Android and Windows), all texture data is destroyed. However, + * Starling will try to restore the textures. To do that, it will keep the bitmap + * and ATF data in memory - at the price of increased RAM consumption. You can optimize + * this behavior, though, by restoring the texture directly from its source, like in this + * example:

+ * + * + * var texture:Texture = Texture.fromBitmap(new EmbeddedBitmap()); + * texture.root.onRestore = function():void + * { + * texture.root.uploadFromBitmap(new EmbeddedBitmap()); + * }; + * + *

The onRestore-method will be called when the context was lost and the + * texture has been recreated (but is still empty). If you use the "AssetManager" class to + * manage your textures, this will be done automatically.

+ * + * @see starling.display.Image + * @see starling.utils.AssetManager + * @see starling.utils.SystemUtil + * @see TextureAtlas + */ + public class Texture + { + // helper objects + private static var sDefaultOptions:TextureOptions = new TextureOptions(); + private static var sRectangle:Rectangle = new Rectangle(); + private static var sMatrix:Matrix = new Matrix(); + private static var sPoint:Point = new Point(); + + /** @private */ + public function Texture() + { + if (Capabilities.isDebugger && + getQualifiedClassName(this) == "starling.textures::Texture") + { + throw new AbstractClassError(); + } + } + + /** Disposes the underlying texture data. Note that not all textures need to be disposed: + * SubTextures (created with 'Texture.fromTexture') just reference other textures and + * and do not take up resources themselves; this is also true for textures from an + * atlas. */ + public function dispose():void + { + // override in subclasses + } + + /** Creates a texture from any of the supported data types, using the specified options. + * + * @param data Either an embedded asset class, a Bitmap, BitmapData, or a ByteArray + * with ATF data. + * @param options Specifies options about the texture settings, e.g. the scale factor. + * If left empty, the default options will be used. + */ + public static function fromData(data:Object, options:TextureOptions=null):Texture + { + if (data is Bitmap) data = (data as Bitmap).bitmapData; + if (options == null) options = sDefaultOptions; + + if (data is Class) + { + return fromEmbeddedAsset(data as Class, + options.mipMapping, options.optimizeForRenderToTexture, + options.scale, options.format, options.forcePotTexture); + } + else if (data is BitmapData) + { + return fromBitmapData(data as BitmapData, + options.mipMapping, options.optimizeForRenderToTexture, + options.scale, options.format, options.forcePotTexture); + } + else if (data is ByteArray) + { + return fromAtfData(data as ByteArray, + options.scale, options.mipMapping, options.onReady); + } + else + throw new ArgumentError("Unsupported 'data' type: " + getQualifiedClassName(data)); + } + + /** Creates a texture from a TextureBase object. + * + * @param base a Stage3D texture object created through the current context. + * @param width the width of the texture in pixels (not points!). + * @param height the height of the texture in pixels (not points!). + * @param options specifies options about the texture settings, e.g. the scale factor. + * If left empty, the default options will be used. Note that not all + * options are supported by all texture types. + */ + public static function fromTextureBase(base:TextureBase, width:int, height:int, + options:TextureOptions=null):ConcreteTexture + { + if (options == null) options = sDefaultOptions; + + if (base is flash.display3D.textures.Texture) + { + return new ConcretePotTexture(base as flash.display3D.textures.Texture, + options.format, width, height, options.mipMapping, + options.premultipliedAlpha, options.optimizeForRenderToTexture, + options.scale); + } + else if (base is RectangleTexture) + { + return new ConcreteRectangleTexture(base as RectangleTexture, + options.format, width, height, options.premultipliedAlpha, + options.optimizeForRenderToTexture, options.scale); + } + else if (base is VideoTexture) + { + return new ConcreteVideoTexture(base as VideoTexture, options.scale); + } + else + throw new ArgumentError("Unsupported 'base' type: " + getQualifiedClassName(base)); + } + + /** Creates a texture object from an embedded asset class. Textures created with this + * method will be restored directly from the asset class in case of a context loss, + * which guarantees a very economic memory usage. + * + * @param assetClass must contain either a Bitmap or a ByteArray with ATF data. + * @param mipMapping for Bitmaps, indicates if mipMaps will be created; + * for ATF data, indicates if the contained mipMaps will be used. + * @param optimizeForRenderToTexture indicates if this texture will be used as + * render target. + * @param scale the scale factor of the created texture. + * @param format the context3D texture format to use. Ignored for ATF data. + * @param forcePotTexture indicates if the underlying Stage3D texture should be created + * as the power-of-two based "Texture" class instead of the more memory + * efficient "RectangleTexture". (Only applicable to bitmaps; ATF + * textures are always POT-textures, anyway.) + */ + public static function fromEmbeddedAsset(assetClass:Class, mipMapping:Boolean=false, + optimizeForRenderToTexture:Boolean=false, + scale:Number=1, format:String="bgra", + forcePotTexture:Boolean=false):Texture + { + var texture:Texture; + var asset:Object = new assetClass(); + + if (asset is Bitmap) + { + texture = Texture.fromBitmap(asset as Bitmap, mipMapping, + optimizeForRenderToTexture, scale, format, forcePotTexture); + texture.root.onRestore = function():void + { + texture.root.uploadBitmap(new assetClass()); + }; + } + else if (asset is ByteArray) + { + texture = Texture.fromAtfData(asset as ByteArray, scale, mipMapping, null); + texture.root.onRestore = function():void + { + texture.root.uploadAtfData(new assetClass()); + }; + } + else + { + throw new ArgumentError("Invalid asset type: " + getQualifiedClassName(asset)); + } + + asset = null; // avoid that object stays in memory (through 'onRestore' functions) + return texture; + } + + /** Creates a texture object from a bitmap. + * Beware: you must not dispose the bitmap's data if Starling should handle a lost device + * context alternatively, you can handle restoration yourself via "texture.root.onRestore". + * + * @param bitmap the texture will be created with the bitmap data of this object. + * @param generateMipMaps indicates if mipMaps will be created. + * @param optimizeForRenderToTexture indicates if this texture will be used as + * render target + * @param scale the scale factor of the created texture. This affects the reported + * width and height of the texture object. + * @param format the context3D texture format to use. Pass one of the packed or + * compressed formats to save memory (at the price of reduced image + * quality). + * @param forcePotTexture indicates if the underlying Stage3D texture should be created + * as the power-of-two based "Texture" class instead of the more memory + * efficient "RectangleTexture". + */ + public static function fromBitmap(bitmap:Bitmap, generateMipMaps:Boolean=false, + optimizeForRenderToTexture:Boolean=false, + scale:Number=1, format:String="bgra", + forcePotTexture:Boolean=false):Texture + { + return fromBitmapData(bitmap.bitmapData, generateMipMaps, optimizeForRenderToTexture, + scale, format, forcePotTexture); + } + + /** Creates a texture object from bitmap data. + * Beware: you must not dispose 'data' if Starling should handle a lost device context; + * alternatively, you can handle restoration yourself via "texture.root.onRestore". + * + * @param data the bitmap data to upload to the texture. + * @param generateMipMaps indicates if mipMaps will be created. + * @param optimizeForRenderToTexture indicates if this texture will be used as + * render target + * @param scale the scale factor of the created texture. This affects the reported + * width and height of the texture object. + * @param format the context3D texture format to use. Pass one of the packed or + * compressed formats to save memory (at the price of reduced image + * quality). + * @param forcePotTexture indicates if the underlying Stage3D texture should be created + * as the power-of-two based "Texture" class instead of the more memory + * efficient "RectangleTexture". + */ + public static function fromBitmapData(data:BitmapData, generateMipMaps:Boolean=false, + optimizeForRenderToTexture:Boolean=false, + scale:Number=1, format:String="bgra", + forcePotTexture:Boolean=false):Texture + { + var texture:Texture = Texture.empty(data.width / scale, data.height / scale, true, + generateMipMaps, optimizeForRenderToTexture, scale, + format, forcePotTexture); + + texture.root.uploadBitmapData(data); + texture.root.onRestore = function():void + { + texture.root.uploadBitmapData(data); + }; + + return texture; + } + + /** Creates a texture from ATF data (Adobe Texture Compression). + * Beware: you must not dispose 'data' if Starling should handle a lost device context; + * alternatively, you can handle restoration yourself via "texture.root.onRestore". + * + * @param data the raw data from an ATF file. + * @param scale the scale factor of the created texture. This affects the reported + * width and height of the texture object. + * @param useMipMaps If the ATF data contains mipmaps, this parameter controls if they + * are used; if it does not, this parameter has no effect. + * @param async If you pass a callback function, the texture will be decoded + * asynchronously, which allows a smooth framerate even during the + * loading process. However, don't use the texture before the callback + * has been executed. This is the expected function definition: + * function(texture:Texture):void; + * @param premultipliedAlpha Indicates if the ATF data contains pixels in PMA format. + * This is "false" for most ATF files, but can be customized in some + * tools. + */ + public static function fromAtfData(data:ByteArray, scale:Number=1, useMipMaps:Boolean=true, + async:Function=null, premultipliedAlpha:Boolean=false):Texture + { + var context:Context3D = Starling.context; + if (context == null) throw new MissingContextError(); + + var atfData:AtfData = new AtfData(data); + var nativeTexture:flash.display3D.textures.Texture = context.createTexture( + atfData.width, atfData.height, atfData.format, false); + var concreteTexture:ConcreteTexture = new ConcretePotTexture(nativeTexture, + atfData.format, atfData.width, atfData.height, useMipMaps && atfData.numTextures > 1, + premultipliedAlpha, false, scale); + + concreteTexture.uploadAtfData(data, 0, async); + concreteTexture.onRestore = function():void + { + concreteTexture.uploadAtfData(data, 0); + }; + + return concreteTexture; + } + + /** Creates a video texture from a NetStream. + * + *

Below, you'll find a minimal sample showing how to stream a video from a file. + * Note that ns.play() is called only after creating the texture, and + * outside the onComplete-callback. It's recommended to always make the + * calls in this order; otherwise, playback won't start on some platforms.

+ * + * + * var nc:NetConnection = new NetConnection(); + * nc.connect(null); + * + * var ns:NetStream = new NetStream(nc); + * var texture:Texture = Texture.fromNetStream(ns, 1, function():void + * { + * addChild(new Image(texture)); + * }); + * + * var file:File = File.applicationDirectory.resolvePath("bugs-bunny.m4v"); + * ns.play(file.url); + * + * @param stream the NetStream from which the video data is streamed. Beware that 'play' + * should be called only after the method returns, and outside the + * onComplete callback. + * @param scale the scale factor of the created texture. This affects the reported + * width and height of the texture object. + * @param onComplete will be executed when the texture is ready. Contains a parameter + * of type 'Texture'. + */ + public static function fromNetStream(stream:NetStream, scale:Number=1, + onComplete:Function=null):Texture + { + // workaround for bug in NetStream class: + if (stream.client == stream && !("onMetaData" in stream)) + stream.client = { onMetaData: function(md:Object):void {} }; + + return fromVideoAttachment("NetStream", stream, scale, onComplete); + } + + /** Creates a video texture from a camera. Beware that the texture must not be used + * before the 'onComplete' callback has been executed; until then, it will have a size + * of zero pixels. + * + *

Here is a minimal sample showing how to display a camera video:

+ * + * + * var camera:Camera = Camera.getCamera(); + * var texture:Texture = Texture.fromCamera(camera, 1, function():void + * { + * addChild(new Image(texture)); + * }); + * + * @param camera the camera from which the video data is streamed. + * @param scale the scale factor of the created texture. This affects the reported + * width and height of the texture object. + * @param onComplete will be executed when the texture is ready. May contain a parameter + * of type 'Texture'. + */ + public static function fromCamera(camera:Camera, scale:Number=1, + onComplete:Function=null):Texture + { + return fromVideoAttachment("Camera", camera, scale, onComplete); + } + + private static function fromVideoAttachment(type:String, attachment:Object, + scale:Number, onComplete:Function):Texture + { + if (!SystemUtil.supportsVideoTexture) + throw new NotSupportedError("Video Textures are not supported on this platform"); + + var context:Context3D = Starling.context; + if (context == null) throw new MissingContextError(); + + var base:VideoTexture = context.createVideoTexture(); + var texture:ConcreteTexture = new ConcreteVideoTexture(base, scale); + texture.attachVideo(type, attachment, onComplete); + texture.onRestore = function():void + { + texture.root.attachVideo(type, attachment); + }; + + return texture; + } + + /** Creates a texture with a certain size and color. + * + * @param width in points; number of pixels depends on scale parameter + * @param height in points; number of pixels depends on scale parameter + * @param color the RGB color the texture will be filled up + * @param alpha the alpha value that will be used for every pixel + * @param optimizeForRenderToTexture indicates if this texture will be used as render target + * @param scale if you omit this parameter, 'Starling.contentScaleFactor' will be used. + * @param format the context3D texture format to use. Pass one of the packed or + * compressed formats to save memory. + * @param forcePotTexture indicates if the underlying Stage3D texture should be created + * as the power-of-two based "Texture" class instead of the more memory + * efficient "RectangleTexture". + */ + public static function fromColor(width:Number, height:Number, + color:uint=0xffffff, alpha:Number=1.0, + optimizeForRenderToTexture:Boolean=false, + scale:Number=-1, format:String="bgra", + forcePotTexture:Boolean=false):Texture + { + var texture:Texture = Texture.empty(width, height, true, false, + optimizeForRenderToTexture, scale, format, forcePotTexture); + texture.root.clear(color, alpha); + texture.root.onRestore = function():void + { + texture.root.clear(color, alpha); + }; + + return texture; + } + + /** Creates an empty texture of a certain size. + * Beware that the texture can only be used after you either upload some color data + * ("texture.root.upload...") or clear the texture ("texture.root.clear()"). + * + * @param width in points; number of pixels depends on scale parameter + * @param height in points; number of pixels depends on scale parameter + * @param premultipliedAlpha the PMA format you will use the texture with. If you will + * use the texture for bitmap data, use "true"; for ATF data, use "false". + * @param mipMapping indicates if mipmaps should be used for this texture. When you upload + * bitmap data, this decides if mipmaps will be created; when you upload ATF + * data, this decides if mipmaps inside the ATF file will be displayed. + * @param optimizeForRenderToTexture indicates if this texture will be used as render target + * @param scale if you omit this parameter, 'Starling.contentScaleFactor' will be used. + * @param format the context3D texture format to use. Pass one of the packed or + * compressed formats to save memory (at the price of reduced image quality). + * @param forcePotTexture indicates if the underlying Stage3D texture should be created + * as the power-of-two based "Texture" class instead of the more memory + * efficient "RectangleTexture". + */ + public static function empty(width:Number, height:Number, premultipliedAlpha:Boolean=true, + mipMapping:Boolean=false, optimizeForRenderToTexture:Boolean=false, + scale:Number=-1, format:String="bgra", + forcePotTexture:Boolean=false):Texture + { + if (scale <= 0) scale = Starling.contentScaleFactor; + + var actualWidth:int, actualHeight:int; + var nativeTexture:TextureBase; + var concreteTexture:ConcreteTexture; + var context:Context3D = Starling.context; + + if (context == null) throw new MissingContextError(); + + var origWidth:Number = width * scale; + var origHeight:Number = height * scale; + var useRectTexture:Boolean = !forcePotTexture && !mipMapping && + Starling.current.profile != "baselineConstrained" && + format.indexOf("compressed") == -1; + + if (useRectTexture) + { + actualWidth = Math.ceil(origWidth - 0.000000001); // avoid floating point errors + actualHeight = Math.ceil(origHeight - 0.000000001); + + nativeTexture = context.createRectangleTexture( + actualWidth, actualHeight, format, optimizeForRenderToTexture); + + concreteTexture = new ConcreteRectangleTexture( + nativeTexture as RectangleTexture, format, actualWidth, actualHeight, + premultipliedAlpha, optimizeForRenderToTexture, scale); + } + else + { + actualWidth = MathUtil.getNextPowerOfTwo(origWidth); + actualHeight = MathUtil.getNextPowerOfTwo(origHeight); + + nativeTexture = context.createTexture( + actualWidth, actualHeight, format, optimizeForRenderToTexture); + + concreteTexture = new ConcretePotTexture( + nativeTexture as flash.display3D.textures.Texture, format, + actualWidth, actualHeight, mipMapping, premultipliedAlpha, + optimizeForRenderToTexture, scale); + } + + concreteTexture.onRestore = concreteTexture.clear; + + if (actualWidth - origWidth < 0.001 && actualHeight - origHeight < 0.001) + return concreteTexture; + else + return new SubTexture(concreteTexture, new Rectangle(0, 0, width, height), true); + } + + /** Creates a texture that contains a region (in pixels) of another texture. The new + * texture will reference the base texture; no data is duplicated. + * + * @param texture The texture you want to create a SubTexture from. + * @param region The region of the parent texture that the SubTexture will show + * (in points). + * @param frame If the texture was trimmed, the frame rectangle can be used to restore + * the trimmed area. + * @param rotated If true, the SubTexture will show the parent region rotated by + * 90 degrees (CCW). + * @param scaleModifier The scale factor of the new texture will be calculated by + * multiplying the parent texture's scale factor with this value. + */ + public static function fromTexture(texture:Texture, region:Rectangle=null, + frame:Rectangle=null, rotated:Boolean=false, + scaleModifier:Number=1.0):Texture + { + return new SubTexture(texture, region, false, frame, rotated, scaleModifier); + } + + /** Sets up a VertexData instance with the correct positions for 4 vertices so that + * the texture can be mapped onto it unscaled. If the texture has a frame, + * the vertices will be offset accordingly. + * + * @param vertexData the VertexData instance to which the positions will be written. + * @param vertexID the start position within the VertexData instance. + * @param attrName the attribute name referencing the vertex positions. + * @param bounds useful only for textures with a frame. This will position the + * vertices at the correct position within the given bounds, + * distorted appropriately. + */ + public function setupVertexPositions(vertexData:VertexData, vertexID:int=0, + attrName:String="position", + bounds:Rectangle=null):void + { + var frame:Rectangle = this.frame; + var width:Number = this.width; + var height:Number = this.height; + + if (frame) + sRectangle.setTo(-frame.x, -frame.y, width, height); + else + sRectangle.setTo(0, 0, width, height); + + vertexData.setPoint(vertexID, attrName, sRectangle.left, sRectangle.top); + vertexData.setPoint(vertexID + 1, attrName, sRectangle.right, sRectangle.top); + vertexData.setPoint(vertexID + 2, attrName, sRectangle.left, sRectangle.bottom); + vertexData.setPoint(vertexID + 3, attrName, sRectangle.right, sRectangle.bottom); + + if (bounds) + { + var scaleX:Number = bounds.width / frameWidth; + var scaleY:Number = bounds.height / frameHeight; + + if (scaleX != 1.0 || scaleY != 1.0 || bounds.x != 0 || bounds.y != 0) + { + sMatrix.identity(); + sMatrix.scale(scaleX, scaleY); + sMatrix.translate(bounds.x, bounds.y); + vertexData.transformPoints(attrName, sMatrix, vertexID, 4); + } + } + } + + /** Sets up a VertexData instance with the correct texture coordinates for + * 4 vertices so that the texture is mapped to the complete quad. + * + * @param vertexData the vertex data to which the texture coordinates will be written. + * @param vertexID the start position within the VertexData instance. + * @param attrName the attribute name referencing the vertex positions. + */ + public function setupTextureCoordinates(vertexData:VertexData, vertexID:int=0, + attrName:String="texCoords"):void + { + setTexCoords(vertexData, vertexID , attrName, 0.0, 0.0); + setTexCoords(vertexData, vertexID + 1, attrName, 1.0, 0.0); + setTexCoords(vertexData, vertexID + 2, attrName, 0.0, 1.0); + setTexCoords(vertexData, vertexID + 3, attrName, 1.0, 1.0); + } + + /** Transforms the given texture coordinates from the local coordinate system + * into the root texture's coordinate system. */ + public function localToGlobal(u:Number, v:Number, out:Point=null):Point + { + if (out == null) out = new Point(); + if (this == root) out.setTo(u, v); + else MatrixUtil.transformCoords(transformationMatrixToRoot, u, v, out); + return out; + } + + /** Transforms the given texture coordinates from the root texture's coordinate system + * to the local coordinate system. */ + public function globalToLocal(u:Number, v:Number, out:Point=null):Point + { + if (out == null) out = new Point(); + if (this == root) out.setTo(u, v); + else + { + sMatrix.identity(); + sMatrix.copyFrom(transformationMatrixToRoot); + sMatrix.invert(); + MatrixUtil.transformCoords(sMatrix, u, v, out); + } + return out; + } + + /** Writes the given texture coordinates to a VertexData instance after transforming + * them into the root texture's coordinate system. That way, the texture coordinates + * can be used directly to sample the texture in the fragment shader. */ + public function setTexCoords(vertexData:VertexData, vertexID:int, attrName:String, + u:Number, v:Number):void + { + localToGlobal(u, v, sPoint); + vertexData.setPoint(vertexID, attrName, sPoint.x, sPoint.y); + } + + /** Reads a pair of texture coordinates from the given VertexData instance and transforms + * them into the current texture's coordinate system. (Remember, the VertexData instance + * will always contain the coordinates in the root texture's coordinate system!) */ + public function getTexCoords(vertexData:VertexData, vertexID:int, + attrName:String="texCoords", out:Point=null):Point + { + if (out == null) out = new Point(); + vertexData.getPoint(vertexID, attrName, out); + return globalToLocal(out.x, out.y, out); + } + + // properties + + /** The texture frame if it has one (see class description), otherwise null. + *

CAUTION: not a copy, but the actual object! Do not modify!

*/ + public function get frame():Rectangle { return null; } + + /** The height of the texture in points, taking into account the frame rectangle + * (if there is one). */ + public function get frameWidth():Number { return frame ? frame.width : width; } + + /** The width of the texture in points, taking into account the frame rectangle + * (if there is one). */ + public function get frameHeight():Number { return frame ? frame.height : height; } + + /** The width of the texture in points. */ + public function get width():Number { return 0; } + + /** The height of the texture in points. */ + public function get height():Number { return 0; } + + /** The width of the texture in pixels (without scale adjustment). */ + public function get nativeWidth():Number { return 0; } + + /** The height of the texture in pixels (without scale adjustment). */ + public function get nativeHeight():Number { return 0; } + + /** The scale factor, which influences width and height properties. */ + public function get scale():Number { return 1.0; } + + /** The Stage3D texture object the texture is based on. */ + public function get base():TextureBase { return null; } + + /** The concrete texture the texture is based on. */ + public function get root():ConcreteTexture { return null; } + + /** The Context3DTextureFormat of the underlying texture data. */ + public function get format():String { return Context3DTextureFormat.BGRA; } + + /** Indicates if the texture contains mip maps. */ + public function get mipMapping():Boolean { return false; } + + /** Indicates if the alpha values are premultiplied into the RGB values. */ + public function get premultipliedAlpha():Boolean { return false; } + + /** The matrix that is used to transform the texture coordinates into the coordinate + * space of the parent texture, if there is one. @default null + * + *

CAUTION: not a copy, but the actual object! Never modify this matrix!

*/ + public function get transformationMatrix():Matrix { return null; } + + /** The matrix that is used to transform the texture coordinates into the coordinate + * space of the root texture, if this instance is not the root. @default null + * + *

CAUTION: not a copy, but the actual object! Never modify this matrix!

*/ + public function get transformationMatrixToRoot():Matrix { return null; } + + /** Returns the maximum size constraint (for both width and height) for textures in the + * current Context3D profile. */ + public static function get maxSize():int + { + var target:Starling = Starling.current; + var profile:String = target ? target.profile : "baseline"; + + if (profile == "baseline" || profile == "baselineConstrained") + return 2048; + else + return 4096; + } + } +} diff --git a/mobile_version/src/starling/textures/TextureAtlas.as b/mobile_version/src/starling/textures/TextureAtlas.as new file mode 100644 index 00000000..c64003da --- /dev/null +++ b/mobile_version/src/starling/textures/TextureAtlas.as @@ -0,0 +1,221 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.textures +{ + import flash.geom.Rectangle; + import flash.utils.Dictionary; + + import starling.utils.StringUtil; + + /** A texture atlas is a collection of many smaller textures in one big image. This class + * is used to access textures from such an atlas. + * + *

Using a texture atlas for your textures solves two problems:

+ * + *
    + *
  • Whenever you switch between textures, the batching of image objects is disrupted.
  • + *
  • Any Stage3D texture has to have side lengths that are powers of two. Starling hides + * this limitation from you, but at the cost of additional graphics memory.
  • + *
+ * + *

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 Starling 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 bundled with Starling's sibling, the + * Sparrow framework. It was only tested in Mac OS X, though. A great multi-platform + * alternative is the commercial tool + * Texture Packer.

+ * + *

Whatever tool you use, Starling expects the following file format:

+ * + * + * <TextureAtlas imagePath='atlas.png'> + * <SubTexture name='texture_1' x='0' y='0' width='50' height='50'/> + * <SubTexture name='texture_2' x='50' y='0' width='20' height='30'/> + * </TextureAtlas> + * + * + * Texture Frame + * + *

If your images have transparent areas at their edges, you can make use of the + * frame property of the Texture class. Trim the texture by removing the + * transparent edges and specify the original texture size like this:

+ * + * + * <SubTexture name='trimmed' x='0' y='0' height='10' width='10' + * frameX='-10' frameY='-10' frameWidth='30' frameHeight='30'/> + * + * + * Texture Rotation + * + *

Some atlas generators can optionally rotate individual textures to optimize the texture + * distribution. This is supported via the boolean attribute "rotated". If it is set to + * true for a certain subtexture, this means that the texture on the atlas + * has been rotated by 90 degrees, clockwise. Starling will undo that rotation by rotating + * it counter-clockwise.

+ * + *

In this case, the positional coordinates (x, y, width, height) + * are expected to point at the subtexture as it is present on the atlas (in its rotated + * form), while the "frame" properties must describe the texture in its upright form.

+ * + */ + public class TextureAtlas + { + private var _atlasTexture:Texture; + private var _subTextures:Dictionary; + private var _subTextureNames:Vector.; + + /** helper objects */ + private static var sNames:Vector. = new []; + + /** Create a texture atlas from a texture by parsing the regions from an XML file. */ + public function TextureAtlas(texture:Texture, atlasXml:XML=null) + { + _subTextures = new Dictionary(); + _atlasTexture = texture; + + if (atlasXml) + parseAtlasXml(atlasXml); + } + + /** Disposes the atlas texture. */ + public function dispose():void + { + _atlasTexture.dispose(); + } + + /** This function is called by the constructor and will parse an XML in Starling's + * default atlas file format. Override this method to create custom parsing logic + * (e.g. to support a different file format). */ + protected function parseAtlasXml(atlasXml:XML):void + { + var scale:Number = _atlasTexture.scale; + var region:Rectangle = new Rectangle(); + var frame:Rectangle = new Rectangle(); + + for each (var subTexture:XML in atlasXml.SubTexture) + { + var name:String = StringUtil.clean(subTexture.@name); + var x:Number = parseFloat(subTexture.@x) / scale; + var y:Number = parseFloat(subTexture.@y) / scale; + var width:Number = parseFloat(subTexture.@width) / scale; + var height:Number = parseFloat(subTexture.@height) / scale; + var frameX:Number = parseFloat(subTexture.@frameX) / scale; + var frameY:Number = parseFloat(subTexture.@frameY) / scale; + var frameWidth:Number = parseFloat(subTexture.@frameWidth) / scale; + var frameHeight:Number = parseFloat(subTexture.@frameHeight) / scale; + var rotated:Boolean = parseBool( subTexture.@rotated); + + region.setTo(x, y, width, height); + frame.setTo(frameX, frameY, frameWidth, frameHeight); + + if (frameWidth > 0 && frameHeight > 0) + addRegion(name, region, frame, rotated); + else + addRegion(name, region, null, rotated); + } + } + + /** Retrieves a SubTexture by name. Returns null if it is not found. */ + public function getTexture(name:String):Texture + { + return _subTextures[name]; + } + + /** Returns all textures that start with a certain string, sorted alphabetically + * (especially useful for "MovieClip"). */ + public function getTextures(prefix:String="", out:Vector.=null):Vector. + { + if (out == null) out = new []; + + for each (var name:String in getNames(prefix, sNames)) + out[out.length] = getTexture(name); // avoid 'push' + + sNames.length = 0; + return out; + } + + /** Returns all texture names that start with a certain string, sorted alphabetically. */ + public function getNames(prefix:String="", out:Vector.=null):Vector. + { + var name:String; + if (out == null) out = new []; + + if (_subTextureNames == null) + { + // optimization: store sorted list of texture names + _subTextureNames = new []; + for (name in _subTextures) _subTextureNames[_subTextureNames.length] = name; + _subTextureNames.sort(Array.CASEINSENSITIVE); + } + + for each (name in _subTextureNames) + if (name.indexOf(prefix) == 0) + out[out.length] = name; + + return out; + } + + /** Returns the region rectangle associated with a specific name, or null + * if no region with that name has been registered. */ + public function getRegion(name:String):Rectangle + { + var subTexture:SubTexture = _subTextures[name]; + return subTexture ? subTexture.region : null; + } + + /** Returns the frame rectangle of a specific region, or null if that region + * has no frame. */ + public function getFrame(name:String):Rectangle + { + var subTexture:SubTexture = _subTextures[name]; + return subTexture ? subTexture.frame : null; + } + + /** If true, the specified region in the atlas is rotated by 90 degrees (clockwise). The + * SubTexture is thus rotated counter-clockwise to cancel out that transformation. */ + public function getRotation(name:String):Boolean + { + var subTexture:SubTexture = _subTextures[name]; + return subTexture ? subTexture.rotated : false; + } + + /** Adds a named region for a SubTexture (described by rectangle with coordinates in + * points) with an optional frame. */ + public function addRegion(name:String, region:Rectangle, frame:Rectangle=null, + rotated:Boolean=false):void + { + _subTextures[name] = new SubTexture(_atlasTexture, region, false, frame, rotated); + _subTextureNames = null; + } + + /** Removes a region with a certain name. */ + public function removeRegion(name:String):void + { + var subTexture:SubTexture = _subTextures[name]; + if (subTexture) subTexture.dispose(); + delete _subTextures[name]; + _subTextureNames = null; + } + + /** The base texture that makes up the atlas. */ + public function get texture():Texture { return _atlasTexture; } + + // utility methods + + private static function parseBool(value:String):Boolean + { + return value.toLowerCase() == "true"; + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/textures/TextureOptions.as b/mobile_version/src/starling/textures/TextureOptions.as new file mode 100644 index 00000000..8fd42fdf --- /dev/null +++ b/mobile_version/src/starling/textures/TextureOptions.as @@ -0,0 +1,100 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.textures +{ + import starling.core.Starling; + + /** The TextureOptions class specifies options for loading textures with the + * Texture.fromData and Texture.fromTextureBase methods. */ + public class TextureOptions + { + private var _scale:Number; + private var _format:String; + private var _mipMapping:Boolean; + private var _optimizeForRenderToTexture:Boolean = false; + private var _premultipliedAlpha:Boolean; + private var _forcePotTexture:Boolean; + private var _onReady:Function = null; + + /** Creates a new instance with the given options. */ + public function TextureOptions(scale:Number=1.0, mipMapping:Boolean=false, + format:String="bgra", premultipliedAlpha:Boolean=true, + forcePotTexture:Boolean=false) + { + _scale = scale; + _format = format; + _mipMapping = mipMapping; + _forcePotTexture = forcePotTexture; + _premultipliedAlpha = premultipliedAlpha; + } + + /** Creates a clone of the TextureOptions object with the exact same properties. */ + public function clone():TextureOptions + { + var clone:TextureOptions = new TextureOptions(_scale, _mipMapping, _format); + clone._optimizeForRenderToTexture = _optimizeForRenderToTexture; + clone._premultipliedAlpha = _premultipliedAlpha; + clone._forcePotTexture = _forcePotTexture; + clone._onReady = _onReady; + return clone; + } + + /** The scale factor, which influences width and height properties. If you pass '-1', + * the current global content scale factor will be used. @default 1.0 */ + public function get scale():Number { return _scale; } + public function set scale(value:Number):void + { + _scale = value > 0 ? value : Starling.contentScaleFactor; + } + + /** The Context3DTextureFormat of the underlying texture data. Only used + * for textures that are created from Bitmaps; the format of ATF files is set when they + * are created. @default BGRA */ + public function get format():String { return _format; } + public function set format(value:String):void { _format = value; } + + /** Indicates if the texture contains mip maps. @default false */ + public function get mipMapping():Boolean { return _mipMapping; } + public function set mipMapping(value:Boolean):void { _mipMapping = value; } + + /** Indicates if the texture will be used as render target. */ + public function get optimizeForRenderToTexture():Boolean { return _optimizeForRenderToTexture; } + public function set optimizeForRenderToTexture(value:Boolean):void { _optimizeForRenderToTexture = value; } + + /** Indicates if the underlying Stage3D texture should be created as the power-of-two based + * Texture class instead of the more memory efficient RectangleTexture. + * That might be useful when you need to render the texture with wrap mode repeat. + * @default false */ + public function get forcePotTexture():Boolean { return _forcePotTexture; } + public function set forcePotTexture(value:Boolean):void { _forcePotTexture = value; } + + /** A callback that is used only for ATF textures; if it is set, the ATF data will be + * decoded asynchronously. The texture can only be used when the callback has been + * executed. This property is ignored for all other texture types (they are ready + * immediately when the 'Texture.from...' method returns, anyway), and it's only used + * by the Texture.fromData factory method. + * + *

This is the expected function definition: + * function(texture:Texture):void;

+ * + * @default null + */ + public function get onReady():Function { return _onReady; } + public function set onReady(value:Function):void { _onReady = value; } + + /** Indicates if the alpha values are premultiplied into the RGB values. This is typically + * true for textures created from BitmapData and false for textures created from ATF data. + * This property will only be read by the Texture.fromTextureBase factory + * method. @default true */ + public function get premultipliedAlpha():Boolean { return _premultipliedAlpha; } + public function set premultipliedAlpha(value:Boolean):void { _premultipliedAlpha = value; } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/textures/TextureSmoothing.as b/mobile_version/src/starling/textures/TextureSmoothing.as new file mode 100644 index 00000000..18b353db --- /dev/null +++ b/mobile_version/src/starling/textures/TextureSmoothing.as @@ -0,0 +1,36 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.textures +{ + import starling.errors.AbstractClassError; + + /** A class that provides constant values for the possible smoothing algorithms of a texture. */ + public class TextureSmoothing + { + /** @private */ + public function TextureSmoothing() { throw new AbstractClassError(); } + + /** No smoothing, also called "Nearest Neighbor". Pixels will scale up as big rectangles. */ + public static const NONE:String = "none"; + + /** Bilinear filtering. Creates smooth transitions between pixels. */ + public static const BILINEAR:String = "bilinear"; + + /** Trilinear filtering. Highest quality by taking the next mip map level into account. */ + public static const TRILINEAR:String = "trilinear"; + + /** Determines whether a smoothing value is valid. */ + public static function isValid(smoothing:String):Boolean + { + return smoothing == NONE || smoothing == BILINEAR || smoothing == TRILINEAR; + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/utils/Align.as b/mobile_version/src/starling/utils/Align.as new file mode 100644 index 00000000..665fd3c9 --- /dev/null +++ b/mobile_version/src/starling/utils/Align.as @@ -0,0 +1,55 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.utils +{ + import starling.errors.AbstractClassError; + + /** A class that provides constant values for horizontal and vertical alignment of objects. */ + public final class Align + { + /** @private */ + public function Align() { throw new AbstractClassError(); } + + /** Horizontal left alignment. */ + public static const LEFT:String = "left"; + + /** Horizontal right alignment. */ + public static const RIGHT:String = "right"; + + /** Vertical top alignment. */ + public static const TOP:String = "top"; + + /** Vertical bottom alignment. */ + public static const BOTTOM:String = "bottom"; + + /** Centered alignment. */ + public static const CENTER:String = "center"; + + /** Indicates whether the given alignment string is valid. */ + public static function isValid(align:String):Boolean + { + return align == LEFT || align == RIGHT || align == CENTER || + align == TOP || align == BOTTOM; + } + + /** Indicates if the given string is a valid horizontal alignment. */ + public static function isValidHorizontal(align:String):Boolean + { + return align == LEFT || align == CENTER || align == RIGHT; + } + + /** Indicates if the given string is a valid vertical alignment. */ + public static function isValidVertical(align:String):Boolean + { + return align == TOP || align == CENTER || align == BOTTOM; + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/utils/AssetManager.as b/mobile_version/src/starling/utils/AssetManager.as new file mode 100644 index 00000000..03636bc2 --- /dev/null +++ b/mobile_version/src/starling/utils/AssetManager.as @@ -0,0 +1,1302 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.utils +{ + import flash.display.Bitmap; + import flash.display.Loader; + import flash.display.LoaderInfo; + import flash.events.HTTPStatusEvent; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.media.Sound; + import flash.media.SoundChannel; + import flash.media.SoundTransform; + import flash.net.FileReference; + import flash.net.URLLoader; + import flash.net.URLLoaderDataFormat; + import flash.net.URLRequest; + import flash.system.ImageDecodingPolicy; + import flash.system.LoaderContext; + import flash.system.System; + import flash.utils.ByteArray; + import flash.utils.Dictionary; + import flash.utils.describeType; + import flash.utils.getQualifiedClassName; + import flash.utils.setTimeout; + + import starling.core.Starling; + import starling.events.Event; + import starling.events.EventDispatcher; + import starling.text.BitmapFont; + import starling.text.TextField; + import starling.textures.AtfData; + import starling.textures.Texture; + import starling.textures.TextureAtlas; + import starling.textures.TextureOptions; + + /** Dispatched when all textures have been restored after a context loss. */ + [Event(name="texturesRestored", type="starling.events.Event")] + + /** Dispatched when an URLLoader fails with an IO_ERROR while processing the queue. + * The 'data' property of the Event contains the URL-String that could not be loaded. */ + [Event(name="ioError", type="starling.events.Event")] + + /** Dispatched when an URLLoader fails with a SECURITY_ERROR while processing the queue. + * The 'data' property of the Event contains the URL-String that could not be loaded. */ + [Event(name="securityError", type="starling.events.Event")] + + /** Dispatched when an XML or JSON file couldn't be parsed. + * The 'data' property of the Event contains the name of the asset that could not be parsed. */ + [Event(name="parseError", type="starling.events.Event")] + + /** The AssetManager handles loading and accessing a variety of asset types. You can + * add assets directly (via the 'add...' methods) or asynchronously via a queue. This allows + * you to deal with assets in a unified way, no matter if they are loaded from a file, + * directory, URL, or from an embedded object. + * + *

The class can deal with the following media types: + *

    + *
  • Textures, either from Bitmaps or ATF data
  • + *
  • Texture atlases
  • + *
  • Bitmap Fonts
  • + *
  • Sounds
  • + *
  • XML data
  • + *
  • JSON data
  • + *
  • ByteArrays
  • + *
+ *

+ * + *

For more information on how to add assets from different sources, read the documentation + * of the "enqueue()" method.

+ * + * Context Loss + * + *

When the stage3D context is lost (and you have enabled 'Starling.handleLostContext'), + * the AssetManager will automatically restore all loaded textures. To save memory, it will + * get them from their original sources. Since this is done asynchronously, your images might + * not reappear all at once, but during a timeframe of several seconds. If you want, you can + * pause your game during that time; the AssetManager dispatches an "Event.TEXTURES_RESTORED" + * event when all textures have been restored.

+ * + * Error handling + * + *

Loading of some assets may fail while the queue is being processed. In that case, the + * AssetManager will dispatch events of type "IO_ERROR", "SECURITY_ERROR" or "PARSE_ERROR". + * You can listen to those events and handle the errors manually (e.g., you could enqueue + * them once again and retry, or provide placeholder textures). Queue processing will + * continue even when those events are dispatched.

+ * + * Using variable texture formats + * + *

When you enqueue a texture, its properties for "format", "scale", "mipMapping", and + * "repeat" will reflect the settings of the AssetManager at the time they were enqueued. + * This means that you can enqueue a bunch of textures, then change the settings and enqueue + * some more. Like this:

+ * + * + * var appDir:File = File.applicationDirectory; + * var assets:AssetManager = new AssetManager(); + * + * assets.textureFormat = Context3DTextureFormat.BGRA; + * assets.enqueue(appDir.resolvePath("textures/32bit")); + * + * assets.textureFormat = Context3DTextureFormat.BGRA_PACKED; + * assets.enqueue(appDir.resolvePath("textures/16bit")); + * + * assets.loadQueue(...); + */ + public class AssetManager extends EventDispatcher + { + // This HTTPStatusEvent is only available in AIR + private static const HTTP_RESPONSE_STATUS:String = "httpResponseStatus"; + + private var _starling:Starling; + private var _numLostTextures:int; + private var _numRestoredTextures:int; + private var _numLoadingQueues:int; + + private var _defaultTextureOptions:TextureOptions; + private var _checkPolicyFile:Boolean; + private var _keepAtlasXmls:Boolean; + private var _keepFontXmls:Boolean; + private var _numConnections:int; + private var _verbose:Boolean; + private var _queue:Array; + + private var _textures:Dictionary; + private var _atlases:Dictionary; + private var _sounds:Dictionary; + private var _xmls:Dictionary; + private var _objects:Dictionary; + private var _byteArrays:Dictionary; + + /** helper objects */ + private static var sNames:Vector. = new []; + + /** Regex for name / extension extraction from URL. */ + private static const NAME_REGEX:RegExp = /([^\?\/\\]+?)(?:\.([\w\-]+))?(?:\?.*)?$/; + + /** Create a new AssetManager. The 'scaleFactor' and 'useMipmaps' parameters define + * how enqueued bitmaps will be converted to textures. */ + public function AssetManager(scaleFactor:Number=1, useMipmaps:Boolean=false) + { + _defaultTextureOptions = new TextureOptions(scaleFactor, useMipmaps); + _textures = new Dictionary(); + _atlases = new Dictionary(); + _sounds = new Dictionary(); + _xmls = new Dictionary(); + _objects = new Dictionary(); + _byteArrays = new Dictionary(); + _numConnections = 3; + _verbose = true; + _queue = []; + } + + /** Disposes all contained textures, XMLs and ByteArrays. + * + *

Beware that all references to the assets will remain intact, even though the assets + * are no longer valid. Call 'purge' if you want to remove all resources and reuse + * the AssetManager later.

+ */ + public function dispose():void + { + for each (var texture:Texture in _textures) + texture.dispose(); + + for each (var atlas:TextureAtlas in _atlases) + atlas.dispose(); + + for each (var xml:XML in _xmls) + System.disposeXML(xml); + + for each (var byteArray:ByteArray in _byteArrays) + byteArray.clear(); + } + + // retrieving + + /** Returns a texture with a certain name. The method first looks through the directly + * added textures; if no texture with that name is found, it scans through all + * texture atlases. */ + public function getTexture(name:String):Texture + { + if (name in _textures) return _textures[name]; + else + { + for each (var atlas:TextureAtlas in _atlases) + { + var texture:Texture = atlas.getTexture(name); + if (texture) return texture; + } + return null; + } + } + + /** Returns all textures that start with a certain string, sorted alphabetically + * (especially useful for "MovieClip"). */ + public function getTextures(prefix:String="", out:Vector.=null):Vector. + { + if (out == null) out = new []; + + for each (var name:String in getTextureNames(prefix, sNames)) + out[out.length] = getTexture(name); // avoid 'push' + + sNames.length = 0; + return out; + } + + /** Returns all texture names that start with a certain string, sorted alphabetically. */ + public function getTextureNames(prefix:String="", out:Vector.=null):Vector. + { + out = getDictionaryKeys(_textures, prefix, out); + + for each (var atlas:TextureAtlas in _atlases) + atlas.getNames(prefix, out); + + out.sort(Array.CASEINSENSITIVE); + return out; + } + + /** Returns a texture atlas with a certain name, or null if it's not found. */ + public function getTextureAtlas(name:String):TextureAtlas + { + return _atlases[name] as TextureAtlas; + } + + /** Returns all texture atlas names that start with a certain string, sorted alphabetically. + * If you pass an out-vector, the names will be added to that vector. */ + public function getTextureAtlasNames(prefix:String="", out:Vector.=null):Vector. + { + return getDictionaryKeys(_atlases, prefix, out); + } + + /** Returns a sound with a certain name, or null if it's not found. */ + public function getSound(name:String):Sound + { + return _sounds[name]; + } + + /** Returns all sound names that start with a certain string, sorted alphabetically. + * If you pass an out-vector, the names will be added to that vector. */ + public function getSoundNames(prefix:String="", out:Vector.=null):Vector. + { + return getDictionaryKeys(_sounds, prefix, out); + } + + /** Generates a new SoundChannel object to play back the sound. This method returns a + * SoundChannel object, which you can access to stop the sound and to control volume. */ + public function playSound(name:String, startTime:Number=0, loops:int=0, + transform:SoundTransform=null):SoundChannel + { + if (name in _sounds) + return getSound(name).play(startTime, loops, transform); + else + return null; + } + + /** Returns an XML with a certain name, or null if it's not found. */ + public function getXml(name:String):XML + { + return _xmls[name]; + } + + /** Returns all XML names that start with a certain string, sorted alphabetically. + * If you pass an out-vector, the names will be added to that vector. */ + public function getXmlNames(prefix:String="", out:Vector.=null):Vector. + { + return getDictionaryKeys(_xmls, prefix, out); + } + + /** Returns an object with a certain name, or null if it's not found. Enqueued JSON + * data is parsed and can be accessed with this method. */ + public function getObject(name:String):Object + { + return _objects[name]; + } + + /** Returns all object names that start with a certain string, sorted alphabetically. + * If you pass an out-vector, the names will be added to that vector. */ + public function getObjectNames(prefix:String="", out:Vector.=null):Vector. + { + return getDictionaryKeys(_objects, prefix, out); + } + + /** Returns a byte array with a certain name, or null if it's not found. */ + public function getByteArray(name:String):ByteArray + { + return _byteArrays[name]; + } + + /** Returns all byte array names that start with a certain string, sorted alphabetically. + * If you pass an out-vector, the names will be added to that vector. */ + public function getByteArrayNames(prefix:String="", out:Vector.=null):Vector. + { + return getDictionaryKeys(_byteArrays, prefix, out); + } + + // direct adding + + /** Register a texture under a certain name. It will be available right away. + * If the name was already taken, the existing texture will be disposed and replaced + * by the new one. */ + public function addTexture(name:String, texture:Texture):void + { + log("Adding texture '" + name + "'"); + + if (name in _textures) + { + log("Warning: name was already in use; the previous texture will be replaced."); + _textures[name].dispose(); + } + + _textures[name] = texture; + } + + /** Register a texture atlas under a certain name. It will be available right away. + * If the name was already taken, the existing atlas will be disposed and replaced + * by the new one. */ + public function addTextureAtlas(name:String, atlas:TextureAtlas):void + { + log("Adding texture atlas '" + name + "'"); + + if (name in _atlases) + { + log("Warning: name was already in use; the previous atlas will be replaced."); + _atlases[name].dispose(); + } + + _atlases[name] = atlas; + } + + /** Register a sound under a certain name. It will be available right away. + * If the name was already taken, the existing sound will be replaced by the new one. */ + public function addSound(name:String, sound:Sound):void + { + log("Adding sound '" + name + "'"); + + if (name in _sounds) + log("Warning: name was already in use; the previous sound will be replaced."); + + _sounds[name] = sound; + } + + /** Register an XML object under a certain name. It will be available right away. + * If the name was already taken, the existing XML will be disposed and replaced + * by the new one. */ + public function addXml(name:String, xml:XML):void + { + log("Adding XML '" + name + "'"); + + if (name in _xmls) + { + log("Warning: name was already in use; the previous XML will be replaced."); + System.disposeXML(_xmls[name]); + } + + _xmls[name] = xml; + } + + /** Register an arbitrary object under a certain name. It will be available right away. + * If the name was already taken, the existing object will be replaced by the new one. */ + public function addObject(name:String, object:Object):void + { + log("Adding object '" + name + "'"); + + if (name in _objects) + log("Warning: name was already in use; the previous object will be replaced."); + + _objects[name] = object; + } + + /** Register a byte array under a certain name. It will be available right away. + * If the name was already taken, the existing byte array will be cleared and replaced + * by the new one. */ + public function addByteArray(name:String, byteArray:ByteArray):void + { + log("Adding byte array '" + name + "'"); + + if (name in _byteArrays) + { + log("Warning: name was already in use; the previous byte array will be replaced."); + _byteArrays[name].clear(); + } + + _byteArrays[name] = byteArray; + } + + // removing + + /** Removes a certain texture, optionally disposing it. */ + public function removeTexture(name:String, dispose:Boolean=true):void + { + log("Removing texture '" + name + "'"); + + if (dispose && name in _textures) + _textures[name].dispose(); + + delete _textures[name]; + } + + /** Removes a certain texture atlas, optionally disposing it. */ + public function removeTextureAtlas(name:String, dispose:Boolean=true):void + { + log("Removing texture atlas '" + name + "'"); + + if (dispose && name in _atlases) + _atlases[name].dispose(); + + delete _atlases[name]; + } + + /** Removes a certain sound. */ + public function removeSound(name:String):void + { + log("Removing sound '"+ name + "'"); + delete _sounds[name]; + } + + /** Removes a certain Xml object, optionally disposing it. */ + public function removeXml(name:String, dispose:Boolean=true):void + { + log("Removing xml '"+ name + "'"); + + if (dispose && name in _xmls) + System.disposeXML(_xmls[name]); + + delete _xmls[name]; + } + + /** Removes a certain object. */ + public function removeObject(name:String):void + { + log("Removing object '"+ name + "'"); + delete _objects[name]; + } + + /** Removes a certain byte array, optionally disposing its memory right away. */ + public function removeByteArray(name:String, dispose:Boolean=true):void + { + log("Removing byte array '"+ name + "'"); + + if (dispose && name in _byteArrays) + _byteArrays[name].clear(); + + delete _byteArrays[name]; + } + + /** Empties the queue and aborts any pending load operations. */ + public function purgeQueue():void + { + _queue.length = 0; + dispatchEventWith(Event.CANCEL); + } + + /** Removes assets of all types (disposing them along the way), empties the queue and + * aborts any pending load operations. */ + public function purge():void + { + log("Purging all assets, emptying queue"); + + purgeQueue(); + dispose(); + + _textures = new Dictionary(); + _atlases = new Dictionary(); + _sounds = new Dictionary(); + _xmls = new Dictionary(); + _objects = new Dictionary(); + _byteArrays = new Dictionary(); + } + + // queued adding + + /** Enqueues one or more raw assets; they will only be available after successfully + * executing the "loadQueue" method. This method accepts a variety of different objects: + * + *
    + *
  • Strings or URLRequests containing an URL to a local or remote resource. Supported + * types: png, jpg, gif, atf, mp3, xml, fnt, json, binary.
  • + *
  • Instances of the File class (AIR only) pointing to a directory or a file. + * Directories will be scanned recursively for all supported types.
  • + *
  • Classes that contain static embedded assets.
  • + *
  • If the file extension is not recognized, the data is analyzed to see if + * contains XML or JSON data. If it's neither, it is stored as ByteArray.
  • + *
+ * + *

Suitable object names are extracted automatically: A file named "image.png" will be + * accessible under the name "image". When enqueuing embedded assets via a class, + * the variable name of the embedded object will be used as its name. An exception + * are texture atlases: they will have the same name as the actual texture they are + * referencing.

+ * + *

XMLs that contain texture atlases or bitmap fonts are processed directly: fonts are + * registered at the TextField class, atlas textures can be acquired with the + * "getTexture()" method. All other XMLs are available via "getXml()".

+ * + *

If you pass in JSON data, it will be parsed into an object and will be available via + * "getObject()".

+ */ + public function enqueue(...rawAssets):void + { + for each (var rawAsset:Object in rawAssets) + { + if (rawAsset is Array) + { + enqueue.apply(this, rawAsset); + } + else if (rawAsset is Class) + { + var typeXml:XML = describeType(rawAsset); + var childNode:XML; + + if (_verbose) + log("Looking for static embedded assets in '" + + (typeXml.@name).split("::").pop() + "'"); + + for each (childNode in typeXml.constant.(@type == "Class")) + enqueueWithName(rawAsset[childNode.@name], childNode.@name); + + for each (childNode in typeXml.variable.(@type == "Class")) + enqueueWithName(rawAsset[childNode.@name], childNode.@name); + } + else if (getQualifiedClassName(rawAsset) == "flash.filesystem::File") + { + if (!rawAsset["exists"]) + { + log("File or directory not found: '" + rawAsset["url"] + "'"); + } + else if (!rawAsset["isHidden"]) + { + if (rawAsset["isDirectory"]) + enqueue.apply(this, rawAsset["getDirectoryListing"]()); + else + enqueueWithName(rawAsset); + } + } + else if (rawAsset is String || rawAsset is URLRequest) + { + enqueueWithName(rawAsset); + } + else + { + log("Ignoring unsupported asset type: " + getQualifiedClassName(rawAsset)); + } + } + } + + /** Enqueues a single asset with a custom name that can be used to access it later. + * If the asset is a texture, you can also add custom texture options. + * + * @param asset The asset that will be enqueued; accepts the same objects as the + * 'enqueue' method. + * @param name The name under which the asset will be found later. If you pass null or + * omit the parameter, it's attempted to generate a name automatically. + * @param options Custom options that will be used if 'asset' points to texture data. + * @return the name with which the asset was registered. + */ + public function enqueueWithName(asset:Object, name:String=null, + options:TextureOptions=null):String + { + var filename:String = null; + + if (getQualifiedClassName(asset) == "flash.filesystem::File") + { + filename = asset["name"]; + asset = decodeURI(asset["url"]); + } + + if (name == null) name = getName(asset); + if (options == null) options = _defaultTextureOptions.clone(); + else options = options.clone(); + + log("Enqueuing '" + (filename || name) + "'"); + + _queue.push({ + name: name, + asset: asset, + options: options + }); + + return name; + } + + /** Loads all enqueued assets asynchronously. The 'onProgress' function will be called + * with a 'ratio' between '0.0' and '1.0', with '1.0' meaning that it's complete. + * + *

When you call this method, the manager will save a reference to "Starling.current"; + * all textures that are loaded will be accessible only from within this instance. Thus, + * if you are working with more than one Starling instance, be sure to call + * "makeCurrent()" on the appropriate instance before processing the queue.

+ * + * @param onProgress function(ratio:Number):void; + */ + public function loadQueue(onProgress:Function):void + { + if (onProgress == null) + throw new ArgumentError("Argument 'onProgress' must not be null"); + + if (_queue.length == 0) + { + onProgress(1.0); + return; + } + + _starling = Starling.current; + + if (_starling == null || _starling.context == null) + throw new Error("The Starling instance needs to be ready before assets can be loaded."); + + const PROGRESS_PART_ASSETS:Number = 0.9; + const PROGRESS_PART_XMLS:Number = 1.0 - PROGRESS_PART_ASSETS; + + var i:int; + var canceled:Boolean = false; + var xmls:Vector. = new []; + var assetInfos:Array = _queue.concat(); + var assetCount:int = _queue.length; + var assetProgress:Array = []; + var assetIndex:int = 0; + + for (i=0; i 0) loadNextQueueElement(); + else processXmls(); + }; + + processRawAsset(assetInfo.name, assetInfo.asset, assetInfo.options, + xmls, onElementProgress, onElementLoaded); + } + + function updateAssetProgress(index:int, progress:Number):void + { + assetProgress[index] = progress; + + var sum:Number = 0.0; + var len:int = assetProgress.length; + + for (i=0; i, + onProgress:Function, onComplete:Function):void + { + var canceled:Boolean = false; + + addEventListener(Event.CANCEL, cancel); + loadRawAsset(rawAsset, progress, process); + + function process(asset:Object):void + { + var texture:Texture; + var bytes:ByteArray; + var object:Object = null; + var xml:XML = null; + + // the 'current' instance might have changed by now + // if we're running in a set-up with multiple instances. + _starling.makeCurrent(); + + if (canceled) + { + // do nothing + } + else if (asset == null) + { + onComplete(); + } + else if (asset is Sound) + { + addSound(name, asset as Sound); + onComplete(); + } + else if (asset is XML) + { + xml = asset as XML; + + if (xml.localName() == "TextureAtlas" || xml.localName() == "font") + xmls.push(xml); + else + addXml(name, xml); + + onComplete(); + } + else if (_starling.context.driverInfo == "Disposed") + { + log("Context lost while processing assets, retrying ..."); + setTimeout(process, 1, asset); + return; // to keep CANCEL event listener intact + } + else if (asset is Bitmap) + { + texture = Texture.fromData(asset, options); + texture.root.onRestore = function():void + { + _numLostTextures++; + loadRawAsset(rawAsset, null, function(asset:Object):void + { + try + { + if (asset == null) throw new Error("Reload failed"); + texture.root.uploadBitmap(asset as Bitmap); + asset.bitmapData.dispose(); + } + catch (e:Error) + { + log("Texture restoration failed for '" + name + "': " + e.message); + } + + _numRestoredTextures++; + Starling.current.stage.setRequiresRedraw(); + + if (_numLostTextures == _numRestoredTextures) + dispatchEventWith(Event.TEXTURES_RESTORED); + }); + }; + + asset.bitmapData.dispose(); + addTexture(name, texture); + onComplete(); + } + else if (asset is ByteArray) + { + bytes = asset as ByteArray; + + if (AtfData.isAtfData(bytes)) + { + options.onReady = prependCallback(options.onReady, function():void + { + addTexture(name, texture); + onComplete(); + }); + + texture = Texture.fromData(bytes, options); + texture.root.onRestore = function():void + { + _numLostTextures++; + loadRawAsset(rawAsset, null, function(asset:Object):void + { + try + { + if (asset == null) throw new Error("Reload failed"); + texture.root.uploadAtfData(asset as ByteArray, 0, false); + asset.clear(); + } + catch (e:Error) + { + log("Texture restoration failed for '" + name + "': " + e.message); + } + + _numRestoredTextures++; + Starling.current.stage.setRequiresRedraw(); + + if (_numLostTextures == _numRestoredTextures) + dispatchEventWith(Event.TEXTURES_RESTORED); + }); + }; + + bytes.clear(); + } + else if (byteArrayStartsWith(bytes, "{") || byteArrayStartsWith(bytes, "[")) + { + try { object = JSON.parse(bytes.readUTFBytes(bytes.length)); } + catch (e:Error) + { + log("Could not parse JSON: " + e.message); + dispatchEventWith(Event.PARSE_ERROR, false, name); + } + + if (object) addObject(name, object); + + bytes.clear(); + onComplete(); + } + else if (byteArrayStartsWith(bytes, "<")) + { + try { xml = new XML(bytes); } + catch (e:Error) + { + log("Could not parse XML: " + e.message); + dispatchEventWith(Event.PARSE_ERROR, false, name); + } + + process(xml); + bytes.clear(); + } + else + { + addByteArray(name, bytes); + onComplete(); + } + } + else + { + addObject(name, asset); + onComplete(); + } + + // avoid that objects stay in memory (through 'onRestore' functions) + asset = null; + bytes = null; + + removeEventListener(Event.CANCEL, cancel); + } + + function progress(ratio:Number):void + { + if (!canceled) onProgress(ratio); + } + + function cancel():void + { + canceled = true; + } + } + + /** This method is called internally for each element of the queue when it is loaded. + * 'rawAsset' is typically either a class (pointing to an embedded asset) or a string + * (containing the path to a file). For texture data, it will also be called after a + * context loss. + * + *

The method has to transform this object into one of the types that the AssetManager + * can work with, e.g. a Bitmap, a Sound, XML data, or a ByteArray. This object needs to + * be passed to the 'onComplete' callback.

+ * + *

The calling method will then process this data accordingly (e.g. a Bitmap will be + * transformed into a texture). Unknown types will be available via 'getObject()'.

+ * + *

When overriding this method, you can call 'onProgress' with a number between 0 and 1 + * to update the total queue loading progress.

+ */ + protected function loadRawAsset(rawAsset:Object, onProgress:Function, onComplete:Function):void + { + var extension:String = null; + var loaderInfo:LoaderInfo = null; + var urlLoader:URLLoader = null; + var urlRequest:URLRequest = null; + var url:String = null; + + if (rawAsset is Class) + { + setTimeout(complete, 1, new rawAsset()); + } + else if (rawAsset is String || rawAsset is URLRequest) + { + urlRequest = rawAsset as URLRequest || new URLRequest(rawAsset as String); + url = urlRequest.url; + extension = getExtensionFromUrl(url); + + urlLoader = new URLLoader(); + urlLoader.dataFormat = URLLoaderDataFormat.BINARY; + urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onIoError); + urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError); + urlLoader.addEventListener(HTTP_RESPONSE_STATUS, onHttpResponseStatus); + urlLoader.addEventListener(ProgressEvent.PROGRESS, onLoadProgress); + urlLoader.addEventListener(Event.COMPLETE, onUrlLoaderComplete); + urlLoader.load(urlRequest); + } + + function onIoError(event:IOErrorEvent):void + { + log("IO error: " + event.text); + dispatchEventWith(Event.IO_ERROR, false, url); + complete(null); + } + + function onSecurityError(event:SecurityErrorEvent):void + { + log("security error: " + event.text); + dispatchEventWith(Event.SECURITY_ERROR, false, url); + complete(null); + } + + function onHttpResponseStatus(event:HTTPStatusEvent):void + { + if (extension == null) + { + var headers:Array = event["responseHeaders"]; + var contentType:String = getHttpHeader(headers, "Content-Type"); + + if (contentType && /(audio|image)\//.exec(contentType)) + extension = contentType.split("/").pop(); + } + } + + function onLoadProgress(event:ProgressEvent):void + { + if (onProgress != null && event.bytesTotal > 0) + onProgress(event.bytesLoaded / event.bytesTotal); + } + + function onUrlLoaderComplete(event:Object):void + { + var bytes:ByteArray = transformData(urlLoader.data as ByteArray, url); + var sound:Sound; + + if (bytes == null) + { + complete(null); + return; + } + + if (extension) + extension = extension.toLowerCase(); + + switch (extension) + { + case "mpeg": + case "mp3": + sound = new Sound(); + sound.loadCompressedDataFromByteArray(bytes, bytes.length); + bytes.clear(); + complete(sound); + break; + case "jpg": + case "jpeg": + case "png": + case "gif": + var loaderContext:LoaderContext = new LoaderContext(_checkPolicyFile); + var loader:Loader = new Loader(); + loaderContext.imageDecodingPolicy = ImageDecodingPolicy.ON_LOAD; + loaderInfo = loader.contentLoaderInfo; + loaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onIoError); + loaderInfo.addEventListener(Event.COMPLETE, onLoaderComplete); + loader.loadBytes(bytes, loaderContext); + break; + default: // any XML / JSON / binary data + complete(bytes); + break; + } + } + + function onLoaderComplete(event:Object):void + { + urlLoader.data.clear(); + complete(event.target.content); + } + + function complete(asset:Object):void + { + // clean up event listeners + + if (urlLoader) + { + urlLoader.removeEventListener(IOErrorEvent.IO_ERROR, onIoError); + urlLoader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError); + urlLoader.removeEventListener(HTTP_RESPONSE_STATUS, onHttpResponseStatus); + urlLoader.removeEventListener(ProgressEvent.PROGRESS, onLoadProgress); + urlLoader.removeEventListener(Event.COMPLETE, onUrlLoaderComplete); + } + + if (loaderInfo) + { + loaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, onIoError); + loaderInfo.removeEventListener(Event.COMPLETE, onLoaderComplete); + } + + // On mobile, it is not allowed / endorsed to make stage3D calls while the app + // is in the background. Thus, we pause queue processing if that's the case. + + if (SystemUtil.isDesktop) + onComplete(asset); + else + SystemUtil.executeWhenApplicationIsActive(onComplete, asset); + } + } + + // helpers + + /** This method is called by 'enqueue' to determine the name under which an asset will be + * accessible; override it if you need a custom naming scheme. Note that this method won't + * be called for embedded assets. + * + * @param rawAsset either a String, an URLRequest or a FileReference. + */ + protected function getName(rawAsset:Object):String + { + var name:String; + + if (rawAsset is String) name = rawAsset as String; + else if (rawAsset is URLRequest) name = (rawAsset as URLRequest).url; + else if (rawAsset is FileReference) name = (rawAsset as FileReference).name; + + if (name) + { + name = name.replace(/%20/g, " "); // URLs use '%20' for spaces + name = getBasenameFromUrl(name); + + if (name) return name; + else throw new ArgumentError("Could not extract name from String '" + rawAsset + "'"); + } + else + { + name = getQualifiedClassName(rawAsset); + throw new ArgumentError("Cannot extract names for objects of type '" + name + "'"); + } + } + + /** This method is called when raw byte data has been loaded from an URL or a file. + * Override it to process the downloaded data in some way (e.g. decompression) or + * to cache it on disk. + * + *

It's okay to call one (or more) of the 'add...' methods from here. If the binary + * data contains multiple objects, this allows you to process all of them at once. + * Return 'null' to abort processing of the current item.

*/ + protected function transformData(data:ByteArray, url:String):ByteArray + { + return data; + } + + /** This method is called during loading of assets when 'verbose' is activated. Per + * default, it traces 'message' to the console. */ + protected function log(message:String):void + { + if (_verbose) trace("[AssetManager]", message); + } + + private function byteArrayStartsWith(bytes:ByteArray, char:String):Boolean + { + var start:int = 0; + var length:int = bytes.length; + var wanted:int = char.charCodeAt(0); + + // recognize BOMs + + if (length >= 4 && + (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0xfe && bytes[3] == 0xff) || + (bytes[0] == 0xff && bytes[1] == 0xfe && bytes[2] == 0x00 && bytes[3] == 0x00)) + { + start = 4; // UTF-32 + } + else if (length >= 3 && bytes[0] == 0xef && bytes[1] == 0xbb && bytes[2] == 0xbf) + { + start = 3; // UTF-8 + } + else if (length >= 2 && + (bytes[0] == 0xfe && bytes[1] == 0xff) || (bytes[0] == 0xff && bytes[1] == 0xfe)) + { + start = 2; // UTF-16 + } + + // find first meaningful letter + + for (var i:int=start; i=null):Vector. + { + if (out == null) out = new []; + + for (var name:String in dictionary) + if (name.indexOf(prefix) == 0) + out[out.length] = name; // avoid 'push' + + out.sort(Array.CASEINSENSITIVE); + return out; + } + + private function getHttpHeader(headers:Array, headerName:String):String + { + if (headers) + { + for each (var header:Object in headers) + if (header.name == headerName) return header.value; + } + return null; + } + + /** Extracts the base name of a file path or URL, i.e. the file name without extension. */ + protected function getBasenameFromUrl(url:String):String + { + var matches:Array = NAME_REGEX.exec(url); + if (matches && matches.length > 0) return matches[1]; + else return null; + } + + /** Extracts the file extension from an URL. */ + protected function getExtensionFromUrl(url:String):String + { + var matches:Array = NAME_REGEX.exec(url); + if (matches && matches.length > 1) return matches[2]; + else return null; + } + + private function prependCallback(oldCallback:Function, newCallback:Function):Function + { + // TODO: it might make sense to add this (together with "appendCallback") + // as a public utility method ("FunctionUtil"?) + + if (oldCallback == null) return newCallback; + else if (newCallback == null) return oldCallback; + else return function():void + { + newCallback(); + oldCallback(); + }; + } + + // properties + + /** The queue contains one 'Object' for each enqueued asset. Each object has 'asset' + * and 'name' properties, pointing to the raw asset and its name, respectively. */ + protected function get queue():Array { return _queue; } + + /** Returns the number of raw assets that have been enqueued, but not yet loaded. */ + public function get numQueuedAssets():int { return _queue.length; } + + /** When activated, the class will trace information about added/enqueued assets. + * @default true */ + public function get verbose():Boolean { return _verbose; } + public function set verbose(value:Boolean):void { _verbose = value; } + + /** Indicates if a queue is currently being loaded. */ + public function get isLoading():Boolean { return _numLoadingQueues > 0; } + + /** For bitmap textures, this flag indicates if mip maps should be generated when they + * are loaded; for ATF textures, it indicates if mip maps are valid and should be + * used. @default false */ + public function get useMipMaps():Boolean { return _defaultTextureOptions.mipMapping; } + public function set useMipMaps(value:Boolean):void { _defaultTextureOptions.mipMapping = value; } + + /** Textures that are created from Bitmaps or ATF files will have the scale factor + * assigned here. @default 1 */ + public function get scaleFactor():Number { return _defaultTextureOptions.scale; } + public function set scaleFactor(value:Number):void { _defaultTextureOptions.scale = value; } + + /** Textures that are created from Bitmaps will be uploaded to the GPU with the + * Context3DTextureFormat assigned to this property. @default "bgra" */ + public function get textureFormat():String { return _defaultTextureOptions.format; } + public function set textureFormat(value:String):void { _defaultTextureOptions.format = value; } + + /** Indicates if the underlying Stage3D textures should be created as the power-of-two based + * Texture class instead of the more memory efficient RectangleTexture. + * @default false */ + public function get forcePotTextures():Boolean { return _defaultTextureOptions.forcePotTexture; } + public function set forcePotTextures(value:Boolean):void { _defaultTextureOptions.forcePotTexture = value; } + + /** Specifies whether a check should be made for the existence of a URL policy file before + * loading an object from a remote server. More information about this topic can be found + * in the 'flash.system.LoaderContext' documentation. @default false */ + public function get checkPolicyFile():Boolean { return _checkPolicyFile; } + public function set checkPolicyFile(value:Boolean):void { _checkPolicyFile = value; } + + /** Indicates if atlas XML data should be stored for access via the 'getXml' method. + * If true, you can access an XML under the same name as the atlas. + * If false, XMLs will be disposed when the atlas was created. @default false. */ + public function get keepAtlasXmls():Boolean { return _keepAtlasXmls; } + public function set keepAtlasXmls(value:Boolean):void { _keepAtlasXmls = value; } + + /** Indicates if bitmap font XML data should be stored for access via the 'getXml' method. + * If true, you can access an XML under the same name as the bitmap font. + * If false, XMLs will be disposed when the font was created. @default false. */ + public function get keepFontXmls():Boolean { return _keepFontXmls; } + public function set keepFontXmls(value:Boolean):void { _keepFontXmls = value; } + + /** The maximum number of parallel connections that are spawned when loading the queue. + * More connections can reduce loading times, but require more memory. @default 3. */ + public function get numConnections():int { return _numConnections; } + public function set numConnections(value:int):void { _numConnections = value; } + } +} diff --git a/mobile_version/src/starling/utils/Color.as b/mobile_version/src/starling/utils/Color.as new file mode 100644 index 00000000..55f8a372 --- /dev/null +++ b/mobile_version/src/starling/utils/Color.as @@ -0,0 +1,117 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.utils +{ + import starling.errors.AbstractClassError; + + /** A utility class containing predefined colors and methods converting between different + * color representations. */ + public class Color + { + public static const WHITE:uint = 0xffffff; + public static const SILVER:uint = 0xc0c0c0; + public static const GRAY:uint = 0x808080; + public static const BLACK:uint = 0x000000; + public static const RED:uint = 0xff0000; + public static const MAROON:uint = 0x800000; + public static const YELLOW:uint = 0xffff00; + public static const OLIVE:uint = 0x808000; + public static const LIME:uint = 0x00ff00; + public static const GREEN:uint = 0x008000; + public static const AQUA:uint = 0x00ffff; + public static const TEAL:uint = 0x008080; + public static const BLUE:uint = 0x0000ff; + public static const NAVY:uint = 0x000080; + public static const FUCHSIA:uint = 0xff00ff; + public static const PURPLE:uint = 0x800080; + + /** Returns the alpha part of an ARGB color (0 - 255). */ + public static function getAlpha(color:uint):int { return (color >> 24) & 0xff; } + + /** Returns the red part of an (A)RGB color (0 - 255). */ + public static function getRed(color:uint):int { return (color >> 16) & 0xff; } + + /** Returns the green part of an (A)RGB color (0 - 255). */ + public static function getGreen(color:uint):int { return (color >> 8) & 0xff; } + + /** Returns the blue part of an (A)RGB color (0 - 255). */ + public static function getBlue(color:uint):int { return color & 0xff; } + + /** Creates an RGB color, stored in an unsigned integer. Channels are expected + * in the range 0 - 255. */ + public static function rgb(red:int, green:int, blue:int):uint + { + return (red << 16) | (green << 8) | blue; + } + + /** Creates an ARGB color, stored in an unsigned integer. Channels are expected + * in the range 0 - 255. */ + public static function argb(alpha:int, red:int, green:int, blue:int):uint + { + return (alpha << 24) | (red << 16) | (green << 8) | blue; + } + + /** Converts a color to a vector containing the RGBA components (in this order) scaled + * between 0 and 1. */ + public static function toVector(color:uint, out:Vector.=null):Vector. + { + if (out == null) out = new Vector.(4, true); + + out[0] = ((color >> 16) & 0xff) / 255.0; + out[1] = ((color >> 8) & 0xff) / 255.0; + out[2] = ( color & 0xff) / 255.0; + out[3] = ((color >> 24) & 0xff) / 255.0; + + return out; + } + + /** Multiplies all channels of an (A)RGB color with a certain factor. */ + public static function multiply(color:uint, factor:Number):uint + { + var alpha:uint = ((color >> 24) & 0xff) * factor; + var red:uint = ((color >> 16) & 0xff) * factor; + var green:uint = ((color >> 8) & 0xff) * factor; + var blue:uint = ( color & 0xff) * factor; + + if (alpha > 255) alpha = 255; + if (red > 255) red = 255; + if (green > 255) green = 255; + if (blue > 255) blue = 255; + + return argb(alpha, red, green, blue); + } + + /** Calculates a smooth transition between one color to the next. + * ratio is expected between 0 and 1. */ + public static function interpolate(startColor:uint, endColor:uint, ratio:Number):uint + { + var startA:uint = (startColor >> 24) & 0xff; + var startR:uint = (startColor >> 16) & 0xff; + var startG:uint = (startColor >> 8) & 0xff; + var startB:uint = (startColor ) & 0xff; + + var endA:uint = (endColor >> 24) & 0xff; + var endR:uint = (endColor >> 16) & 0xff; + var endG:uint = (endColor >> 8) & 0xff; + var endB:uint = (endColor ) & 0xff; + + var newA:uint = startA + (endA - startA) * ratio; + var newR:uint = startR + (endR - startR) * ratio; + var newG:uint = startG + (endG - startG) * ratio; + var newB:uint = startB + (endB - startB) * ratio; + + return (newA << 24) | (newR << 16) | (newG << 8) | newB; + } + + /** @private */ + public function Color() { throw new AbstractClassError(); } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/utils/MathUtil.as b/mobile_version/src/starling/utils/MathUtil.as new file mode 100644 index 00000000..07b5db8d --- /dev/null +++ b/mobile_version/src/starling/utils/MathUtil.as @@ -0,0 +1,124 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.utils +{ + import flash.geom.Point; + import flash.geom.Vector3D; + + import starling.errors.AbstractClassError; + + /** A utility class containing methods you might need for math problems. */ + public class MathUtil + { + private static const TWO_PI:Number = Math.PI * 2.0; + + /** @private */ + public function MathUtil() { throw new AbstractClassError(); } + + /** Calculates the intersection point between the xy-plane and an infinite line + * that is defined by two 3D points in the same coordinate system. */ + public static function intersectLineWithXYPlane(pointA:Vector3D, pointB:Vector3D, + out:Point=null):Point + { + if (out == null) out = new Point(); + + var vectorX:Number = pointB.x - pointA.x; + var vectorY:Number = pointB.y - pointA.y; + var vectorZ:Number = pointB.z - pointA.z; + var lambda:Number = -pointA.z / vectorZ; + + out.x = pointA.x + lambda * vectorX; + out.y = pointA.y + lambda * vectorY; + + return out; + } + + /** Calculates if the point p is inside the triangle a-b-c. */ + public static function isPointInTriangle(p:Point, a:Point, b:Point, c:Point):Boolean + { + // This algorithm is described well in this article: + // http://www.blackpawn.com/texts/pointinpoly/default.html + + var v0x:Number = c.x - a.x; + var v0y:Number = c.y - a.y; + var v1x:Number = b.x - a.x; + var v1y:Number = b.y - a.y; + var v2x:Number = p.x - a.x; + var v2y:Number = p.y - a.y; + + var dot00:Number = v0x * v0x + v0y * v0y; + var dot01:Number = v0x * v1x + v0y * v1y; + var dot02:Number = v0x * v2x + v0y * v2y; + var dot11:Number = v1x * v1x + v1y * v1y; + var dot12:Number = v1x * v2x + v1y * v2y; + + var invDen:Number = 1.0 / (dot00 * dot11 - dot01 * dot01); + var u:Number = (dot11 * dot02 - dot01 * dot12) * invDen; + var v:Number = (dot00 * dot12 - dot01 * dot02) * invDen; + + return (u >= 0) && (v >= 0) && (u + v < 1); + } + + /** Moves a radian angle into the range [-PI, +PI], while keeping the direction intact. */ + public static function normalizeAngle(angle:Number):Number + { + // move to equivalent value in range [0 deg, 360 deg] without a loop + angle = angle % TWO_PI; + + // move to [-180 deg, +180 deg] + if (angle < -Math.PI) angle += TWO_PI; + if (angle > Math.PI) angle -= TWO_PI; + + return angle; + } + + /** Returns the next power of two that is equal to or bigger than the specified number. */ + public static function getNextPowerOfTwo(number:Number):int + { + if (number is int && number > 0 && (number & (number - 1)) == 0) // see: http://goo.gl/D9kPj + return number; + else + { + var result:int = 1; + number -= 0.000000001; // avoid floating point rounding errors + + while (result < number) result <<= 1; + return result; + } + } + + /** Indicates if two float (Number) values are equal, give or take epsilon. */ + public static function isEquivalent(a:Number, b:Number, epsilon:Number=0.0001):Boolean + { + return (a - epsilon < b) && (a + epsilon > b); + } + + /** Returns the larger of the two values. Different to the native Math.max, + * this doesn't create any temporary objects when using the AOT compiler. */ + public static function max(a:Number, b:Number):Number + { + return a > b ? a : b; + } + + /** Returns the smaller of the two values. Different to the native Math.min, + * this doesn't create any temporary objects when using the AOT compiler. */ + public static function min(a:Number, b:Number):Number + { + return a < b ? a : b; + } + + /** Moves value into the range between min and max. */ + public static function clamp(value:Number, min:Number, max:Number):Number + { + return value < min ? min : (value > max ? max : value); + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/utils/MatrixUtil.as b/mobile_version/src/starling/utils/MatrixUtil.as new file mode 100644 index 00000000..f83691f1 --- /dev/null +++ b/mobile_version/src/starling/utils/MatrixUtil.as @@ -0,0 +1,382 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.utils +{ + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Point; + import flash.geom.Vector3D; + + import starling.errors.AbstractClassError; + + /** A utility class containing methods related to the Matrix class. */ + public class MatrixUtil + { + // helper objects + private static var sRawData:Vector. = + new [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + private static var sRawData2:Vector. = new Vector.(16, true); + private static var sPoint3D:Vector3D = new Vector3D(); + private static var sMatrixData:Vector. = + new [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + /** @private */ + public function MatrixUtil() { throw new AbstractClassError(); } + + /** Converts a 2D matrix to a 3D matrix. If you pass an out-matrix, + * the result will be stored in this matrix instead of creating a new object. */ + public static function convertTo3D(matrix:Matrix, out:Matrix3D=null):Matrix3D + { + if (out == null) out = new Matrix3D(); + + sRawData[ 0] = matrix.a; + sRawData[ 1] = matrix.b; + sRawData[ 4] = matrix.c; + sRawData[ 5] = matrix.d; + sRawData[12] = matrix.tx; + sRawData[13] = matrix.ty; + + out.copyRawDataFrom(sRawData); + return out; + } + + /** Converts a 3D matrix to a 2D matrix. Beware that this will work only for a 3D matrix + * describing a pure 2D transformation. */ + public static function convertTo2D(matrix3D:Matrix3D, out:Matrix=null):Matrix + { + if (out == null) out = new Matrix(); + + matrix3D.copyRawDataTo(sRawData2); + out.a = sRawData2[ 0]; + out.b = sRawData2[ 1]; + out.c = sRawData2[ 4]; + out.d = sRawData2[ 5]; + out.tx = sRawData2[12]; + out.ty = sRawData2[13]; + + return out; + } + + /** Determines if the matrix is an identity matrix. */ + public static function isIdentity(matrix:Matrix):Boolean + { + return matrix.a == 1.0 && matrix.b == 0.0 && matrix.c == 0.0 && matrix.d == 1.0 && + matrix.tx == 0.0 && matrix.ty == 0.0; + } + + /** Determines if the 3D matrix is an identity matrix. */ + public static function isIdentity3D(matrix:Matrix3D):Boolean + { + var data:Vector. = sRawData2; + matrix.copyRawDataTo(data); + + return data[ 0] == 1.0 && data[ 1] == 0.0 && data[ 2] == 0.0 && data[ 3] == 0.0 && + data[ 4] == 0.0 && data[ 5] == 1.0 && data[ 6] == 0.0 && data[ 7] == 0.0 && + data[ 8] == 0.0 && data[ 9] == 0.0 && data[10] == 1.0 && data[11] == 0.0 && + data[12] == 0.0 && data[13] == 0.0 && data[14] == 0.0 && data[15] == 1.0; + } + + /** Transform a point with the given matrix. */ + public static function transformPoint(matrix:Matrix, point:Point, + out:Point=null):Point + { + return transformCoords(matrix, point.x, point.y, out); + } + + /** Transforms a 3D point with the given matrix. */ + public static function transformPoint3D(matrix:Matrix3D, point:Vector3D, + out:Vector3D=null):Vector3D + { + return transformCoords3D(matrix, point.x, point.y, point.z, out); + } + + /** Uses a matrix to transform 2D coordinates into a different space. If you pass an + * out-point, the result will be stored in this point instead of creating + * a new object. */ + public static function transformCoords(matrix:Matrix, x:Number, y:Number, + out:Point=null):Point + { + if (out == null) out = new Point(); + + out.x = matrix.a * x + matrix.c * y + matrix.tx; + out.y = matrix.d * y + matrix.b * x + matrix.ty; + + return out; + } + + /** Uses a matrix to transform 3D coordinates into a different space. If you pass a + * 'resultVector', the result will be stored in this vector3D instead of creating a + * new object. */ + public static function transformCoords3D(matrix:Matrix3D, x:Number, y:Number, z:Number, + out:Vector3D=null):Vector3D + { + if (out == null) out = new Vector3D(); + + matrix.copyRawDataTo(sRawData2); + out.x = x * sRawData2[0] + y * sRawData2[4] + z * sRawData2[ 8] + sRawData2[12]; + out.y = x * sRawData2[1] + y * sRawData2[5] + z * sRawData2[ 9] + sRawData2[13]; + out.z = x * sRawData2[2] + y * sRawData2[6] + z * sRawData2[10] + sRawData2[14]; + out.w = x * sRawData2[3] + y * sRawData2[7] + z * sRawData2[11] + sRawData2[15]; + + return out; + } + + /** Appends a skew transformation to a matrix (angles in radians). The skew matrix + * has the following form: + *
+         *  | cos(skewY)  -sin(skewX)  0 |
+         *  | sin(skewY)   cos(skewX)  0 |
+         *  |     0            0       1 |
+         *  
+ */ + public static function skew(matrix:Matrix, skewX:Number, skewY:Number):void + { + var sinX:Number = Math.sin(skewX); + var cosX:Number = Math.cos(skewX); + var sinY:Number = Math.sin(skewY); + var cosY:Number = Math.cos(skewY); + + matrix.setTo(matrix.a * cosY - matrix.b * sinX, + matrix.a * sinY + matrix.b * cosX, + matrix.c * cosY - matrix.d * sinX, + matrix.c * sinY + matrix.d * cosX, + matrix.tx * cosY - matrix.ty * sinX, + matrix.tx * sinY + matrix.ty * cosX); + } + + /** Prepends a matrix to 'base' by multiplying it with another matrix. */ + public static function prependMatrix(base:Matrix, prep:Matrix):void + { + base.setTo(base.a * prep.a + base.c * prep.b, + base.b * prep.a + base.d * prep.b, + base.a * prep.c + base.c * prep.d, + base.b * prep.c + base.d * prep.d, + base.tx + base.a * prep.tx + base.c * prep.ty, + base.ty + base.b * prep.tx + base.d * prep.ty); + } + + /** Prepends an incremental translation to a Matrix object. */ + public static function prependTranslation(matrix:Matrix, tx:Number, ty:Number):void + { + matrix.tx += matrix.a * tx + matrix.c * ty; + matrix.ty += matrix.b * tx + matrix.d * ty; + } + + /** Prepends an incremental scale change to a Matrix object. */ + public static function prependScale(matrix:Matrix, sx:Number, sy:Number):void + { + matrix.setTo(matrix.a * sx, matrix.b * sx, + matrix.c * sy, matrix.d * sy, + matrix.tx, matrix.ty); + } + + /** Prepends an incremental rotation to a Matrix object (angle in radians). */ + public static function prependRotation(matrix:Matrix, angle:Number):void + { + var sin:Number = Math.sin(angle); + var cos:Number = Math.cos(angle); + + matrix.setTo(matrix.a * cos + matrix.c * sin, matrix.b * cos + matrix.d * sin, + matrix.c * cos - matrix.a * sin, matrix.d * cos - matrix.b * sin, + matrix.tx, matrix.ty); + } + + /** Prepends a skew transformation to a Matrix object (angles in radians). The skew matrix + * has the following form: + *
+         *  | cos(skewY)  -sin(skewX)  0 |
+         *  | sin(skewY)   cos(skewX)  0 |
+         *  |     0            0       1 |
+         *  
+ */ + public static function prependSkew(matrix:Matrix, skewX:Number, skewY:Number):void + { + var sinX:Number = Math.sin(skewX); + var cosX:Number = Math.cos(skewX); + var sinY:Number = Math.sin(skewY); + var cosY:Number = Math.cos(skewY); + + matrix.setTo(matrix.a * cosY + matrix.c * sinY, + matrix.b * cosY + matrix.d * sinY, + matrix.c * cosX - matrix.a * sinX, + matrix.d * cosX - matrix.b * sinX, + matrix.tx, matrix.ty); + } + + /** Converts a Matrix3D instance to a String, which is useful when debugging. Per default, + * the raw data is displayed transposed, so that the columns are displayed vertically. */ + public static function toString3D(matrix:Matrix3D, transpose:Boolean=true, + precision:int=3):String + { + if (transpose) matrix.transpose(); + matrix.copyRawDataTo(sRawData2); + if (transpose) matrix.transpose(); + + return "[Matrix3D rawData=\n" + formatRawData(sRawData2, 4, 4, precision) + "\n]"; + } + + /** Converts a Matrix instance to a String, which is useful when debugging. */ + public static function toString(matrix:Matrix, precision:int=3):String + { + sRawData2[0] = matrix.a; sRawData2[1] = matrix.c; sRawData2[2] = matrix.tx; + sRawData2[3] = matrix.b; sRawData2[4] = matrix.d; sRawData2[5] = matrix.ty; + + return "[Matrix rawData=\n" + formatRawData(sRawData2, 3, 2, precision) + "\n]"; + } + + private static function formatRawData(data:Vector., numCols:int, numRows:int, + precision:int, indent:String=" "):String + { + var result:String = indent; + var numValues:int = numCols * numRows; + var highestValue:Number = 0.0; + var valueString:String; + var value:Number; + + for (var i:int=0; i highestValue) highestValue = value; + } + + var numChars:int = highestValue.toFixed(precision).length + 1; + + for (var y:int=0; y 0 && matrix.b - E < 0 && matrix.c + E > 0 && matrix.c - E < 0) + { + // what we actually want is 'Math.abs(matrix.a)', but squaring + // the value works just as well for our needs & is faster. + + aSq = matrix.a * matrix.a; + dSq = matrix.d * matrix.d; + doSnap = aSq + E > 1 && aSq - E < 1 && dSq + E > 1 && dSq - E < 1; + } + else if (matrix.a + E > 0 && matrix.a - E < 0 && matrix.d + E > 0 && matrix.d - E < 0) + { + bSq = matrix.b * matrix.b; + cSq = matrix.c * matrix.c; + doSnap = bSq + E > 1 && bSq - E < 1 && cSq + E > 1 && cSq - E < 1; + } + + if (doSnap) + { + matrix.tx = Math.round(matrix.tx / pixelSize) * pixelSize; + matrix.ty = Math.round(matrix.ty / pixelSize) * pixelSize; + } + } + + /** Creates a perspective projection matrix suitable for 2D and 3D rendering. + * + *

The first 4 parameters define which area of the stage you want to view (the camera + * will 'zoom' to exactly this region). The final 3 parameters determine the perspective + * in which you're looking at the stage.

+ * + *

The stage is always on the rectangle that is spawned up between x- and y-axis (with + * the given size). All objects that are exactly on that rectangle (z equals zero) will be + * rendered in their true size, without any distortion.

+ * + *

If you pass only the first 4 parameters, the camera will be set up above the center + * of the stage, with a field of view of 1.0 rad.

+ */ + public static function createPerspectiveProjectionMatrix( + x:Number, y:Number, width:Number, height:Number, + stageWidth:Number=0, stageHeight:Number=0, cameraPos:Vector3D=null, + out:Matrix3D=null):Matrix3D + { + if (out == null) out = new Matrix3D(); + if (stageWidth <= 0) stageWidth = width; + if (stageHeight <= 0) stageHeight = height; + if (cameraPos == null) + { + cameraPos = sPoint3D; + cameraPos.setTo( + stageWidth / 2, stageHeight / 2, // -> center of stage + stageWidth / Math.tan(0.5) * 0.5); // -> fieldOfView = 1.0 rad + } + + const focalLength:Number = Math.abs(cameraPos.z); + const offsetX:Number = cameraPos.x - stageWidth / 2; + const offsetY:Number = cameraPos.y - stageHeight / 2; + const far:Number = focalLength * 20; + const near:Number = 1; + const scaleX:Number = stageWidth / width; + const scaleY:Number = stageHeight / height; + + // set up general perspective + sMatrixData[ 0] = 2 * focalLength / stageWidth; // 0,0 + sMatrixData[ 5] = -2 * focalLength / stageHeight; // 1,1 [negative to invert y-axis] + sMatrixData[10] = far / (far - near); // 2,2 + sMatrixData[14] = -far * near / (far - near); // 2,3 + sMatrixData[11] = 1; // 3,2 + + // now zoom in to visible area + sMatrixData[0] *= scaleX; + sMatrixData[5] *= scaleY; + sMatrixData[8] = scaleX - 1 - 2 * scaleX * (x - offsetX) / stageWidth; + sMatrixData[9] = -scaleY + 1 + 2 * scaleY * (y - offsetY) / stageHeight; + + out.copyRawDataFrom(sMatrixData); + out.prependTranslation( + -stageWidth /2.0 - offsetX, + -stageHeight/2.0 - offsetY, + focalLength); + + return out; + } + + /** Creates a orthographic projection matrix suitable for 2D rendering. */ + public static function createOrthographicProjectionMatrix( + x:Number, y:Number, width:Number, height:Number, out:Matrix=null):Matrix + { + if (out == null) out = new Matrix(); + + out.setTo(2.0/width, 0, 0, -2.0/height, + -(2*x + width) / width, (2*y + height) / height); + + return out; + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/utils/MeshSubset.as b/mobile_version/src/starling/utils/MeshSubset.as new file mode 100644 index 00000000..014ef5db --- /dev/null +++ b/mobile_version/src/starling/utils/MeshSubset.as @@ -0,0 +1,46 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.utils +{ + /** A class describing a range of vertices and indices, thus referencing a subset of a Mesh. */ + public class MeshSubset + { + /** The ID of the first vertex. */ + public var vertexID:int; + + /** The total number of vertices. */ + public var numVertices:int; + + /** The ID of the first index. */ + public var indexID:int; + + /** The total number of indices. */ + public var numIndices:int; + + /** Creates a new MeshSubset. */ + public function MeshSubset(vertexID:int=0, numVertices:int=-1, + indexID:int=0, numIndices:int=-1) + { + setTo(vertexID, numVertices, indexID, numIndices); + } + + /** Changes all properties at once. + * Call without any arguments to reference a complete mesh. */ + public function setTo(vertexID:int=0, numVertices:int=-1, + indexID:int=0, numIndices:int=-1):void + { + this.vertexID = vertexID; + this.numVertices = numVertices; + this.indexID = indexID; + this.numIndices = numIndices; + } + } +} diff --git a/mobile_version/src/starling/utils/MeshUtil.as b/mobile_version/src/starling/utils/MeshUtil.as new file mode 100644 index 00000000..0ba36f97 --- /dev/null +++ b/mobile_version/src/starling/utils/MeshUtil.as @@ -0,0 +1,93 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.utils +{ + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.geom.Vector3D; + + import starling.display.DisplayObject; + import starling.display.Stage; + import starling.errors.AbstractClassError; + import starling.rendering.IndexData; + import starling.rendering.VertexData; + + /** A utility class that helps with tasks that are common when working with meshes. */ + public class MeshUtil + { + // helper objects + private static var sPoint3D:Vector3D = new Vector3D(); + private static var sMatrix:Matrix = new Matrix(); + private static var sMatrix3D:Matrix3D = new Matrix3D(); + + /** @private */ + public function MeshUtil() { throw new AbstractClassError(); } + + /** Determines if a point is inside a mesh that is spawned up by the given + * vertex- and index-data. */ + public static function containsPoint(vertexData:VertexData, indexData:IndexData, + point:Point):Boolean + { + var i:int; + var result:Boolean = false; + var numIndices:int = indexData.numIndices; + var p0:Point = Pool.getPoint(); + var p1:Point = Pool.getPoint(); + var p2:Point = Pool.getPoint(); + + for (i=0; inull to reset all values to zero. */ + public function copyFrom(padding:Padding):void + { + if (padding == null) setTo(0, 0, 0, 0); + else setTo(padding._left, padding._right, padding._top, padding._bottom); + } + + /** Creates a new instance with the exact same values. */ + public function clone():Padding + { + return new Padding(_left, _right, _top, _bottom); + } + + /** The padding on the left side. */ + public function get left():Number { return _left; } + public function set left(value:Number):void + { + if (_left != value) + { + _left = value; + dispatchEventWith(Event.CHANGE); + } + } + + /** The padding on the right side. */ + public function get right():Number { return _right; } + public function set right(value:Number):void + { + if (_right != value) + { + _right = value; + dispatchEventWith(Event.CHANGE); + } + } + + /** The padding towards the top. */ + public function get top():Number { return _top; } + public function set top(value:Number):void + { + if (_top != value) + { + _top = value; + dispatchEventWith(Event.CHANGE); + } + } + + /** The padding towards the bottom. */ + public function get bottom():Number { return _bottom; } + public function set bottom(value:Number):void + { + if (_bottom != value) + { + _bottom = value; + dispatchEventWith(Event.CHANGE); + } + } + } +} diff --git a/mobile_version/src/starling/utils/Pool.as b/mobile_version/src/starling/utils/Pool.as new file mode 100644 index 00000000..24145716 --- /dev/null +++ b/mobile_version/src/starling/utils/Pool.as @@ -0,0 +1,143 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.utils +{ + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.geom.Vector3D; + + import starling.errors.AbstractClassError; + + /** A simple object pool supporting the most basic utility objects. + * + *

If you want to retrieve an object, but the pool does not contain any more instances, + * it will silently create a new one.

+ * + *

It's important that you use the pool in a balanced way, i.e. don't just "get" or "put" + * alone! Always make the calls in pairs; whenever you get an object, be sure to put it back + * later, and the other way round. Otherwise, the pool will empty or (even worse) grow + * in size uncontrolled.

+ */ + public class Pool + { + private static var sPoints:Vector. = new []; + private static var sPoints3D:Vector. = new []; + private static var sMatrices:Vector. = new []; + private static var sMatrices3D:Vector. = new []; + private static var sRectangles:Vector. = new []; + + /** @private */ + public function Pool() { throw new AbstractClassError(); } + + /** Retrieves a Point instance from the pool. */ + public static function getPoint(x:Number = 0, y:Number = 0):Point + { + if (sPoints.length == 0) return new Point(x, y); + else + { + var point:Point = sPoints.pop(); + point.x = x; point.y = y; + return point; + } + } + + /** Stores a Point instance in the pool. + * Don't keep any references to the object after moving it to the pool! */ + public static function putPoint(point:Point):void + { + if (point) sPoints[sPoints.length] = point; + } + + /** Retrieves a Vector3D instance from the pool. */ + public static function getPoint3D(x:Number = 0, y:Number = 0, z:Number = 0):Vector3D + { + if (sPoints.length == 0) return new Vector3D(x, y, z); + else + { + var point:Vector3D = sPoints3D.pop(); + point.x = x; point.y = y; point.z = z; + return point; + } + } + + /** Stores a Vector3D instance in the pool. + * Don't keep any references to the object after moving it to the pool! */ + public static function putPoint3D(point:Vector3D):void + { + if (point) sPoints3D[sPoints3D.length] = point; + } + + /** Retrieves a Matrix instance from the pool. */ + public static function getMatrix(a:Number = 1, b:Number = 0, c:Number = 0, d:Number = 1, + tx:Number = 0, ty:Number = 0):Matrix + { + if (sMatrices.length == 0) return new Matrix(a, b, c, d, tx, ty); + else + { + var matrix:Matrix = sMatrices.pop(); + matrix.setTo(a, b, c, d, tx, ty); + return matrix; + } + } + + /** Stores a Matrix instance in the pool. + * Don't keep any references to the object after moving it to the pool! */ + public static function putMatrix(matrix:Matrix):void + { + if (matrix) sMatrices[sMatrices.length] = matrix; + } + + /** Retrieves a Matrix3D instance from the pool. + * + * @param identity If enabled, the matrix will be reset to the identity. + * Otherwise, its contents is undefined. + */ + public static function getMatrix3D(identity:Boolean = true):Matrix3D + { + if (sMatrices3D.length == 0) return new Matrix3D(); + else + { + var matrix:Matrix3D = sMatrices3D.pop(); + if (identity) matrix.identity(); + return matrix; + } + } + + /** Stores a Matrix3D instance in the pool. + * Don't keep any references to the object after moving it to the pool! */ + public static function putMatrix3D(matrix:Matrix3D):void + { + if (matrix) sMatrices3D[sMatrices3D.length] = matrix; + } + + /** Retrieves a Rectangle instance from the pool. */ + public static function getRectangle(x:Number = 0, y:Number = 0, + width:Number = 0, height:Number = 0):Rectangle + { + if (sRectangles.length == 0) return new Rectangle(x, y, width, height); + else + { + var rectangle:Rectangle = sRectangles.pop(); + rectangle.setTo(x, y, width, height); + return rectangle; + } + } + + /** Stores a Rectangle instance in the pool. + * Don't keep any references to the object after moving it to the pool! */ + public static function putRectangle(rectangle:Rectangle):void + { + if (rectangle) sRectangles[sRectangles.length] = rectangle; + } + } +} diff --git a/mobile_version/src/starling/utils/RectangleUtil.as b/mobile_version/src/starling/utils/RectangleUtil.as new file mode 100644 index 00000000..b19505ce --- /dev/null +++ b/mobile_version/src/starling/utils/RectangleUtil.as @@ -0,0 +1,246 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.utils +{ + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.geom.Vector3D; + + import starling.errors.AbstractClassError; + + /** A utility class containing methods related to the Rectangle class. */ + public class RectangleUtil + { + // helper objects + private static const sPoint:Point = new Point(); + private static const sPoint3D:Vector3D = new Vector3D(); + private static const sPositions:Vector. = + new [new Point(), new Point(), new Point(), new Point()]; + + /** @private */ + public function RectangleUtil() { throw new AbstractClassError(); } + + /** Calculates the intersection between two Rectangles. If the rectangles do not intersect, + * this method returns an empty Rectangle object with its properties set to 0. */ + public static function intersect(rect1:Rectangle, rect2:Rectangle, + out:Rectangle=null):Rectangle + { + if (out == null) out = new Rectangle(); + + var left:Number = rect1.x > rect2.x ? rect1.x : rect2.x; + var right:Number = rect1.right < rect2.right ? rect1.right : rect2.right; + var top:Number = rect1.y > rect2.y ? rect1.y : rect2.y; + var bottom:Number = rect1.bottom < rect2.bottom ? rect1.bottom : rect2.bottom; + + if (left > right || top > bottom) + out.setEmpty(); + else + out.setTo(left, top, right-left, bottom-top); + + return out; + } + + /** Calculates a rectangle with the same aspect ratio as the given 'rectangle', + * centered within 'into'. + * + *

This method is useful for calculating the optimal viewPort for a certain display + * size. You can use different scale modes to specify how the result should be calculated; + * furthermore, you can avoid pixel alignment errors by only allowing whole-number + * multipliers/divisors (e.g. 3, 2, 1, 1/2, 1/3).

+ * + * @see starling.utils.ScaleMode + */ + public static function fit(rectangle:Rectangle, into:Rectangle, + scaleMode:String="showAll", pixelPerfect:Boolean=false, + out:Rectangle=null):Rectangle + { + if (!ScaleMode.isValid(scaleMode)) throw new ArgumentError("Invalid scaleMode: " + scaleMode); + if (out == null) out = new Rectangle(); + + var width:Number = rectangle.width; + var height:Number = rectangle.height; + var factorX:Number = into.width / width; + var factorY:Number = into.height / height; + var factor:Number = 1.0; + + if (scaleMode == ScaleMode.SHOW_ALL) + { + factor = factorX < factorY ? factorX : factorY; + if (pixelPerfect) factor = nextSuitableScaleFactor(factor, false); + } + else if (scaleMode == ScaleMode.NO_BORDER) + { + factor = factorX > factorY ? factorX : factorY; + if (pixelPerfect) factor = nextSuitableScaleFactor(factor, true); + } + + width *= factor; + height *= factor; + + out.setTo( + into.x + (into.width - width) / 2, + into.y + (into.height - height) / 2, + width, height); + + return out; + } + + /** Calculates the next whole-number multiplier or divisor, moving either up or down. */ + private static function nextSuitableScaleFactor(factor:Number, up:Boolean):Number + { + var divisor:Number = 1.0; + + if (up) + { + if (factor >= 0.5) return Math.ceil(factor); + else + { + while (1.0 / (divisor + 1) > factor) + ++divisor; + } + } + else + { + if (factor >= 1.0) return Math.floor(factor); + else + { + while (1.0 / divisor > factor) + ++divisor; + } + } + + return 1.0 / divisor; + } + + /** If the rectangle contains negative values for width or height, all coordinates + * are adjusted so that the rectangle describes the same region with positive values. */ + public static function normalize(rect:Rectangle):void + { + if (rect.width < 0) + { + rect.width = -rect.width; + rect.x -= rect.width; + } + + if (rect.height < 0) + { + rect.height = -rect.height; + rect.y -= rect.height; + } + } + + /** Extends the bounds of the rectangle in all four directions. */ + public static function extend(rect:Rectangle, left:Number=0, right:Number=0, + top:Number=0, bottom:Number=0):void + { + rect.x -= left; + rect.y -= top; + rect.width += left + right; + rect.height += top + bottom; + } + + /** Calculates the bounds of a rectangle after transforming it by a matrix. + * If you pass an out-rectangle, the result will be stored in this rectangle + * instead of creating a new object. */ + public static function getBounds(rectangle:Rectangle, matrix:Matrix, + out:Rectangle=null):Rectangle + { + if (out == null) out = new Rectangle(); + + var minX:Number = Number.MAX_VALUE, maxX:Number = -Number.MAX_VALUE; + var minY:Number = Number.MAX_VALUE, maxY:Number = -Number.MAX_VALUE; + var positions:Vector. = getPositions(rectangle, sPositions); + + for (var i:int=0; i<4; ++i) + { + MatrixUtil.transformCoords(matrix, positions[i].x, positions[i].y, sPoint); + + if (minX > sPoint.x) minX = sPoint.x; + if (maxX < sPoint.x) maxX = sPoint.x; + if (minY > sPoint.y) minY = sPoint.y; + if (maxY < sPoint.y) maxY = sPoint.y; + } + + out.setTo(minX, minY, maxX - minX, maxY - minY); + return out; + } + + /** Calculates the bounds of a rectangle projected into the XY-plane of a certain 3D space + * as they appear from the given camera position. Note that 'camPos' is expected in the + * target coordinate system (the same that the XY-plane lies in). + * + *

If you pass an 'out' Rectangle, the result will be stored in this rectangle + * instead of creating a new object.

*/ + public static function getBoundsProjected(rectangle:Rectangle, matrix:Matrix3D, + camPos:Vector3D, out:Rectangle=null):Rectangle + { + if (out == null) out = new Rectangle(); + if (camPos == null) throw new ArgumentError("camPos must not be null"); + + var minX:Number = Number.MAX_VALUE, maxX:Number = -Number.MAX_VALUE; + var minY:Number = Number.MAX_VALUE, maxY:Number = -Number.MAX_VALUE; + var positions:Vector. = getPositions(rectangle, sPositions); + + for (var i:int=0; i<4; ++i) + { + var position:Point = positions[i]; + + if (matrix) + MatrixUtil.transformCoords3D(matrix, position.x, position.y, 0, sPoint3D); + else + sPoint3D.setTo(position.x, position.y, 0); + + MathUtil.intersectLineWithXYPlane(camPos, sPoint3D, sPoint); + + if (minX > sPoint.x) minX = sPoint.x; + if (maxX < sPoint.x) maxX = sPoint.x; + if (minY > sPoint.y) minY = sPoint.y; + if (maxY < sPoint.y) maxY = sPoint.y; + } + + out.setTo(minX, minY, maxX - minX, maxY - minY); + return out; + } + + /** Returns a vector containing the positions of the four edges of the given rectangle. */ + public static function getPositions(rectangle:Rectangle, + out:Vector.=null):Vector. + { + if (out == null) out = new Vector.(4, true); + + for (var i:int=0; i<4; ++i) + if (out[i] == null) out[i] = new Point(); + + out[0].x = rectangle.left; out[0].y = rectangle.top; + out[1].x = rectangle.right; out[1].y = rectangle.top; + out[2].x = rectangle.left; out[2].y = rectangle.bottom; + out[3].x = rectangle.right; out[3].y = rectangle.bottom; + return out; + } + + /** Compares all properties of the given rectangle, returning true only if + * they are equal (with the given accuracy 'e'). */ + public static function compare(r1:Rectangle, r2:Rectangle, e:Number=0.0001):Boolean + { + if (r1 == null) return r2 == null; + else if (r2 == null) return false; + else + { + return r1.x > r2.x - e && r1.x < r2.x + e && + r1.y > r2.y - e && r1.y < r2.y + e && + r1.width > r2.width - e && r1.width < r2.width + e && + r1.height > r2.height - e && r1.height < r2.height + e; + } + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/utils/RenderUtil.as b/mobile_version/src/starling/utils/RenderUtil.as new file mode 100644 index 00000000..019564e4 --- /dev/null +++ b/mobile_version/src/starling/utils/RenderUtil.as @@ -0,0 +1,275 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.utils +{ + import flash.display.Stage3D; + import flash.display3D.Context3D; + import flash.display3D.Context3DMipFilter; + import flash.display3D.Context3DRenderMode; + import flash.display3D.Context3DTextureFilter; + import flash.display3D.Context3DTextureFormat; + import flash.display3D.Context3DWrapMode; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.utils.setTimeout; + + import starling.core.Starling; + import starling.errors.AbstractClassError; + import starling.textures.Texture; + import starling.textures.TextureSmoothing; + + /** A utility class containing methods related to Stage3D and rendering in general. */ + public class RenderUtil + { + /** @private */ + public function RenderUtil() + { + throw new AbstractClassError(); + } + + /** Clears the render context with a certain color and alpha value. */ + public static function clear(rgb:uint=0, alpha:Number=0.0):void + { + Starling.context.clear( + Color.getRed(rgb) / 255.0, + Color.getGreen(rgb) / 255.0, + Color.getBlue(rgb) / 255.0, + alpha); + } + + /** Returns the flags that are required for AGAL texture lookup, + * including the '<' and '>' delimiters. */ + public static function getTextureLookupFlags(format:String, mipMapping:Boolean, + repeat:Boolean=false, + smoothing:String="bilinear"):String + { + // TODO this method can probably be removed + + var options:Array = ["2d", repeat ? "repeat" : "clamp"]; + + if (format == Context3DTextureFormat.COMPRESSED) + options.push("dxt1"); + else if (format == "compressedAlpha") + options.push("dxt5"); + + if (smoothing == TextureSmoothing.NONE) + options.push("nearest", mipMapping ? "mipnearest" : "mipnone"); + else if (smoothing == TextureSmoothing.BILINEAR) + options.push("linear", mipMapping ? "mipnearest" : "mipnone"); + else + options.push("linear", mipMapping ? "miplinear" : "mipnone"); + + return "<" + options.join() + ">"; + } + + /** Returns a bit field uniquely describing texture format and premultiplied alpha, + * so that each required AGAL variant will get its unique ID. This method is most + * useful when overriding the programVariantName method of custom + * effects. + * + * @return a bit field using the 3 least significant bits. + */ + public static function getTextureVariantBits(texture:Texture):uint + { + if (texture == null) return 0; + + var bitField:uint = 0; + var formatBits:uint = 0; + + switch (texture.format) + { + case Context3DTextureFormat.COMPRESSED_ALPHA: + formatBits = 3; break; + case Context3DTextureFormat.COMPRESSED: + formatBits = 2; break; + default: + formatBits = 1; + } + + bitField |= formatBits; + + if (!texture.premultipliedAlpha) + bitField |= 1 << 2; + + return bitField; + } + + /** Calls setSamplerStateAt at the current context, + * converting the given parameters to their low level counterparts. */ + public static function setSamplerStateAt(sampler:int, mipMapping:Boolean, + smoothing:String="bilinear", + repeat:Boolean=false):void + { + var wrap:String = repeat ? Context3DWrapMode.REPEAT : Context3DWrapMode.CLAMP; + var filter:String; + var mipFilter:String; + + if (smoothing == TextureSmoothing.NONE) + { + filter = Context3DTextureFilter.NEAREST; + mipFilter = mipMapping ? Context3DMipFilter.MIPNEAREST : Context3DMipFilter.MIPNONE; + } + else if (smoothing == TextureSmoothing.BILINEAR) + { + filter = Context3DTextureFilter.LINEAR; + mipFilter = mipMapping ? Context3DMipFilter.MIPNEAREST : Context3DMipFilter.MIPNONE; + } + else + { + filter = Context3DTextureFilter.LINEAR; + mipFilter = mipMapping ? Context3DMipFilter.MIPLINEAR : Context3DMipFilter.MIPNONE; + } + + Starling.context.setSamplerStateAt(sampler, wrap, filter, mipFilter); + } + + /** Creates an AGAL source string with a tex operation, including an options + * list with the appropriate format flag. + * + *

Note that values for repeat/clamp, filter and + * mip-filter are not included in the options list, since it's preferred + * to set those values at runtime via setSamplerStateAt.

+ * + *

Starling expects every color to have its alpha value premultiplied into + * the RGB channels. Thus, if this method encounters a non-PMA texture, it will + * (per default) convert the color in the result register to PMA mode, resulting + * in an additional mul-operation.

+ * + * @param resultReg the register to write the result into. + * @param uvReg the register containing the texture coordinates. + * @param sampler the texture sampler to use. + * @param texture the texture that's active in the given texture sampler. + * @param convertToPmaIfRequired indicates if a non-PMA color should be converted to PMA. + * @param tempReg if 'resultReg' is the output register and PMA conversion is done, + * a temporary register is needed. + * + * @return the AGAL source code, line break(s) included. + */ + public static function createAGALTexOperation( + resultReg:String, uvReg:String, sampler:int, texture:Texture, + convertToPmaIfRequired:Boolean=true, tempReg:String="ft0"):String + { + var format:String = texture.format; + var formatFlag:String; + + switch (format) + { + case Context3DTextureFormat.COMPRESSED: + formatFlag = "dxt1"; break; + case Context3DTextureFormat.COMPRESSED_ALPHA: + formatFlag = "dxt5"; break; + default: + formatFlag = "rgba"; + } + + var needsConversion:Boolean = convertToPmaIfRequired && !texture.premultipliedAlpha; + var texReg:String = needsConversion && resultReg == "oc" ? tempReg : resultReg; + var operation:String = "tex " + texReg + ", " + uvReg + ", fs" + sampler + + " <2d, " + formatFlag + ">\n"; + + if (needsConversion) + { + if (resultReg == "oc") // the output color register cannot use a write mask ... + { + operation += "mul " + texReg + ".xyz, " + texReg + ".xyz, " + texReg + ".www\n"; + operation += "mov " + resultReg + ", " + texReg; + } + else + { + operation += "mul " + resultReg + ".xyz, " + texReg + ".xyz, " + texReg + ".www\n"; + } + } + + return operation; + } + + /** Requests a context3D object from the given Stage3D object. + * + * @param stage3D The stage3D object the context needs to be requested from. + * @param renderMode The 'Context3DRenderMode' to use when requesting the context. + * @param profile If you know exactly which 'Context3DProfile' you want to use, simply + * pass a String with that profile. + * + *

If you are unsure which profiles are supported on the current + * device, you can also pass an Array of profiles; they will be + * tried one after the other (starting at index 0), until a working + * profile is found. If none of the given profiles is supported, + * the Stage3D object will dispatch an ERROR event.

+ * + *

You can also pass the String 'auto' to use the best available + * profile automatically. This will try all known Stage3D profiles, + * beginning with the most powerful.

+ */ + public static function requestContext3D(stage3D:Stage3D, renderMode:String, profile:*):void + { + var profiles:Array; + var currentProfile:String; + + if (profile == "auto") + profiles = ["standardExtended", "standard", "standardConstrained", + "baselineExtended", "baseline", "baselineConstrained"]; + else if (profile is String) + profiles = [profile as String]; + else if (profile is Array) + profiles = profile as Array; + else + throw new ArgumentError("Profile must be of type 'String' or 'Array'"); + + stage3D.addEventListener(Event.CONTEXT3D_CREATE, onCreated, false, 100); + stage3D.addEventListener(ErrorEvent.ERROR, onError, false, 100); + + requestNextProfile(); + + function requestNextProfile():void + { + currentProfile = profiles.shift(); + + try { execute(stage3D.requestContext3D, renderMode, currentProfile); } + catch (error:Error) + { + if (profiles.length != 0) setTimeout(requestNextProfile, 1); + else throw error; + } + } + + function onCreated(event:Event):void + { + var context:Context3D = stage3D.context3D; + + if (renderMode == Context3DRenderMode.AUTO && profiles.length != 0 && + context.driverInfo.indexOf("Software") != -1) + { + onError(event); + } + else + { + onFinished(); + } + } + + function onError(event:Event):void + { + if (profiles.length != 0) + { + event.stopImmediatePropagation(); + setTimeout(requestNextProfile, 1); + } + else onFinished(); + } + + function onFinished():void + { + stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onCreated); + stage3D.removeEventListener(ErrorEvent.ERROR, onError); + } + } + } +} diff --git a/mobile_version/src/starling/utils/ScaleMode.as b/mobile_version/src/starling/utils/ScaleMode.as new file mode 100644 index 00000000..0e6034a1 --- /dev/null +++ b/mobile_version/src/starling/utils/ScaleMode.as @@ -0,0 +1,30 @@ +package starling.utils +{ + import starling.errors.AbstractClassError; + + /** A class that provides constant values for the 'RectangleUtil.fit' method. */ + public class ScaleMode + { + /** @private */ + public function ScaleMode() { throw new AbstractClassError(); } + + /** Specifies that the rectangle is not scaled, but simply centered within the + * specified area. */ + public static const NONE:String = "none"; + + /** Specifies that the rectangle fills the specified area without distortion + * but possibly with some cropping, while maintaining the original aspect ratio. */ + public static const NO_BORDER:String = "noBorder"; + + /** Specifies that the entire rectangle will be scaled to fit into the specified + * area, while maintaining the original aspect ratio. This might leave empty bars at + * either the top and bottom, or left and right. */ + public static const SHOW_ALL:String = "showAll"; + + /** Indicates whether the given scale mode string is valid. */ + public static function isValid(scaleMode:String):Boolean + { + return scaleMode == NONE || scaleMode == NO_BORDER || scaleMode == SHOW_ALL; + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/utils/StringUtil.as b/mobile_version/src/starling/utils/StringUtil.as new file mode 100644 index 00000000..fbcdd52c --- /dev/null +++ b/mobile_version/src/starling/utils/StringUtil.as @@ -0,0 +1,95 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.utils +{ + import starling.errors.AbstractClassError; + + /** A utility class with methods related to the String class. */ + public class StringUtil + { + /** @private */ + public function StringUtil() { throw new AbstractClassError(); } + + /** Formats a String in .Net-style, with curly braces ("{0}"). Does not support any + * number formatting options yet. */ + public static function format(format:String, ...args):String + { + // TODO: add number formatting options + + for (var i:int=0; ihere. + * + * @param string The String to clean + * @return The input string, but with a master string only one character larger than it. + * @author Jackson Dunstan, JacksonDunstan.com + */ + public static function clean(string:String):String + { + return ("_" + string).substr(1); + } + + /** Removes all leading white-space and control characters from the given String. + * + *

Beware: this method does not make a proper Unicode white-space check, + * but simply trims all character codes of '0x20' or below.

+ */ + public static function trimStart(string:String):String + { + var pos:int; + var length:int = string.length; + + for (pos = 0; pos < length; ++pos) + if (string.charCodeAt(pos) > 0x20) break; + + return string.substring(pos, length); + } + + /** Removes all trailing white-space and control characters from the given String. + * + *

Beware: this method does not make a proper Unicode white-space check, + * but simply trims all character codes of '0x20' or below.

+ */ + public static function trimEnd(string:String):String + { + for (var pos:int = string.length - 1; pos >= 0; --pos) + if (string.charCodeAt(pos) > 0x20) break; + + return string.substring(0, pos + 1); + } + + /** Removes all leading and trailing white-space and control characters from the given + * String. + * + *

Beware: this method does not make a proper Unicode white-space check, + * but simply trims all character codes of '0x20' or below.

+ */ + public static function trim(string:String):String + { + var startPos:int, endPos:int; + var length:int = string.length; + + for (startPos = 0; startPos < length; ++startPos) + if (string.charCodeAt(startPos) > 0x20) break; + + for (endPos = string.length - 1; endPos >= startPos; --endPos) + if (string.charCodeAt(endPos) > 0x20) break; + + return string.substring(startPos, endPos + 1); + } + } +} diff --git a/mobile_version/src/starling/utils/SystemUtil.as b/mobile_version/src/starling/utils/SystemUtil.as new file mode 100644 index 00000000..2d81e648 --- /dev/null +++ b/mobile_version/src/starling/utils/SystemUtil.as @@ -0,0 +1,197 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.utils +{ + import flash.display3D.Context3D; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.system.Capabilities; + import flash.text.Font; + import flash.text.FontStyle; + import flash.utils.getDefinitionByName; + + import starling.errors.AbstractClassError; + + /** A utility class with methods related to the current platform and runtime. */ + public class SystemUtil + { + private static var sInitialized:Boolean = false; + private static var sApplicationActive:Boolean = true; + private static var sWaitingCalls:Array = []; + private static var sPlatform:String; + private static var sDesktop:Boolean; + private static var sVersion:String; + private static var sAIR:Boolean; + private static var sEmbeddedFonts:Array = null; + private static var sSupportsDepthAndStencil:Boolean = true; + + /** @private */ + public function SystemUtil() { throw new AbstractClassError(); } + + /** Initializes the ACTIVATE/DEACTIVATE event handlers on the native + * application. This method is automatically called by the Starling constructor. */ + public static function initialize():void + { + if (sInitialized) return; + + sInitialized = true; + sPlatform = Capabilities.version.substr(0, 3); + sVersion = Capabilities.version.substr(4); + sDesktop = /(WIN|MAC|LNX)/.exec(sPlatform) != null; + + try + { + var nativeAppClass:Object = getDefinitionByName("flash.desktop::NativeApplication"); + var nativeApp:EventDispatcher = nativeAppClass["nativeApplication"] as EventDispatcher; + + nativeApp.addEventListener(Event.ACTIVATE, onActivate, false, 0, true); + nativeApp.addEventListener(Event.DEACTIVATE, onDeactivate, false, 0, true); + + var appDescriptor:XML = nativeApp["applicationDescriptor"]; + var ns:Namespace = appDescriptor.namespace(); + var ds:String = appDescriptor.ns::initialWindow.ns::depthAndStencil.toString().toLowerCase(); + + sSupportsDepthAndStencil = (ds == "true"); + sAIR = true; + } + catch (e:Error) + { + sAIR = false; + } + } + + private static function onActivate(event:Object):void + { + sApplicationActive = true; + + for each (var call:Array in sWaitingCalls) + { + try { call[0].apply(null, call[1]); } + catch (e:Error) + { + trace("[Starling] Error in 'executeWhenApplicationIsActive' call:", e.message); + } + } + + sWaitingCalls = []; + } + + private static function onDeactivate(event:Object):void + { + sApplicationActive = false; + } + + /** Executes the given function with its arguments the next time the application is active. + * (If it is active already, the call will be executed right away.) */ + public static function executeWhenApplicationIsActive(call:Function, ...args):void + { + initialize(); + + if (sApplicationActive) call.apply(null, args); + else sWaitingCalls.push([call, args]); + } + + /** Indicates if the application is currently active. On Desktop, this means that it has + * the focus; on mobile, that it is in the foreground. In the Flash Plugin, always + * returns true. */ + public static function get isApplicationActive():Boolean + { + initialize(); + return sApplicationActive; + } + + /** Indicates if the code is executed in an Adobe AIR runtime (true) + * or Flash plugin/projector (false). */ + public static function get isAIR():Boolean + { + initialize(); + return sAIR; + } + + /** Indicates if the code is executed on a Desktop computer with Windows, OS X or Linux + * operating system. If the method returns 'false', it's probably a mobile device + * or a Smart TV. */ + public static function get isDesktop():Boolean + { + initialize(); + return sDesktop; + } + + /** Returns the three-letter platform string of the current system. These are + * the most common platforms: WIN, MAC, LNX, IOS, AND, QNX. Except for the + * last one, which indicates "Blackberry", all should be self-explanatory. */ + public static function get platform():String + { + initialize(); + return sPlatform; + } + + /** Returns the Flash Player/AIR version string. The format of the version number is: + * majorVersion,minorVersion,buildNumber,internalBuildNumber. */ + public static function get version():String + { + initialize(); + return sVersion; + } + + /** Returns the value of the 'initialWindow.depthAndStencil' node of the application + * descriptor, if this in an AIR app; otherwise always true. */ + public static function get supportsDepthAndStencil():Boolean + { + return sSupportsDepthAndStencil; + } + + /** Indicates if Context3D supports video textures. At the time of this writing, + * video textures are only supported on Windows, OS X and iOS, and only in AIR + * applications (not the Flash Player). */ + public static function get supportsVideoTexture():Boolean + { + return Context3D["supportsVideoTexture"]; + } + + /** Updates the list of embedded fonts. To be called when a font is loaded at runtime. */ + public static function updateEmbeddedFonts():void + { + sEmbeddedFonts = null; // will be updated in 'isEmbeddedFont()' + } + + /** Figures out if an embedded font with the specified style is available. + * The fonts are enumerated only once; if you load a font at runtime, be sure to call + * 'updateEmbeddedFonts' before calling this method. + * + * @param fontName the name of the font + * @param bold indicates if the font has a bold style + * @param italic indicates if the font has an italic style + * @param fontType the type of the font (one of the constants defined in the FontType class) + */ + public static function isEmbeddedFont(fontName:String, bold:Boolean=false, italic:Boolean=false, + fontType:String="embedded"):Boolean + { + if (sEmbeddedFonts == null) + sEmbeddedFonts = Font.enumerateFonts(false); + + for each (var font:Font in sEmbeddedFonts) + { + var style:String = font.fontStyle; + var isBold:Boolean = style == FontStyle.BOLD || style == FontStyle.BOLD_ITALIC; + var isItalic:Boolean = style == FontStyle.ITALIC || style == FontStyle.BOLD_ITALIC; + + if (fontName == font.fontName && bold == isBold && italic == isItalic && + fontType == font.fontType) + { + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/utils/deg2rad.as b/mobile_version/src/starling/utils/deg2rad.as new file mode 100644 index 00000000..d14d22b5 --- /dev/null +++ b/mobile_version/src/starling/utils/deg2rad.as @@ -0,0 +1,18 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.utils +{ + /** Converts an angle from degrees into radians. */ + public function deg2rad(deg:Number):Number + { + return deg / 180.0 * Math.PI; + } +} \ No newline at end of file diff --git a/mobile_version/src/starling/utils/execute.as b/mobile_version/src/starling/utils/execute.as new file mode 100644 index 00000000..c9f8ea6d --- /dev/null +++ b/mobile_version/src/starling/utils/execute.as @@ -0,0 +1,42 @@ +// ================================================================================================= +// +// Starling Framework +// Copyright Gamua GmbH. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.utils +{ + /** Executes a function with the specified arguments. If the argument count does not match + * the function, the argument list is cropped / filled up with null values. */ + public function execute(func:Function, ...args):void + { + if (func != null) + { + var i:int; + var maxNumArgs:int = func.length; + + for (i=args.length; i= 1) { tl = 1; tm = 1; } setcol(int(tr * tl), int(tg * tl), int(tb * tl)); + redraw = true; }else if (tm == 2) { tl -= .1; if (tl <= 0.5) { tl = 0.5; active = false; } setcol(int(tr * tl), int(tg * tl), int(tb * tl)); + redraw = true; } if (timer > 0) { timer--; @@ -103,5 +104,9 @@ public var tl:Number, tm:int; public var iter:int, max:int; + + //For android + public var ybuffer:int; + public var redraw:Boolean; } }