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; } }