Update mobile version to mobile v2.2.1

The android version just got a much needed update to fix some resolution issues on devices with cutouts.

It turns out the mobile source was actually pretty out of date, like 3 versions out of date! This commit brings it up to date.

All the changes have just been about keeping the game running on modern devices, though. The biggest change was adding the Starling library to the project, which made the game GPU powered and sped the whole thing up.
This commit is contained in:
Terry Cavanagh 2022-12-02 18:19:58 +01:00
parent 86d90a1296
commit 72d018ea04
140 changed files with 30533 additions and 2409 deletions

View file

@ -1,73 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<application xmlns="http://ns.adobe.com/air/application/25.0">
<id>com.distractionware.vvvvvvmobile</id>
<versionNumber>1.02</versionNumber>
<supportedProfiles>mobileDevice</supportedProfiles>
<filename>VVVVVV</filename>
<name>VVVVVV</name>
<android>
<manifestAdditions><![CDATA[<manifest android:installLocation="auto">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-feature android:required="true" android:name="android.hardware.touchscreen.multitouch" />
</manifest>]]></manifestAdditions>
</android>
<iPhone>
<InfoAdditions><![CDATA[<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleBlackOpaque</string>
<key>UIRequiresPersistentWiFi</key>
<string>NO</string>
<key>UIPrerenderedIcon</key>
<true />
<key>UIApplicationExitsOnSuspend</key>
<false />
<key>UIDeviceFamily</key>
<array>
<!-- iPhone support -->
<string>1</string>
<!-- iPad support -->
<string>2</string>
</array>]]></InfoAdditions>
<requestedDisplayResolution>standard</requestedDisplayResolution>
</iPhone>
<initialWindow>
<title>VVVVVV</title>
<content>vvvvvv.swf</content>
<visible>true</visible>
<aspectRatio>landscape</aspectRatio>
<renderMode>gpu</renderMode>
<systemChrome>standard</systemChrome>
<autoOrients>true</autoOrients>
<fullScreen>true</fullScreen>
</initialWindow>
<icon>
<image48x48>icons/icon_48.png</image48x48>
<image57x57>icons/icon_57.png</image57x57>
<image72x72>icons/icon_72.png</image72x72>
<image76x76>icons/icon_76.png</image76x76>
<image96x96>icons/icon_96.png</image96x96>
<image114x114>icons/icon_114.png</image114x114>
<image120x120>icons/icon_120.png</image120x120>
<image144x144>icons/icon_144.png</image144x144>
<image152x152>icons/icon_152.png</image152x152>
<image512x512>icons/icon_512.png</image512x512>
<image1024x1024>icons/icon_1024.png</image1024x1024>
</icon>
<!--
AIR options:
http://livedocs.adobe.com/flex/3/html/File_formats_1.html#1043413
AIR mobile options:
http://help.adobe.com/en_US/air/build/WSfffb011ac560372f-5d0f4f25128cc9cd0cb-7ffe.html
Android manifest documentation:
http://developer.android.com/guide/topics/manifest/manifest-intro.html
-->
<extensions>
<extensionID>com.milkmangames.extensions.GameCenter</extensionID>
<!--
<extensionID>com.sticksports.nativeExtensions.SilentSwitch</extensionID>
<extensionID>com.mesmotronic.ane.fullscreen</extensionID>
-->
</extensions>
<?xml version="1.0" encoding="utf-8" ?>
<application xmlns="http://ns.adobe.com/air/application/50.0">
<id>com.distractionware.vvvvvvmobile</id>
<versionNumber>2.2.1</versionNumber>
<supportedProfiles>mobileDevice</supportedProfiles>
<filename>VVVVVV</filename>
<name>VVVVVV</name>
<description></description>
<copyright></copyright>
<android>
<manifestAdditions><![CDATA[
<manifest android:installLocation="auto">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="31" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-configuration android:reqFiveWayNav="true"/>
<supports-screens android:normalScreens="true"/>
<uses-feature android:required="true" android:name="android.hardware.touchscreen.multitouch"/>
<application android:enabled="true">
<activity android:excludeFromRecents="false" android:hardwareAccelerated="true" android:resizeableActivity="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data android:name="android.max_aspect" android:value="2.1" />
</application>
</manifest>
]]></manifestAdditions>
<BuildLegacyAPK>true</BuildLegacyAPK>
</android>
<iPhone>
<InfoAdditions><![CDATA[
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleBlackOpaque</string>
<key>UIRequiresPersistentWiFi</key>
<string>NO</string>
<key>UIPrerenderedIcon</key>
<true/>
<key>UIApplicationExitsOnSuspend</key>
<true/>
<key>UIDeviceFamily</key>
<array>
<!-- iPhone support -->
<string>1</string>
<!-- iPad support -->
<!-- iPad support -->
<!--<string>2</string>-->
</array>
]]></InfoAdditions>
<requestedDisplayResolution>high</requestedDisplayResolution>
</iPhone>
<initialWindow>
<title>VVVVVV</title>
<content>vvvvvv.swf</content>
<visible>true</visible>
<fullScreen>true</fullScreen>
<systemChrome>standard</systemChrome>
<autoOrients>true</autoOrients>
<aspectRatio>landscape</aspectRatio>
<renderMode>direct</renderMode>
<!--<depthAndStencil>true</depthAndStencil>--> <!-- required for 3D -->
</initialWindow>
<icon>
<image48x48>icons/icon_48.png</image48x48>
<image57x57>icons/icon_57.png</image57x57>
<image72x72>icons/icon_72.png</image72x72>
<image76x76>icons/icon_76.png</image76x76>
<image96x96>icons/icon_96.png</image96x96>
<image114x114>icons/icon_114.png</image114x114>
<image120x120>icons/icon_120.png</image120x120>
<image144x144>icons/icon_144.png</image144x144>
<image152x152>icons/icon_152.png</image152x152>
<image512x512>icons/icon_512.png</image512x512>
<image1024x1024>icons/icon_1024.png</image1024x1024>
</icon>
<extensions>
<!--
<extensionID>com.mesmotronic.ane.fullscreen</extensionID>
-->
</extensions>
<!--
AIR options:
http://livedocs.adobe.com/flex/3/html/File_formats_1.html#1043413
AIR mobile options:
http://help.adobe.com/en_US/air/build/WSfffb011ac560372f-5d0f4f25128cc9cd0cb-7ffe.html
iOS icons guidelines:
http://developer.apple.com/library/ios/#documentation/userexperience/conceptual/mobilehig/IconsImages/IconsImages.html
Android manifest documentation:
http://developer.android.com/guide/topics/manifest/manifest-intro.html
-->
</application>

View file

@ -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();

View file

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

View file

@ -1,5 +1,4 @@
package {
import flash.display.*;
import flash.geom.*;
import flash.events.*;
import flash.net.*;

View file

@ -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();
}
}
}

File diff suppressed because it is too large Load diff

View file

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

View file

@ -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();

View file

@ -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<optsLength; k++ )
{
if ( verbose )
trace( " opt: "+opts[k] );
var optfound:Sampler = SAMPLEMAP [opts[k]];
if ( optfound == null )
{
// todo check that it's a number...
//trace( "Warning, unknown sampler option: "+opts[k] );
bias = Number(opts[k]);
if ( verbose )
trace( " bias: " + bias );
}
else
{
if ( optfound.flag != SAMPLER_SPECIAL_SHIFT )
samplerbits &= ~( 0xf << optfound.flag );
samplerbits |= uint( optfound.mask ) << uint( optfound.flag );
}
}
agalcode.writeShort( regidx );
agalcode.writeByte(int(bias*8.0));
agalcode.writeByte(0);
agalcode.writeUnsignedInt( samplerbits );
if ( verbose )
trace( " bits: " + ( samplerbits - 5 ) );
pad -= 64;
}
else
{
if ( j == 0 )
{
agalcode.writeUnsignedInt( 0 );
pad -= 32;
}
agalcode.writeShort( regidx );
agalcode.writeByte( reloffset );
agalcode.writeByte( regmask );
agalcode.writeByte( regFound.emitCode );
agalcode.writeByte( reltype );
agalcode.writeShort( isRelative ? ( relsel | ( 1 << 15 ) ) : 0 );
pad -= 64;
}
}
}
// pad unused regs
for ( j = 0; j < pad; j += 8 )
agalcode.writeByte( 0 );
if ( badreg )
break;
}
if ( _error != "" )
{
_error += "\n at line " + i + " " + lines[i];
agalcode.length = 0;
trace( _error );
}
// trace the bytecode bytes if debugging is enabled
if ( debugEnabled )
{
var dbgLine:String = "generated bytecode:";
var agalLength:uint = agalcode.length;
for ( var index:uint = 0; index < agalLength; index++ )
{
if ( !( index % 16 ) )
dbgLine += "\n";
if ( !( index % 4 ) )
dbgLine += " ";
var byteStr:String = agalcode[ index ].toString( 16 );
if ( byteStr.length < 2 )
byteStr = "0" + byteStr;
dbgLine += byteStr;
}
trace( dbgLine );
}
if ( verbose )
trace( "AGALMiniAssembler.assemble time: " + ( ( getTimer() - start ) / 1000 ) + "s" );
return agalcode;
}
private function initregmap ( version:uint, ignorelimits:Boolean ) : void {
// version changes limits
REGMAP[ VA ] = new Register( VA, "vertex attribute", 0x0, ignorelimits?1024:((version==1||version==2)?7:15), REG_VERT | REG_READ );
REGMAP[ VC ] = new Register( VC, "vertex constant", 0x1, ignorelimits?1024:(version==1?127:249), REG_VERT | REG_READ );
REGMAP[ VT ] = new Register( VT, "vertex temporary", 0x2, ignorelimits?1024:(version==1?7:25), REG_VERT | REG_WRITE | REG_READ );
REGMAP[ VO ] = new Register( VO, "vertex output", 0x3, ignorelimits?1024:0, REG_VERT | REG_WRITE );
REGMAP[ VI ] = new Register( VI, "varying", 0x4, ignorelimits?1024:(version==1?7:9), REG_VERT | REG_FRAG | REG_READ | REG_WRITE );
REGMAP[ FC ] = new Register( FC, "fragment constant", 0x1, ignorelimits?1024:(version==1?27:((version==2)?63:199)), REG_FRAG | REG_READ );
REGMAP[ FT ] = new Register( FT, "fragment temporary", 0x2, ignorelimits?1024:(version==1?7:25), REG_FRAG | REG_WRITE | REG_READ );
REGMAP[ FS ] = new Register( FS, "texture sampler", 0x5, ignorelimits?1024:7, REG_FRAG | REG_READ );
REGMAP[ FO ] = new Register( FO, "fragment output", 0x3, ignorelimits?1024:(version==1?0:3), REG_FRAG | REG_WRITE );
REGMAP[ FD ] = new Register( FD, "fragment depth output",0x6, ignorelimits?1024:(version==1?-1:0), REG_FRAG | REG_WRITE );
// aliases
REGMAP[ "op" ] = REGMAP[ VO ];
REGMAP[ "i" ] = REGMAP[ VI ];
REGMAP[ "v" ] = REGMAP[ VI ];
REGMAP[ "oc" ] = REGMAP[ FO ];
REGMAP[ "od" ] = REGMAP[ FD ];
REGMAP[ "fi" ] = REGMAP[ VI ];
}
static private function init():void
{
initialized = true;
// Fill the dictionaries with opcodes and registers
OPMAP[ MOV ] = new OpCode( MOV, 2, 0x00, 0 );
OPMAP[ ADD ] = new OpCode( ADD, 3, 0x01, 0 );
OPMAP[ SUB ] = new OpCode( SUB, 3, 0x02, 0 );
OPMAP[ MUL ] = new OpCode( MUL, 3, 0x03, 0 );
OPMAP[ DIV ] = new OpCode( DIV, 3, 0x04, 0 );
OPMAP[ RCP ] = new OpCode( RCP, 2, 0x05, 0 );
OPMAP[ MIN ] = new OpCode( MIN, 3, 0x06, 0 );
OPMAP[ MAX ] = new OpCode( MAX, 3, 0x07, 0 );
OPMAP[ FRC ] = new OpCode( FRC, 2, 0x08, 0 );
OPMAP[ SQT ] = new OpCode( SQT, 2, 0x09, 0 );
OPMAP[ RSQ ] = new OpCode( RSQ, 2, 0x0a, 0 );
OPMAP[ POW ] = new OpCode( POW, 3, 0x0b, 0 );
OPMAP[ LOG ] = new OpCode( LOG, 2, 0x0c, 0 );
OPMAP[ EXP ] = new OpCode( EXP, 2, 0x0d, 0 );
OPMAP[ NRM ] = new OpCode( NRM, 2, 0x0e, 0 );
OPMAP[ SIN ] = new OpCode( SIN, 2, 0x0f, 0 );
OPMAP[ COS ] = new OpCode( COS, 2, 0x10, 0 );
OPMAP[ CRS ] = new OpCode( CRS, 3, 0x11, 0 );
OPMAP[ DP3 ] = new OpCode( DP3, 3, 0x12, 0 );
OPMAP[ DP4 ] = new OpCode( DP4, 3, 0x13, 0 );
OPMAP[ ABS ] = new OpCode( ABS, 2, 0x14, 0 );
OPMAP[ NEG ] = new OpCode( NEG, 2, 0x15, 0 );
OPMAP[ SAT ] = new OpCode( SAT, 2, 0x16, 0 );
OPMAP[ M33 ] = new OpCode( M33, 3, 0x17, OP_SPECIAL_MATRIX );
OPMAP[ M44 ] = new OpCode( M44, 3, 0x18, OP_SPECIAL_MATRIX );
OPMAP[ M34 ] = new OpCode( M34, 3, 0x19, OP_SPECIAL_MATRIX );
OPMAP[ DDX ] = new OpCode( DDX, 2, 0x1a, OP_VERSION2 | OP_FRAG_ONLY );
OPMAP[ DDY ] = new OpCode( DDY, 2, 0x1b, OP_VERSION2 | OP_FRAG_ONLY );
OPMAP[ IFE ] = new OpCode( IFE, 2, 0x1c, OP_NO_DEST | OP_VERSION2 | OP_INCNEST | OP_SCALAR );
OPMAP[ INE ] = new OpCode( INE, 2, 0x1d, OP_NO_DEST | OP_VERSION2 | OP_INCNEST | OP_SCALAR );
OPMAP[ IFG ] = new OpCode( IFG, 2, 0x1e, OP_NO_DEST | OP_VERSION2 | OP_INCNEST | OP_SCALAR );
OPMAP[ IFL ] = new OpCode( IFL, 2, 0x1f, OP_NO_DEST | OP_VERSION2 | OP_INCNEST | OP_SCALAR );
OPMAP[ ELS ] = new OpCode( ELS, 0, 0x20, OP_NO_DEST | OP_VERSION2 | OP_INCNEST | OP_DECNEST | OP_SCALAR );
OPMAP[ EIF ] = new OpCode( EIF, 0, 0x21, OP_NO_DEST | OP_VERSION2 | OP_DECNEST | OP_SCALAR );
// space
//OPMAP[ TED ] = new OpCode( TED, 3, 0x26, OP_FRAG_ONLY | OP_SPECIAL_TEX | OP_VERSION2); //ted is not available in AGAL2
OPMAP[ KIL ] = new OpCode( KIL, 1, 0x27, OP_NO_DEST | OP_FRAG_ONLY );
OPMAP[ TEX ] = new OpCode( TEX, 3, 0x28, OP_FRAG_ONLY | OP_SPECIAL_TEX );
OPMAP[ SGE ] = new OpCode( SGE, 3, 0x29, 0 );
OPMAP[ SLT ] = new OpCode( SLT, 3, 0x2a, 0 );
OPMAP[ SGN ] = new OpCode( SGN, 2, 0x2b, 0 );
OPMAP[ SEQ ] = new OpCode( SEQ, 3, 0x2c, 0 );
OPMAP[ SNE ] = new OpCode( SNE, 3, 0x2d, 0 );
SAMPLEMAP[ RGBA ] = new Sampler( RGBA, SAMPLER_TYPE_SHIFT, 0 );
SAMPLEMAP[ DXT1 ] = new Sampler( DXT1, SAMPLER_TYPE_SHIFT, 1 );
SAMPLEMAP[ DXT5 ] = new Sampler( DXT5, SAMPLER_TYPE_SHIFT, 2 );
SAMPLEMAP[ VIDEO ] = new Sampler( VIDEO, SAMPLER_TYPE_SHIFT, 3 );
SAMPLEMAP[ D2 ] = new Sampler( D2, SAMPLER_DIM_SHIFT, 0 );
SAMPLEMAP[ D3 ] = new Sampler( D3, SAMPLER_DIM_SHIFT, 2 );
SAMPLEMAP[ CUBE ] = new Sampler( CUBE, SAMPLER_DIM_SHIFT, 1 );
SAMPLEMAP[ MIPNEAREST ] = new Sampler( MIPNEAREST, SAMPLER_MIPMAP_SHIFT, 1 );
SAMPLEMAP[ MIPLINEAR ] = new Sampler( MIPLINEAR, SAMPLER_MIPMAP_SHIFT, 2 );
SAMPLEMAP[ MIPNONE ] = new Sampler( MIPNONE, SAMPLER_MIPMAP_SHIFT, 0 );
SAMPLEMAP[ NOMIP ] = new Sampler( NOMIP, SAMPLER_MIPMAP_SHIFT, 0 );
SAMPLEMAP[ NEAREST ] = new Sampler( NEAREST, SAMPLER_FILTER_SHIFT, 0 );
SAMPLEMAP[ LINEAR ] = new Sampler( LINEAR, SAMPLER_FILTER_SHIFT, 1 );
SAMPLEMAP[ ANISOTROPIC2X ] = new Sampler( ANISOTROPIC2X, SAMPLER_FILTER_SHIFT, 2 );
SAMPLEMAP[ ANISOTROPIC4X ] = new Sampler( ANISOTROPIC4X, SAMPLER_FILTER_SHIFT, 3 );
SAMPLEMAP[ ANISOTROPIC8X ] = new Sampler( ANISOTROPIC8X, SAMPLER_FILTER_SHIFT, 4 );
SAMPLEMAP[ ANISOTROPIC16X ] = new Sampler( ANISOTROPIC16X, SAMPLER_FILTER_SHIFT,5 );
SAMPLEMAP[ CENTROID ] = new Sampler( CENTROID, SAMPLER_SPECIAL_SHIFT, 1 << 0 );
SAMPLEMAP[ SINGLE ] = new Sampler( SINGLE, SAMPLER_SPECIAL_SHIFT, 1 << 1 );
SAMPLEMAP[ IGNORESAMPLER ] = new Sampler( IGNORESAMPLER, SAMPLER_SPECIAL_SHIFT, 1 << 2 );
SAMPLEMAP[ REPEAT ] = new Sampler( REPEAT, SAMPLER_REPEAT_SHIFT, 1 );
SAMPLEMAP[ WRAP ] = new Sampler( WRAP, SAMPLER_REPEAT_SHIFT, 1 );
SAMPLEMAP[ CLAMP ] = new Sampler( CLAMP, SAMPLER_REPEAT_SHIFT, 0 );
SAMPLEMAP[ CLAMP_U_REPEAT_V ] = new Sampler( CLAMP_U_REPEAT_V, SAMPLER_REPEAT_SHIFT, 2 );
SAMPLEMAP[ REPEAT_U_CLAMP_V ] = new Sampler( REPEAT_U_CLAMP_V, SAMPLER_REPEAT_SHIFT, 3 );
}
// ======================================================================
// Constants
// ----------------------------------------------------------------------
private static const OPMAP:Dictionary = new Dictionary();
private static const REGMAP:Dictionary = new Dictionary();
private static const SAMPLEMAP:Dictionary = new Dictionary();
private static const MAX_NESTING:int = 4;
private static const MAX_OPCODES:int = 2048;
private static const FRAGMENT:String = "fragment";
private static const VERTEX:String = "vertex";
// masks and shifts
private static const SAMPLER_TYPE_SHIFT:uint = 8;
private static const SAMPLER_DIM_SHIFT:uint = 12;
private static const SAMPLER_SPECIAL_SHIFT:uint = 16;
private static const SAMPLER_REPEAT_SHIFT:uint = 20;
private static const SAMPLER_MIPMAP_SHIFT:uint = 24;
private static const SAMPLER_FILTER_SHIFT:uint = 28;
// regmap flags
private static const REG_WRITE:uint = 0x1;
private static const REG_READ:uint = 0x2;
private static const REG_FRAG:uint = 0x20;
private static const REG_VERT:uint = 0x40;
// opmap flags
private static const OP_SCALAR:uint = 0x1;
private static const OP_SPECIAL_TEX:uint = 0x8;
private static const OP_SPECIAL_MATRIX:uint = 0x10;
private static const OP_FRAG_ONLY:uint = 0x20;
private static const OP_VERT_ONLY:uint = 0x40;
private static const OP_NO_DEST:uint = 0x80;
private static const OP_VERSION2:uint = 0x100;
private static const OP_INCNEST:uint = 0x200;
private static const OP_DECNEST:uint = 0x400;
// opcodes
private static const MOV:String = "mov";
private static const ADD:String = "add";
private static const SUB:String = "sub";
private static const MUL:String = "mul";
private static const DIV:String = "div";
private static const RCP:String = "rcp";
private static const MIN:String = "min";
private static const MAX:String = "max";
private static const FRC:String = "frc";
private static const SQT:String = "sqt";
private static const RSQ:String = "rsq";
private static const POW:String = "pow";
private static const LOG:String = "log";
private static const EXP:String = "exp";
private static const NRM:String = "nrm";
private static const SIN:String = "sin";
private static const COS:String = "cos";
private static const CRS:String = "crs";
private static const DP3:String = "dp3";
private static const DP4:String = "dp4";
private static const ABS:String = "abs";
private static const NEG:String = "neg";
private static const SAT:String = "sat";
private static const M33:String = "m33";
private static const M44:String = "m44";
private static const M34:String = "m34";
private static const DDX:String = "ddx";
private static const DDY:String = "ddy";
private static const IFE:String = "ife";
private static const INE:String = "ine";
private static const IFG:String = "ifg";
private static const IFL:String = "ifl";
private static const ELS:String = "els";
private static const EIF:String = "eif";
private static const TED:String = "ted";
private static const KIL:String = "kil";
private static const TEX:String = "tex";
private static const SGE:String = "sge";
private static const SLT:String = "slt";
private static const SGN:String = "sgn";
private static const SEQ:String = "seq";
private static const SNE:String = "sne";
// registers
private static const VA:String = "va";
private static const VC:String = "vc";
private static const VT:String = "vt";
private static const VO:String = "vo";
private static const VI:String = "vi";
private static const FC:String = "fc";
private static const FT:String = "ft";
private static const FS:String = "fs";
private static const FO:String = "fo";
private static const FD:String = "fd";
// samplers
private static const D2:String = "2d";
private static const D3:String = "3d";
private static const CUBE:String = "cube";
private static const MIPNEAREST:String = "mipnearest";
private static const MIPLINEAR:String = "miplinear";
private static const MIPNONE:String = "mipnone";
private static const NOMIP:String = "nomip";
private static const NEAREST:String = "nearest";
private static const LINEAR:String = "linear";
private static const ANISOTROPIC2X:String = "anisotropic2x"; //Introduced by Flash 14
private static const ANISOTROPIC4X:String = "anisotropic4x"; //Introduced by Flash 14
private static const ANISOTROPIC8X:String = "anisotropic8x"; //Introduced by Flash 14
private static const ANISOTROPIC16X:String = "anisotropic16x"; //Introduced by Flash 14
private static const CENTROID:String = "centroid";
private static const SINGLE:String = "single";
private static const IGNORESAMPLER:String = "ignoresampler";
private static const REPEAT:String = "repeat";
private static const WRAP:String = "wrap";
private static const CLAMP:String = "clamp";
private static const REPEAT_U_CLAMP_V:String = "repeat_u_clamp_v"; //Introduced by Flash 13
private static const CLAMP_U_REPEAT_V:String = "clamp_u_repeat_v"; //Introduced by Flash 13
private static const RGBA:String = "rgba";
private static const DXT1:String = "dxt1";
private static const DXT5:String = "dxt5";
private static const VIDEO:String = "video";
}
}
// ================================================================================
// Helper Classes
// --------------------------------------------------------------------------------
{
// ===========================================================================
// Class
// ---------------------------------------------------------------------------
class OpCode
{
// ======================================================================
// Properties
// ----------------------------------------------------------------------
private var _emitCode:uint;
private var _flags:uint;
private var _name:String;
private var _numRegister:uint;
// ======================================================================
// Getters
// ----------------------------------------------------------------------
public function get emitCode():uint { return _emitCode; }
public function get flags():uint { return _flags; }
public function get name():String { return _name; }
public function get numRegister():uint { return _numRegister; }
// ======================================================================
// Constructor
// ----------------------------------------------------------------------
public function OpCode( name:String, numRegister:uint, emitCode:uint, flags:uint)
{
_name = name;
_numRegister = numRegister;
_emitCode = emitCode;
_flags = flags;
}
// ======================================================================
// Methods
// ----------------------------------------------------------------------
public function toString():String
{
return "[OpCode name=\""+_name+"\", numRegister="+_numRegister+", emitCode="+_emitCode+", flags="+_flags+"]";
}
}
// ===========================================================================
// Class
// ---------------------------------------------------------------------------
class Register
{
// ======================================================================
// Properties
// ----------------------------------------------------------------------
private var _emitCode:uint;
private var _name:String;
private var _longName:String;
private var _flags:uint;
private var _range:uint;
// ======================================================================
// Getters
// ----------------------------------------------------------------------
public function get emitCode():uint { return _emitCode; }
public function get longName():String { return _longName; }
public function get name():String { return _name; }
public function get flags():uint { return _flags; }
public function get range():uint { return _range; }
// ======================================================================
// Constructor
// ----------------------------------------------------------------------
public function Register( name:String, longName:String, emitCode:uint, range:uint, flags:uint)
{
_name = name;
_longName = longName;
_emitCode = emitCode;
_range = range;
_flags = flags;
}
// ======================================================================
// Methods
// ----------------------------------------------------------------------
public function toString():String
{
return "[Register name=\""+_name+"\", longName=\""+_longName+"\", emitCode="+_emitCode+", range="+_range+", flags="+ _flags+"]";
}
}
// ===========================================================================
// Class
// ---------------------------------------------------------------------------
class Sampler
{
// ======================================================================
// Properties
// ----------------------------------------------------------------------
private var _flag:uint;
private var _mask:uint;
private var _name:String;
// ======================================================================
// Getters
// ----------------------------------------------------------------------
public function get flag():uint { return _flag; }
public function get mask():uint { return _mask; }
public function get name():String { return _name; }
// ======================================================================
// Constructor
// ----------------------------------------------------------------------
public function Sampler( name:String, flag:uint, mask:uint )
{
_name = name;
_flag = flag;
_mask = mask;
}
// ======================================================================
// Methods
// ----------------------------------------------------------------------
public function toString():String
{
return "[Sampler name=\""+_name+"\", flag=\""+_flag+"\", mask="+mask+"]";
}
}
}

View file

@ -53,5 +53,7 @@ package {
public static var deviceresolution:int;
public static var xres:int, yres:int;
public static var localtesting:Boolean = false;
}
}

File diff suppressed because it is too large Load diff

View file

@ -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();

View file

@ -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 {

View file

@ -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();

View file

@ -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();
}

View file

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

View file

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

View file

@ -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();

View file

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

View file

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

View file

@ -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<obj.nblocks; i++){
@ -1060,7 +1063,7 @@ public function gamerender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, m
//dwgfx.drawminimap(game, map);
if (map.extrarow == 0 || (map.custommode && map.roomname != "")) {
dwgfx.backbuffer.fillRect(dwgfx.footerrect, 0x000000);
dwgfx.drawfillrect(dwgfx.footerrect.x, dwgfx.footerrect.y, dwgfx.footerrect.width, dwgfx.footerrect.height, 0);
if (map.finallevel) {
map.glitchname = map.getglitchname(game.roomx, game.roomy);
dwgfx.print(5, 231, map.glitchname, 196, 196, 255 - help.glow, true);
@ -1086,18 +1089,18 @@ public function gamerender(key:KeyPoll, dwgfx:dwgraphicsclass, game:gameclass, m
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);
}
if(!game.mobilemenu){
if (game.readytotele > 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();
}

View file

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

View file

@ -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)");
}
}
}

View file

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

View file

@ -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) {

View file

@ -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 {

View file

@ -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 {
}

View file

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

View file

@ -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");

View file

@ -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.
*
* <p>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.</p>
*
* @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.<DelayedCall> = new <DelayedCall>[];
/** @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);
}
}
}

View file

@ -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.
*
* <p>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 <code>juggler.remove(object)</code>,
* or the object can request to be removed by dispatching a Starling event with the type
* <code>Event.REMOVE_FROM_JUGGLER</code>. 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.</p>
*
* @see Juggler
* @see Tween
*/
public interface IAnimatable
{
/** Advance the time by a number of seconds. @param time in seconds. */
function advanceTime(time:Number):void;
}
}

View file

@ -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.
*
* <p>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.</p>
*
* <p>There is a default juggler available at the Starling class:</p>
*
* <pre>
* var juggler:Juggler = Starling.juggler;
* </pre>
*
* <p>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.</p>
*
* <p>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.</p>
*
* <pre>
* juggler.delayCall(object.removeFromParent, 1.0);
* juggler.delayCall(object.addChild, 2.0, theChild);
* juggler.delayCall(function():void { rotation += 0.1; }, 3.0);
* </pre>
*
* @see Tween
* @see DelayedCall
*/
public class Juggler implements IAnimatable
{
private var _objects:Vector.<IAnimatable>;
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 <IAnimatable>[];
_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 <code>removeByID()</code>.
*/
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.
*
* <p>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 <code>remove</code> method,
* you might accidentally remove an object that has changed its context. By using
* <code>removeByID</code> instead, you can be sure to avoid that, since the objectID
* will always be unique.</p>
*
* @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 <code>delay</code> 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 <code>removeByID()</code>.
*/
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 <code>removeByID()</code>.
*/
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 <code>time</code> 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.
*
* <p>Fill 'properties' with key-value pairs that describe both the
* tween and the animation target. Here is an example:</p>
*
* <pre>
* juggler.tween(object, 2.0, {
* transition: Transitions.EASE_IN_OUT,
* delay: 20, // -> tween.delay = 20
* x: 50 // -> tween.animate("x", 50)
* });
* </pre>
*
* <p>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.</p>
*
* <p>Note that some property types may be animated in a special way:</p>
* <ul>
* <li>If the property contains the string <code>color</code> or <code>Color</code>,
* it will be treated as an unsigned integer with a color value
* (e.g. <code>0xff0000</code> for red). Each color channel will be animated
* individually.</li>
* <li>The same happens if you append the string <code>#rgb</code> to the name.</li>
* <li>If you append <code>#rad</code>, the property is treated as an angle in radians,
* making sure it always uses the shortest possible arc for the rotation.</li>
* <li>The string <code>#deg</code> does the same for angles in degrees.</li>
* </ul>
*/
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<numObjects; ++i)
{
var object:IAnimatable = _objects[i];
if (object)
{
// shift objects into empty slots along the way
if (currentIndex != i)
{
_objects[currentIndex] = object;
_objects[i] = null;
}
object.advanceTime(time);
++currentIndex;
}
}
if (currentIndex != i)
{
numObjects = _objects.length; // count might have changed!
while (i < numObjects)
_objects[int(currentIndex++)] = _objects[int(i++)];
_objects.length = currentIndex;
}
}
private function onRemove(event:Event):void
{
var objectID:uint = remove(event.target as IAnimatable);
if (objectID)
{
var tween:Tween = event.target as Tween;
if (tween && tween.isComplete)
addWithID(tween.nextTween, objectID);
}
}
private static function getNextID():uint { return ++sCurrentObjectID; }
/** The total life time of the juggler (in seconds). */
public function get elapsedTime():Number { return _elapsedTime; }
/** The scale at which the time is passing. This can be used for slow motion or time laps
* effects. Values below '1' will make all animations run slower, values above '1' faster.
* @default 1.0 */
public function get timeScale():Number { return _timeScale; }
public function set timeScale(value:Number):void { _timeScale = value; }
/** The actual vector that contains all objects that are currently being animated. */
protected function get objects():Vector.<IAnimatable> { return _objects; }
}
}

View file

@ -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.
*
* <p>Here is a visual representation of the available transitions:</p>
* <img src="http://gamua.com/img/blog/2010/sparrow-transitions.png"/>
*
* <p>You can define your own transitions through the "registerTransition" function. A
* transition function must have the following signature, where <code>ratio</code> is
* in the range 0-1:</p>
*
* <pre>function myTransition(ratio:Number):Number</pre>
*/
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;
}
}
}

View file

@ -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.
*
* <p>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 (<code>int, uint, Number</code>), the tween can handle it. For a list
* of available Transition types, look at the "Transitions" class.</p>
*
* <p>Here is an example of a tween that moves an object to the right, rotates it, and
* fades it out:</p>
*
* <listing>
* 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);</listing>
*
* <p>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.</p>
*
* @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.<String>;
private var _startValues:Vector.<Number>;
private var _endValues:Vector.<Number>;
private var _updateFuncs:Vector.<Function>;
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 <String>[];
if (_startValues) _startValues.length = 0; else _startValues = new <Number>[];
if (_endValues) _endValues.length = 0; else _endValues = new <Number>[];
if (_updateFuncs) _updateFuncs.length = 0; else _updateFuncs = new <Function>[];
return this;
}
/** Animates the property of the target to a certain value. You can call this method
* multiple times on one tween.
*
* <p>Some property types are handled in a special way:</p>
* <ul>
* <li>If the property contains the string <code>color</code> or <code>Color</code>,
* it will be treated as an unsigned integer with a color value
* (e.g. <code>0xff0000</code> for red). Each color channel will be animated
* individually.</li>
* <li>The same happens if you append the string <code>#rgb</code> to the name.</li>
* <li>If you append <code>#rad</code>, the property is treated as an angle in radians,
* making sure it always uses the shortest possible arc for the rotation.</li>
* <li>The string <code>#deg</code> does the same for angles in degrees.</li>
* </ul>
*/
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<numProperties; ++i)
{
if (_startValues[i] != _startValues[i]) // isNaN check - "isNaN" causes allocation!
_startValues[i] = _target[_properties[i]] as Number;
var updateFunc:Function = _updateFuncs[i] as Function;
updateFunc(_properties[i], _startValues[i], _endValues[i]);
}
if (_onUpdate != null)
_onUpdate.apply(this, _onUpdateArgs);
if (previousTime < _totalTime && _currentTime >= _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.<Tween> = new <Tween>[];
/** @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);
}
}
}

View file

@ -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.
*
* <p>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.</p>
*
* <p>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.</p>
*/
public class AssetFactory
{
private var _priority:int;
private var _mimeTypes:Vector.<String>;
private var _extensions:Vector.<String>;
/** Creates a new instance. */
public function AssetFactory()
{
if (Capabilities.isDebugger &&
getQualifiedClassName(this) == "starling.assets::AssetFactory")
{
throw new AbstractClassError();
}
_mimeTypes = new <String>[];
_extensions = new <String>[];
}
/** 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.
* <pre>function(name:String, asset:Object, type:String):void;</pre>
* @param onError To be called when creation fails for some reason. Do not call
* 'onComplete' when that happens. <pre>function(error:String):void</pre>
*/
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.<String>=null):Vector.<String>
{
out ||= new Vector.<String>();
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.<String>=null):Vector.<String>
{
out ||= new Vector.<String>();
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; }
}
}

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -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();
}
}
}
}

View file

@ -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.
* <pre>function(bitmapData:BitmapData):void;</pre>
* @param onError To be called when creation fails for some reason.
* <pre>function(error:String):void</pre>
*/
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);
}
}
}
}

View file

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

View file

@ -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.
*
* <p>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.</p>
*
* <p>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 <code>dataLoader</code>
* property.</p>
*/
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.
*
* <p>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.</p>
*
* @param url a String containing an URL.
* @param onComplete will be called when the data has been loaded.
* <code>function(data:ByteArray, mimeType:String, name:String, extension:String):void</code>
* @param onError will be called in case of an error. The 2nd parameter is optional.
* <code>function(error:String, httpStatus:int):void</code>
* @param onProgress will be called multiple times with the current load ratio (0-1).
* <code>function(ratio:Number):void</code>
*/
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;
}
}
}

View file

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

View file

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

View file

@ -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 "&lt;" 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.");
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,160 @@
// =================================================================================================
//
// 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.system.System;
import starling.display.Quad;
import starling.display.Sprite;
import starling.events.EnterFrameEvent;
import starling.events.Event;
import starling.rendering.Painter;
import starling.styles.MeshStyle;
import starling.text.BitmapFont;
import starling.text.TextField;
import starling.utils.Align;
/** A small, lightweight box that displays the current framerate, memory consumption and
* the number of draw calls per frame. The display is updated automatically once per frame. */
internal class StatsDisplay extends Sprite
{
private static const UPDATE_INTERVAL:Number = 0.5;
private static const B_TO_MB:Number = 1.0 / (1024 * 1024); // convert from bytes to MB
private var _background:Quad;
private var _labels:TextField;
private var _values:TextField;
private var _frameCount:int = 0;
private var _totalTime:Number = 0;
private var _fps:Number = 0;
private var _memory:Number = 0;
private var _gpuMemory:Number = 0;
private var _drawCount:int = 0;
private var _skipCount:int = 0;
/** Creates a new Statistics Box. */
public function StatsDisplay()
{
const fontName:String = BitmapFont.MINI;
const fontSize:Number = BitmapFont.NATIVE_SIZE;
const fontColor:uint = 0xffffff;
const width:Number = 90;
const height:Number = supportsGpuMem ? 35 : 27;
const gpuLabel:String = supportsGpuMem ? "\ngpu memory:" : "";
const labels:String = "frames/sec:\nstd memory:" + gpuLabel + "\ndraw calls:";
_labels = new TextField(width, height, labels);
_labels.format.setTo(fontName, fontSize, fontColor, Align.LEFT);
_labels.batchable = true;
_labels.x = 2;
_values = new TextField(width - 1, height, "");
_values.format.setTo(fontName, fontSize, fontColor, Align.RIGHT);
_values.batchable = true;
_background = new Quad(width, height, 0x0);
// make sure that rendering takes 2 draw calls
if (_background.style.type != MeshStyle) _background.style = new MeshStyle();
if (_labels.style.type != MeshStyle) _labels.style = new MeshStyle();
if (_values.style.type != MeshStyle) _values.style = new MeshStyle();
addChild(_background);
addChild(_labels);
addChild(_values);
addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);
}
private function onAddedToStage():void
{
addEventListener(Event.ENTER_FRAME, onEnterFrame);
_totalTime = _frameCount = _skipCount = 0;
update();
}
private function onRemovedFromStage():void
{
removeEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:EnterFrameEvent):void
{
_totalTime += event.passedTime;
_frameCount++;
if (_totalTime > 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; }
}
}

View file

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

View file

@ -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.
*
* <p>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:</p>
*
* <pre>result = source × sourceFactor + destination × destinationFactor</pre>
*
* <p>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.</p>
*
* <p>You can add your own blend modes via <code>BlendMode.register</code>.
* 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.</p>
*
* @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 <code>BlendMode.register</code>. */
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; }
}
}

View file

@ -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.
*
* <p>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.</p>
*
* <p>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 <code>textBounds</code> property.</p>
*
* <p>To react on touches on a button, there is special <code>Event.TRIGGERED</code> 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.</p>
*/
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; }
}
}

View file

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

View file

@ -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.<Polygon>;
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 <Polygon>[];
_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 <code>drawCircle()</code>) 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;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -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.
*
* <p>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.</p>
*
* <p>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".</p>
*
* <strong>Adding and removing children</strong>
*
* <p>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.</p>
*
* Adding and removing objects from a container triggers non-bubbling events.
*
* <ul>
* <li><code>Event.ADDED</code>: the object was added to a parent.</li>
* <li><code>Event.ADDED_TO_STAGE</code>: the object was added to a parent that is
* connected to the stage, thus becoming visible now.</li>
* <li><code>Event.REMOVED</code>: the object was removed from a parent.</li>
* <li><code>Event.REMOVED_FROM_STAGE</code>: the object was removed from a parent that
* is connected to the stage, thus becoming invisible now.</li>
* </ul>
*
* Especially the <code>ADDED_TO_STAGE</code> 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.<DisplayObject>;
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.<DisplayObject> = new <DisplayObject>[];
private static var sSortBuffer:Vector.<DisplayObject> = new <DisplayObject>[];
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 <DisplayObject>[];
}
/** 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
* <code>null</code>. 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<numChildren; ++i)
if (_children[i].name == name) return _children[i];
return null;
}
/** Returns the index of a child within the container, or "-1" if it is not found. */
public function getChildIndex(child:DisplayObject):int
{
return _children.indexOf(child);
}
/** Moves a child to a certain index. Children at and after the replaced position move up.*/
public function setChildIndex(child:DisplayObject, index:int):void
{
var oldIndex:int = getChildIndex(child);
if (oldIndex == index) return;
if (oldIndex == -1) throw new ArgumentError("Not a child of this container");
_children.removeAt(oldIndex);
_children.insertAt(index, child);
setRequiresRedraw();
}
/** Swaps the indexes of two children. */
public function swapChildren(child1:DisplayObject, child2:DisplayObject):void
{
var index1:int = getChildIndex(child1);
var index2:int = getChildIndex(child2);
if (index1 == -1 || index2 == -1) throw new ArgumentError("Not a child of this container");
swapChildrenAt(index1, index2);
}
/** Swaps the indexes of two children. */
public function swapChildrenAt(index1:int, index2:int):void
{
var child1:DisplayObject = getChildAt(index1);
var child2:DisplayObject = getChildAt(index2);
_children[index1] = child2;
_children[index2] = child1;
setRequiresRedraw();
}
/** Sorts the children according to a given function (that works just like the sort function
* of the Vector class). */
public function sortChildren(compareFunction:Function):void
{
sSortBuffer.length = _children.length;
mergeSort(_children, compareFunction, 0, _children.length, sSortBuffer);
sSortBuffer.length = 0;
setRequiresRedraw();
}
/** Determines if a certain object is a child of the container (recursively). */
public function contains(child:DisplayObject):Boolean
{
while (child)
{
if (child == this) return true;
else child = child.parent;
}
return false;
}
// other methods
/** @inheritDoc */
public override function getBounds(targetSpace:DisplayObject, out:Rectangle=null):Rectangle
{
if (out == null) out = new Rectangle();
var numChildren:int = _children.length;
if (numChildren == 0)
{
getTransformationMatrix(targetSpace, sHelperMatrix);
MatrixUtil.transformCoords(sHelperMatrix, 0.0, 0.0, sHelperPoint);
out.setTo(sHelperPoint.x, sHelperPoint.y, 0, 0);
}
else if (numChildren == 1)
{
_children[0].getBounds(targetSpace, out);
}
else
{
var minX:Number = Number.MAX_VALUE, maxX:Number = -Number.MAX_VALUE;
var minY:Number = Number.MAX_VALUE, maxY:Number = -Number.MAX_VALUE;
for (var i:int=0; i<numChildren; ++i)
{
_children[i].getBounds(targetSpace, out);
if (minX > 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<numChildren; ++i)
{
var child:DisplayObject = _children[i];
if (child._hasVisibleArea)
{
if (selfOrParentChanged)
child._lastParentOrSelfChangeFrameID = frameID;
if (child._lastParentOrSelfChangeFrameID != frameID &&
child._lastChildChangeFrameID != frameID &&
child._tokenFrameID == frameID - 1 && cacheEnabled)
{
painter.pushState(sCacheToken);
painter.drawFromCache(child._pushToken, child._popToken);
painter.popState(child._popToken);
child._pushToken.copyFrom(sCacheToken);
}
else
{
var pushToken:BatchToken = cacheEnabled ? child._pushToken : null;
var popToken:BatchToken = cacheEnabled ? child._popToken : null;
var filter:FragmentFilter = child._filter;
var mask:DisplayObject = child._mask;
painter.pushState(pushToken);
painter.setStateTo(child.transformationMatrix, child.alpha, child.blendMode);
if (mask) painter.drawMask(mask, child);
if (filter) filter.render(painter);
else child.render(painter);
if (mask) painter.eraseMask(mask, child);
painter.popState(popToken);
}
if (cacheEnabled)
child._tokenFrameID = frameID;
}
}
}
/** Dispatches an event on all children (recursively). The event must not bubble. */
public function broadcastEvent(event:Event):void
{
if (event.bubbles)
throw new ArgumentError("Broadcast of bubbling events is prohibited");
// The event listeners might modify the display tree, which could make the loop crash.
// Thus, we collect them in a list and iterate over that list instead.
// And since another listener could call this method internally, we have to take
// care that the static helper vector does not get corrupted.
var fromIndex:int = sBroadcastListeners.length;
getChildEventListeners(this, event.type, sBroadcastListeners);
var toIndex:int = sBroadcastListeners.length;
for (var i:int=fromIndex; i<toIndex; ++i)
sBroadcastListeners[i].dispatchEvent(event);
sBroadcastListeners.length = fromIndex;
}
/** Dispatches an event with the given parameters on all children (recursively).
* The method uses an internal pool of event objects to avoid allocations. */
public function broadcastEventWith(eventType:String, data:Object=null):void
{
var event:Event = Event.fromPool(eventType, false, data);
broadcastEvent(event);
Event.toPool(event);
}
/** The number of children of this container. */
public function get numChildren():int { return _children.length; }
/** If a container is a 'touchGroup', it will act as a single touchable object.
* Touch events will have the container as target, not the touched child.
* (Similar to 'mouseChildren' in the classic display list, but with inverted logic.)
* @default false */
public function get touchGroup():Boolean { return _touchGroup; }
public function set touchGroup(value:Boolean):void { _touchGroup = value; }
// helpers
private static function mergeSort(input:Vector.<DisplayObject>, compareFunc:Function,
startIndex:int, length:int,
buffer:Vector.<DisplayObject>):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.<DisplayObject>):void
{
var container:DisplayObjectContainer = object as DisplayObjectContainer;
if (object.hasEventListener(eventType))
listeners[listeners.length] = object; // avoiding 'push'
if (container)
{
var children:Vector.<DisplayObject> = container._children;
var numChildren:int = children.length;
for (var i:int=0; i<numChildren; ++i)
getChildEventListeners(children[i], eventType, listeners);
}
}
}
}

View file

@ -0,0 +1,493 @@
// =================================================================================================
//
// 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 starling.rendering.IndexData;
import starling.rendering.VertexData;
import starling.textures.Texture;
import starling.utils.MathUtil;
import starling.utils.Padding;
import starling.utils.Pool;
import starling.utils.RectangleUtil;
/** An Image is a quad with a texture mapped onto it.
*
* <p>Typically, 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.</p>
*
* <p>While the base class <code>Quad</code> already supports textures, the <code>Image</code>
* class adds some additional functionality.</p>
*
* <p>First of all, it provides a convenient constructor that will automatically synchronize
* the size of the image with the displayed texture.</p>
*
* <p>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.</p>
*
* <p>Finally, you can repeat a texture horizontally and vertically within the image's region,
* just like the tiles of a wallpaper. Use the <code>tileGrid</code> property to do that.</p>
*
* @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.<Number> = new Vector.<Number>(3, true);
private static var sBasRows:Vector.<Number> = new Vector.<Number>(3, true);
private static var sPosCols:Vector.<Number> = new Vector.<Number>(3, true);
private static var sPosRows:Vector.<Number> = new Vector.<Number>(3, true);
private static var sTexCols:Vector.<Number> = new Vector.<Number>(3, true);
private static var sTexRows:Vector.<Number> = new Vector.<Number>(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.
*
* <p>Notes:</p>
*
* <ul>
* <li>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).
* </li>
* <li>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.</li>
* <li>An image can have either a <code>scale9Grid</code> or a <code>tileGrid</code>, but
* not both. Assigning one will delete the other.</li>
* <li>Changes will only be applied on assignment. To force an update, simply call
* <code>image.scale9Grid = image.scale9Grid</code>.</li>
* <li>Assignment causes an implicit call to <code>readjustSize()</code>,
* and the same will happen when the texture is changed afterwards.</li>
* </ul>
*
* @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:
*
* <listing>
* var image:Image = new Image(texture);
* image.tileGrid = new Rectangle();
* image.scale = 2;</listing>
*
* <p>Notes:</p>
*
* <ul>
* <li>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).</li>
* <li>An image can have either a <code>scale9Grid</code> or a <code>tileGrid</code>, but
* not both. Assigning one will delete the other.</li>
* <li>Changes will only be applied on assignment. To force an update, simply call
* <code>image.tileGrid = image.tileGrid</code>.</li>
* </ul>
*
* @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<numQuads; ++i)
indexData.addQuad(i*4, i*4 + 1, i*4 + 2, i*4 + 3);
// if we just switched from a normal to a scale9 image,
// we need to colorize all vertices just like the first one.
if (numVertices != prevNumVertices)
{
var color:uint = prevNumVertices ? vertexData.getColor(0) : 0xffffff;
var alpha:Number = prevNumVertices ? vertexData.getAlpha(0) : 1.0;
vertexData.colorize("color", color, alpha);
}
Pool.putRectangle(textureBounds);
Pool.putRectangle(pixelBounds);
Pool.putRectangle(gridCenter);
Pool.putRectangle(intersection);
setRequiresRedraw();
}
private function setupScale9GridAttributes(startX:Number, startY:Number,
posCols:Vector.<Number>, posRows:Vector.<Number>,
texCols:Vector.<Number>, texRows:Vector.<Number>):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();
}
}
}

View file

@ -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.
*
* <p>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.</p>
*
* <p>How a mesh is rendered depends on its style. Per default, this is an instance
* of the <code>MeshStyle</code> base class; however, subclasses may extend its behavior
* to add support for color transformations, normal mapping, etc.</p>
*
* @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 <code>MeshStyle</code> 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
* <code>MeshStyle</code>) provide a means to completely modify the way a mesh is rendered.
* For example, they may add support for color transformations or normal mapping.
*
* <p>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
* <code>style.clone()</code> to assign an identical style to multiple meshes.</p>
*
* @param meshStyle the style to assign. If <code>null</code>, 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 <code>setRequiresRedraw</code>. */
public function setVertexDataChanged():void
{
setRequiresRedraw();
}
/** This method is called whenever the mesh's index data was changed.
* The base implementation simply forwards to <code>setRequiresRedraw</code>. */
public function setIndexDataChanged():void
{
setRequiresRedraw();
}
// vertex manipulation
/** The position of the vertex at the specified index, in the mesh's local coordinate
* system.
*
* <p>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
* <code>Quad</code> 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.</p>
*/
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 <code>setRequiresRedraw</code>. */
protected function get vertexData():VertexData { return _vertexData; }
/** The index data describing how the vertices are interconnected.
* Any change requires a call to <code>setRequiresRedraw</code>. */
protected function get indexData():IndexData { return _indexData; }
/** The style that is used to render the mesh. Styles (which are always subclasses of
* <code>MeshStyle</code>) provide a means to completely modify the way a mesh is rendered.
* For example, they may add support for color transformations or normal mapping.
*
* <p>The setter will simply forward the assignee to <code>setStyle(value)</code>.</p>
*
* @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 <code>null</code>, 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 <code>Image.tileGrid</code>. @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
* <code>starling.rendering.MeshStyle</code>, 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 <code>defaultStyle</code>
* property, this method allows plugging in custom logic and passing arguments to the
* constructor. Return <code>null</code> to fall back to the default behavior (i.e.
* to instantiate <code>defaultStyle</code>). The <code>mesh</code>-parameter is optional
* and may be omitted.
*
* <listing>
* Mesh.defaultStyleFactory = function(mesh:Mesh):MeshStyle
* {
* return new ColorizeMeshStyle(Math.random() * 0xffffff);
* }</listing>
*/
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);
}
}
}

View file

@ -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.
*
* <p>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.</p>
*
* <p>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 <code>canAddMesh</code> to find out if a new mesh shares that state.
* To reset the current state, you can call <code>clear</code>; this will also remove all
* geometry that has been added thus far.</p>
*
* <p>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.</p>
*
* @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, <code>mesh.transformationMatrix</code>
* 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 <code>null</code> 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
* <code>matrix</code> 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.
*
* <p>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).</p>
*/
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 <code>true</code> 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 <code>-1</code>, <code>mesh.numVertices</code> 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 <code>batchable</code> 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.
*
* <p>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).</p>
*
* @default false
*/
public function get batchable():Boolean { return _batchable; }
public function set batchable(value:Boolean):void
{
if (_batchable != value)
{
_batchable = value;
setRequiresRedraw();
}
}
}
}

View file

@ -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.
*
* <p>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 <code>getTextures</code>-method of the
* atlas to receive the textures in the correct (alphabetic) order.</p>
*
* <p>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").</p>
*
* <p>The methods <code>play</code> and <code>pause</code> control playback of the movie. You
* will receive an event of type <code>Event.COMPLETE</code> when the movie finished
* playback. If the movie is looping, the event is dispatched once per loop.</p>
*
* <p>As any animated object, a movie clip has to be added to a juggler (or have its
* <code>advanceTime</code> method called regularly) to run. The movie will dispatch
* an event of type "Event.COMPLETE" whenever it has displayed its last frame.</p>
*
* @see starling.textures.TextureAtlas
*/
public class MovieClip extends Image implements IAnimatable
{
private var _frames:Vector.<MovieClipFrame>;
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.<Texture>, 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.<Texture>, 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 <MovieClipFrame>[];
for (var i:int=0; i<numFrames; ++i)
_frames[i] = new MovieClipFrame(
textures[i], _defaultFrameDuration, _defaultFrameDuration * i);
}
// frame manipulation
/** Adds an additional frame, optionally with a sound and a custom duration. If the
* duration is omitted, the default framerate is used (as specified in the constructor). */
public function addFrame(texture:Texture, sound:Sound=null, duration:Number=-1):void
{
addFrameAt(numFrames, texture, sound, duration);
}
/** Adds a frame at a certain index, optionally with a sound and a custom duration. */
public function addFrameAt(frameID:int, texture:Texture, sound:Sound=null,
duration:Number=-1):void
{
if (frameID < 0 || frameID > 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<numFrames; ++i)
{
_frames[i].startTime = prevFrame.startTime + prevFrame.duration;
prevFrame = _frames[i];
}
}
// IAnimatable
/** @inheritDoc */
public function advanceTime(passedTime:Number):void
{
if (!_playing) return;
// The tricky part in this method is that whenever a callback is executed
// (a frame action or a 'COMPLETE' event handler), that callback might modify the clip.
// Thus, we have to start over with the remaining time whenever that happens.
var frame:MovieClipFrame = _frames[_currentFrameID];
if (_wasStopped)
{
// if the clip was stopped and started again,
// sound and action of this frame need to be repeated.
_wasStopped = false;
frame.playSound(_soundTransform);
if (frame.action != null)
{
frame.executeAction(this, _currentFrameID);
advanceTime(passedTime);
return;
}
}
if (_currentTime == totalTime)
{
if (_loop)
{
_currentTime = 0.0;
_currentFrameID = 0;
frame = _frames[0];
frame.playSound(_soundTransform);
texture = frame.texture;
if (frame.action != null)
{
frame.executeAction(this, _currentFrameID);
advanceTime(passedTime);
return;
}
}
else return;
}
var finalFrameID:int = _frames.length - 1;
var restTimeInFrame:Number = frame.duration - _currentTime + frame.startTime;
var dispatchCompleteEvent:Boolean = false;
var frameAction:Function = null;
var previousFrameID:int = _currentFrameID;
var changedFrame:Boolean;
while (passedTime >= 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; i<numFrames; ++i)
_frames[i].duration *= acceleration;
updateStartTimes();
}
/** Indicates if the clip is still playing. Returns <code>false</code> 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");
}
}
}

View file

@ -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.
*
* <p>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).</p>
*
* <p>A quad is, by definition, always rectangular. The basic quad class will always contain
* exactly four vertices, arranged like this:</p>
*
* <pre>
* 0 - 1
* | / |
* 2 - 3
* </pre>
*
* <p>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).</p>
*
* <p>However, note that the number of vertices may be different in subclasses.
* Check the property <code>numVertices</code> if you are unsure.</p>
*
* @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 <code>null</code>, 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
* <code>setTexCoords</code>-method.
*
* <p>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 <code>readjustSize</code> to
* synchronize quad and texture size.</p>
*
* <p>You could also set the texture via the <code>style.texture</code> 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.
* </p>
*/
override public function set texture(value:Texture):void
{
if (value != texture)
{
super.texture = value;
setupVertices();
}
}
}
}

View file

@ -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();
}
}
}

View file

@ -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.
*
* <p>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.</p>
*
* <p><strong>Positioning objects in 3D</strong></p>
*
* <p>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:</p>
*
* <ul>
* <li>z - Moves the sprite closer to / further away from the camera.</li>
* <li>rotationX Rotates the sprite around the x-axis.</li>
* <li>rotationY Rotates the sprite around the y-axis.</li>
* <li>scaleZ - Scales the sprite along the z-axis.</li>
* <li>pivotZ - Moves the pivot point along the z-axis.</li>
* </ul>
*
* <p>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).</p>
*
* <p>Note that Starling does not make any z-tests: visibility is solely established by the
* order of the children, just as with 2D objects.</p>
*
* <p><strong>Setting up the camera</strong></p>
*
* <p>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.</p>
*
* <p><strong>Limitations</strong></p>
*
* <p>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.</p>
*
*/
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; i<numChildren; ++i)
recursivelySetIs3D(container.getChildAt(i), value);
}
object.setIs3D(value);
}
private function updateMatrices():void
{
var x:Number = this.x;
var y:Number = this.y;
var scaleX:Number = this.scaleX;
var scaleY:Number = this.scaleY;
var pivotX:Number = this.pivotX;
var pivotY:Number = this.pivotY;
var rotationZ:Number = this.rotation;
_transformationMatrix3D.identity();
if (scaleX != 1.0 || scaleY != 1.0 || _scaleZ != 1.0)
_transformationMatrix3D.appendScale(scaleX || E , scaleY || E, _scaleZ || E);
if (_rotationX != 0.0)
_transformationMatrix3D.appendRotation(rad2deg(_rotationX), Vector3D.X_AXIS);
if (_rotationY != 0.0)
_transformationMatrix3D.appendRotation(rad2deg(_rotationY), Vector3D.Y_AXIS);
if (rotationZ != 0.0)
_transformationMatrix3D.appendRotation(rad2deg( rotationZ), Vector3D.Z_AXIS);
if (x != 0.0 || y != 0.0 || _z != 0.0)
_transformationMatrix3D.appendTranslation(x, y, _z);
if (pivotX != 0.0 || pivotY != 0.0 || _pivotZ != 0.0)
_transformationMatrix3D.prependTranslation(-pivotX, -pivotY, -_pivotZ);
if (_is2D) MatrixUtil.convertTo2D(_transformationMatrix3D, _transformationMatrix);
else _transformationMatrix.identity();
}
// properties
/** The 2D transformation matrix of the object relative to its parent if it can be
* represented in such a matrix (the values of 'z', 'rotationX/Y', and 'pivotZ' are
* zero). Otherwise, the identity matrix. CAUTION: not a copy, but the actual object! */
public override function get transformationMatrix():Matrix
{
if (_transformationChanged)
{
updateMatrices();
_transformationChanged = false;
}
return _transformationMatrix;
}
public override function set transformationMatrix(value:Matrix):void
{
super.transformationMatrix = value;
_rotationX = _rotationY = _pivotZ = _z = 0;
_transformationChanged = true;
}
/** The 3D transformation matrix of the object relative to its parent.
* CAUTION: not a copy, but the actual object! */
public override function get transformationMatrix3D():Matrix3D
{
if (_transformationChanged)
{
updateMatrices();
_transformationChanged = false;
}
return _transformationMatrix3D;
}
/** @inheritDoc */
public override function set x(value:Number):void
{
super.x = value;
_transformationChanged = true;
}
/** @inheritDoc */
public override function set y(value:Number):void
{
super.y = value;
_transformationChanged = true;
}
/** The z coordinate of the object relative to the local coordinates of the parent.
* The z-axis points away from the camera, i.e. positive z-values will move the object further
* away from the viewer. */
public function get z():Number { return _z; }
public function set z(value:Number):void
{
_z = value;
_transformationChanged = true;
setRequiresRedraw();
}
/** @inheritDoc */
public override function set pivotX(value:Number):void
{
super.pivotX = value;
_transformationChanged = true;
}
/** @inheritDoc */
public override function set pivotY(value:Number):void
{
super.pivotY = value;
_transformationChanged = true;
}
/** The z coordinate of the object's origin in its own coordinate space (default: 0). */
public function get pivotZ():Number { return _pivotZ; }
public function set pivotZ(value:Number):void
{
_pivotZ = value;
_transformationChanged = true;
setRequiresRedraw();
}
/** @inheritDoc */
public override function set scaleX(value:Number):void
{
super.scaleX = value;
_transformationChanged = true;
}
/** @inheritDoc */
public override function set scaleY(value:Number):void
{
super.scaleY = value;
_transformationChanged = true;
}
/** The depth scale factor. '1' means no scale, negative values flip the object. */
public function get scaleZ():Number { return _scaleZ; }
public function set scaleZ(value:Number):void
{
_scaleZ = value;
_transformationChanged = true;
setRequiresRedraw();
}
/** @private */
override public function set scale(value:Number):void
{
scaleX = scaleY = scaleZ = value;
}
/** @private */
public override function set skewX(value:Number):void
{
throw new Error("3D objects do not support skewing");
// super.skewX = value;
// _orientationChanged = true;
}
/** @private */
public override function set skewY(value:Number):void
{
throw new Error("3D objects do not support skewing");
// super.skewY = value;
// _orientationChanged = true;
}
/** The rotation of the object about the z axis, in radians.
* (In Starling, all angles are measured in radians.) */
public override function set rotation(value:Number):void
{
super.rotation = value;
_transformationChanged = true;
}
/** The rotation of the object about the x axis, in radians.
* (In Starling, all angles are measured in radians.) */
public function get rotationX():Number { return _rotationX; }
public function set rotationX(value:Number):void
{
_rotationX = MathUtil.normalizeAngle(value);
_transformationChanged = true;
setRequiresRedraw();
}
/** The rotation of the object about the y axis, in radians.
* (In Starling, all angles are measured in radians.) */
public function get rotationY():Number { return _rotationY; }
public function set rotationY(value:Number):void
{
_rotationY = MathUtil.normalizeAngle(value);
_transformationChanged = true;
setRequiresRedraw();
}
/** The rotation of the object about the z axis, in radians.
* (In Starling, all angles are measured in radians.) */
public function get rotationZ():Number { return rotation; }
public function set rotationZ(value:Number):void { rotation = value; }
}
}

View file

@ -0,0 +1,350 @@
// =================================================================================================
//
// 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.display.BitmapData;
import flash.display3D.Context3D;
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 starling.core.Starling;
import starling.core.starling_internal;
import starling.events.EnterFrameEvent;
import starling.events.Event;
import starling.filters.FragmentFilter;
import starling.rendering.Painter;
import starling.rendering.RenderState;
import starling.utils.MatrixUtil;
import starling.utils.RectangleUtil;
use namespace starling_internal;
/** Dispatched when the Flash container is resized. */
[Event(name="resize", type="starling.events.ResizeEvent")]
/** A Stage represents the root of the display tree.
* Only objects that are direct or indirect children of the stage will be rendered.
*
* <p>This class represents the Starling version of the stage. Don't confuse it with its
* Flash equivalent: while the latter contains objects of the type
* <code>flash.display.DisplayObject</code>, the Starling stage contains only objects of the
* type <code>starling.display.DisplayObject</code>. Those classes are not compatible, and
* you cannot exchange one type with the other.</p>
*
* <p>A stage object is created automatically by the <code>Starling</code> class. Don't
* create a Stage instance manually.</p>
*
* <strong>Keyboard Events</strong>
*
* <p>In Starling, keyboard events are only dispatched at the stage. Add an event listener
* directly to the stage to be notified of keyboard events.</p>
*
* <strong>Resize Events</strong>
*
* <p>When the Flash player is resized, the stage dispatches a <code>ResizeEvent</code>. The
* event contains properties containing the updated width and height of the Flash player.</p>
*
* @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.<DisplayObject>;
// 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 <DisplayObject>[];
}
/** @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.
*
* <p>If you encounter problems with transparency, start Starling in BASELINE profile
* (or higher). BASELINE_CONSTRAINED might not support transparency on all platforms.
* </p>
*
* @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.<DisplayObject>):void
{
if (eventType == Event.ENTER_FRAME && object == this)
{
for (var i:int=0, length:int=_enterFrameListeners.length; i<length; ++i)
listeners[listeners.length] = _enterFrameListeners[i]; // avoiding 'push'
}
else
super.getChildEventListeners(object, eventType, listeners);
}
// properties
/** @private */
public override function set width(value:Number):void
{
throw new IllegalOperationError("Cannot set width of stage");
}
/** @private */
public override function set height(value:Number):void
{
throw new IllegalOperationError("Cannot set height of stage");
}
/** @private */
public override function set x(value:Number):void
{
throw new IllegalOperationError("Cannot set x-coordinate of stage");
}
/** @private */
public override function set y(value:Number):void
{
throw new IllegalOperationError("Cannot set y-coordinate of stage");
}
/** @private */
public override function set scaleX(value:Number):void
{
throw new IllegalOperationError("Cannot scale stage");
}
/** @private */
public override function set scaleY(value:Number):void
{
throw new IllegalOperationError("Cannot scale stage");
}
/** @private */
public override function set rotation(value:Number):void
{
throw new IllegalOperationError("Cannot rotate stage");
}
/** @private */
public override function set skewX(value:Number):void
{
throw new IllegalOperationError("Cannot skew stage");
}
/** @private */
public override function set skewY(value:Number):void
{
throw new IllegalOperationError("Cannot skew stage");
}
/** @private */
public override function set filter(value:FragmentFilter):void
{
throw new IllegalOperationError("Cannot add filter to stage. Add it to 'root' instead!");
}
/** The background color of the stage. */
public function get color():uint { return _color; }
public function set color(value:uint):void { _color = value; }
/** The width of the stage coordinate system. Change it to scale its contents relative
* to the <code>viewPort</code> 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 <code>viewPort</code> 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> = Starling.all;
var numInstances:int = instances.length;
for (var i:int=0; i<numInstances; ++i)
if (instances[i].stage == this) return instances[i];
return null;
}
/** The distance between the stage and the camera. Changing this value will update the
* field of view accordingly. */
public function get focalLength():Number
{
return _width / (2 * Math.tan(_fieldOfView/2));
}
public function set focalLength(value:Number):void
{
_fieldOfView = 2 * Math.atan(stageWidth / (2*value));
}
/** Specifies an angle (radian, between zero and PI) for the field of view. This value
* determines how strong the perspective transformation and distortion apply to a Sprite3D
* object.
*
* <p>A 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.</p>
*
* @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. <p>CAUTION: not a copy, but the actual object!</p>
*/
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.
*
* <p>CAUTION: not a copy, but the actual object!</p>
*/
public function get cameraPosition():Vector3D
{
return getCameraPosition(null, _cameraPosition);
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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.
*
* <p>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.</p>
*
* <p>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.</p>
*
* <p>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.</p>
*
* @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.<Event> = new <Event>[];
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;
}
}
}

View file

@ -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.
*
* <p>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.</p>
*
* <p>As in the conventional Flash classes, display objects inherit from EventDispatcher
* and can thus dispatch events. Beware, though, that the Starling event classes are
* <em>not compatible with Flash events:</em> 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.</p>
*
* @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.<Function> = _eventListeners[type] as Vector.<Function>;
if (listeners == null)
_eventListeners[type] = new <Function>[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.<Function> = _eventListeners[type] as Vector.<Function>;
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.<Function> = listeners.slice(0, index);
for (var i:int=index+1; i<numListeners; ++i)
restListeners[i-1] = listeners[i];
_eventListeners[type] = restListeners;
}
}
}
}
/** Removes all event listeners with a certain type, or all of them if type is null.
* Be careful when removing all event listeners: you never know who else was listening. */
public function removeEventListeners(type:String=null):void
{
if (type && _eventListeners)
delete _eventListeners[type];
else
_eventListeners = null;
}
/** Dispatches an event to all objects that have registered listeners for its type.
* If an event with enabled 'bubble' property is dispatched to a display object, it will
* travel up along the line of parents, until it either hits the root object or someone
* stops its propagation manually. */
public function dispatchEvent(event:Event):void
{
var bubbles:Boolean = event.bubbles;
if (!bubbles && (_eventListeners == null || !(event.type in _eventListeners)))
return; // no need to do anything
// we save the current target and restore it later;
// this allows users to re-dispatch events without creating a clone.
var previousTarget:EventDispatcher = event.target;
event.setTarget(this);
if (bubbles && this is DisplayObject) bubbleEvent(event);
else invokeEvent(event);
if (previousTarget) event.setTarget(previousTarget);
}
/** @private
* Invokes an event on the current object. This method does not do any bubbling, nor
* does it back-up and restore the previous target on the event. The 'dispatchEvent'
* method uses this method internally. */
internal function invokeEvent(event:Event):Boolean
{
var listeners:Vector.<Function> = _eventListeners ?
_eventListeners[event.type] as Vector.<Function> : 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<numListeners; ++i)
{
var listener:Function = listeners[i] as Function;
var numArgs:int = listener.length;
if (numArgs == 0) listener();
else if (numArgs == 1) listener(event);
else listener(event, event.data);
if (event.stopsImmediatePropagation)
return true;
}
return event.stopsPropagation;
}
else
{
return false;
}
}
/** @private */
internal function bubbleEvent(event:Event):void
{
// we determine the bubble chain before starting to invoke the listeners.
// that way, changes done by the listeners won't affect the bubble chain.
var chain:Vector.<EventDispatcher>;
var element:DisplayObject = this as DisplayObject;
var length:int = 1;
if (sBubbleChains.length > 0) { chain = sBubbleChains.pop(); chain[0] = element; }
else chain = new <EventDispatcher>[element];
while ((element = element.parent) != null)
chain[int(length++)] = element;
for (var i:int=0; i<length; ++i)
{
var stopPropagation:Boolean = chain[i].invokeEvent(event);
if (stopPropagation) break;
}
chain.length = 0;
sBubbleChains[sBubbleChains.length] = chain; // avoid 'push'
}
/** Dispatches an event with the given parameters to all objects that have registered
* listeners for the given type. The method uses an internal pool of event objects to
* avoid allocations. */
public function dispatchEventWith(type:String, bubbles:Boolean=false, data:Object=null):void
{
if (bubbles || hasEventListener(type))
{
var event:Event = Event.fromPool(type, bubbles, data);
dispatchEvent(event);
Event.toPool(event);
}
}
/** If called with one argument, figures out if there are any listeners registered for
* the given event type. If called with two arguments, also determines if a specific
* listener is registered. */
public function hasEventListener(type:String, listener:Function=null):Boolean
{
var listeners:Vector.<Function> = _eventListeners ? _eventListeners[type] : null;
if (listeners == null) return false;
else
{
if (listener != null) return listeners.indexOf(listener) != -1;
else return listeners.length != 0;
}
}
}
}

View file

@ -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.
*
* <p>This is Starling's version of the Flash KeyboardEvent class. It contains the same
* properties as the Flash equivalent.</p>
*
* <p>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.</p>
*
* @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; }
}
}

View file

@ -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.
*
* <p>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
* <code>Starling.current.viewPort</code> rectangle accordingly. If you want to make use of
* the additional screen estate, update the values of <code>stage.stageWidth</code> and
* <code>stage.stageHeight</code> as well.</p>
*
* @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; }
}
}

View file

@ -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.
*
* <p>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.</p>
*
* <strong>The position of a touch</strong>
*
* <p>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.</p>
*
* @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.<EventDispatcher>;
/** 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 <EventDispatcher>[];
}
/** Converts the current location of a touch to the local coordinate system of a display
* object. If you pass an <code>out</code>-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 <code>out</code>-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 <code>out</code>-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.<EventDispatcher>
{
return _bubbleChain.concat();
}
}
}

View file

@ -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.
*
* <p>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 <code>TouchEvent.TOUCH</code>. 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.</p>
*
* <p>The difference between mouse input and touch input is that</p>
*
* <ul>
* <li>only one mouse cursor can be present at a given moment and</li>
* <li>only the mouse can "hover" over an object without a pressed button.</li>
* </ul>
*
* <strong>Which objects receive touch events?</strong>
*
* <p>In Starling, any display object receives touch events, as long as the
* <code>touchable</code> property of the object and its parents is enabled. There
* is no "InteractiveObject" class in Starling.</p>
*
* <strong>How to work with individual touches</strong>
*
* <p>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:</p>
*
* <code>var touches:Vector.&lt;Touch&gt; = touchEvent.getTouches(this);</code>
*
* <p>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:</p>
*
* <code>var touch:Touch = touchEvent.getTouch(this);</code>
*
* @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.<EventDispatcher>;
/** Helper object. */
private static var sTouches:Vector.<Touch> = new <Touch>[];
/** Creates a new TouchEvent instance. */
public function TouchEvent(type:String, touches:Vector.<Touch>=null, shiftKey:Boolean=false,
ctrlKey:Boolean=false, bubbles:Boolean=true)
{
super(type, bubbles, touches);
_shiftKey = shiftKey;
_ctrlKey = ctrlKey;
_visitedObjects = new <EventDispatcher>[];
updateTimestamp(touches);
}
/** @private */
internal function resetTo(type:String, touches:Vector.<Touch>=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.<Touch>):void
{
_timestamp = -1.0;
var numTouches:int = touches ? touches.length : 0;
for (var i:int=0; i<numTouches; ++i)
if (touches[i].timestamp > _timestamp)
_timestamp = touches[i].timestamp;
}
/** Returns a list of touches that originated over a certain target. If you pass an
* <code>out</code>-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.<Touch>=null):Vector.<Touch>
{
if (out == null) out = new <Touch>[];
var allTouches:Vector.<Touch> = data as Vector.<Touch>;
var numTouches:int = allTouches.length;
for (var i:int=0; i<numTouches; ++i)
{
var touch:Touch = allTouches[i];
var correctTarget:Boolean = touch.isTouching(target);
var correctPhase:Boolean = (phase == null || phase == touch.phase);
if (correctTarget && correctPhase)
out[out.length] = touch; // avoiding 'push'
}
return out;
}
/** Returns a touch that originated over a certain target.
*
* @param target The object that was touched; may also be a parent of the actual
* touch-target.
* @param phase The phase the touch must be in, or null if you don't care.
* @param id The ID of the requested touch, or -1 if you don't care.
*/
public function getTouch(target:DisplayObject, phase:String=null, id:int=-1):Touch
{
getTouches(target, phase, sTouches);
var numTouches:int = sTouches.length;
if (numTouches > 0)
{
var touch:Touch = null;
if (id < 0) touch = sTouches[0];
else
{
for (var i:int=0; i<numTouches; ++i)
if (sTouches[i].id == id) { touch = sTouches[i]; break; }
}
sTouches.length = 0;
return touch;
}
else return null;
}
/** Indicates if a target is currently being touched or hovered over. */
public function interactsWith(target:DisplayObject):Boolean
{
var result:Boolean = false;
getTouches(target, null, sTouches);
for (var i:int=sTouches.length-1; 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.<EventDispatcher>):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<chainLength; ++i)
{
var chainElement:EventDispatcher = chain[i] as EventDispatcher;
if (_visitedObjects.indexOf(chainElement) == -1)
{
var stopPropagation:Boolean = chainElement.invokeEvent(this);
_visitedObjects[_visitedObjects.length] = chainElement;
if (stopPropagation) break;
}
}
setTarget(previousTarget);
}
}
// properties
/** The time the event occurred (in seconds since application launch). */
public function get timestamp():Number { return _timestamp; }
/** All touches that are currently available. */
public function get touches():Vector.<Touch> { return (data as Vector.<Touch>).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; }
}
}

View file

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

View file

@ -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.
*
* <p>A touch moves through at least the following phases in its life:</p>
*
* <code>BEGAN -> MOVED -> ENDED</code>
*
* <p>Furthermore, a touch can enter a <code>STATIONARY</code> 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 <em>moving</em> finger. In the list of touches
* of that event, you will find the second touch in the stationary phase.</p>
*
* <p>Finally, there's the <code>HOVER</code> phase, which is exclusive to mouse input. It is
* the equivalent of a <code>MouseOver</code> event in Flash when the mouse button is
* <em>not</em> pressed.</p>
*/
public final class TouchPhase
{
/** @private */
public function TouchPhase() { throw new AbstractClassError(); }
/** Only available for mouse input: the cursor hovers over an object <em>without</em> 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";
}
}

View file

@ -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.
*
* <p>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.</p>
*
* <p>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.</p>
*
* <p>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.</p>
*
* <strong>Subclassing TouchProcessor</strong>
*
* <p>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.</p>
*
* <p>To use your custom TouchProcessor, assign it to the "Starling.touchProcessor"
* property.</p>
*
* <p>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.</p>
*/
public class TouchProcessor
{
private var _stage:Stage;
private var _root:DisplayObject;
private var _elapsedTime:Number;
private var _lastTaps:Vector.<Touch>;
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.<Array>;
/** The list of all currently active touches. */
protected var _currentTouches:Vector.<Touch>;
/** Helper objects. */
private static var sUpdatedTouches:Vector.<Touch> = new <Touch>[];
private static var sHoveringTouchData:Vector.<Object> = new <Object>[];
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 <Touch>[];
_queue = new <Array>[];
_lastTaps = new <Touch>[];
_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.<Touch>,
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.
*
* <p>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.</p>
*/
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.<Touch>, 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();
}
}
}

View file

@ -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
* <code>Math.ceil(blur)</code>.
*
* <ul><li>blur = 0.5: 1 pass</li>
* <li>blur = 1.0: 1 pass</li>
* <li>blur = 1.5: 2 passes</li>
* <li>blur = 2.0: 2 passes</li>
* <li>etc.</li>
* </ul>
*/
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 <code>Math.ceil(value)</code>. */
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 <code>Math.ceil(value)</code>. */
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.<Number> = new <Number>[0, 0, 0, 0];
private var _weights:Vector.<Number> = new <Number>[0, 0, 0, 0];
// helpers
private var sTmpWeights:Vector.<Number> = new Vector.<Number>(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);
}
}

View file

@ -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.
*
* <p>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:</p>
*
* <listing>
* // 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);</listing>
*
* <p>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.</p>
*/
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.<Number> = new <Number>[];
/** 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.<Number>=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.<Number>):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.<Number> { return colorEffect.matrix; }
public function set matrix(value:Vector.<Number>):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.<Number>; // offset in range 0-255
private var _shaderMatrix:Vector.<Number>; // offset in range 0-1, changed order
private static const MIN_COLOR:Vector.<Number> = new <Number>[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.<Number> = new Vector.<Number>(20, true);
public function ColorMatrixEffect():void
{
_userMatrix = new <Number>[];
_shaderMatrix = new <Number>[];
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.<Number>):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.<Number>, to:Vector.<Number>):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.<Number> { return _userMatrix; }
public function set matrix(value:Vector.<Number>):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();
}
}

View file

@ -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 <code>replace</code> 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.<CompositeLayer>;
private static var sLayers:Array = [];
private static var sOffset:Vector.<Number> = new <Number>[0, 0, 0, 0];
private static var sColor:Vector.<Number> = new <Number>[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.<CompositeLayer>(numLayers, true);
for (var i:int=0; i<numLayers; ++i)
_layers[i] = new CompositeLayer();
}
public function getLayerAt(layerID:int):CompositeLayer
{
return _layers[layerID];
}
private function getUsedLayers(out:Array=null):Array
{
if (out == null) out = [];
else out.length = 0;
for each (var layer:CompositeLayer in _layers)
if (layer.texture) out[out.length] = layer;
return out;
}
override protected function createProgram():Program
{
var layers:Array = getUsedLayers(sLayers);
var numLayers:int = layers.length;
var i:int;
if (numLayers)
{
var vertexShader:Array = ["m44 op, va0, vc0"]; // transform position to clip-space
var layer:CompositeLayer = _layers[0];
for (i=0; i<numLayers; ++i) // v0-4 -> 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<numLayers; ++i)
{
var fti:String = "ft" + i;
var fci:String = "fc" + i;
var vi:String = "v" + i;
layer = _layers[i];
fragmentShader.push(
tex(fti, vi, i, layers[i].texture) // fti => 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; i<numLayers; ++i)
{
layer = layers[i];
bits = RenderUtil.getTextureVariantBits(layer.texture) | (int(layer.replaceColor) << 3);
totalBits |= bits << (i * 4);
}
return totalBits;
}
/** vc0-vc3 MVP matrix
* vc4-vc7 layer offsets
* fs0-fs3 input textures
* fc0-fc3 input colors (RGBA+pma)
* va0 vertex position (xy)
* va1-va4 texture coordinates (without offset)
* v0-v3 texture coordinates (with offset)
*/
override protected function beforeDraw(context:Context3D):void
{
var layers:Array = getUsedLayers(sLayers);
var numLayers:int = layers.length;
if (numLayers)
{
for (var i:int=0; i<numLayers; ++i)
{
var layer:CompositeLayer = layers[i];
var texture:Texture = layer.texture;
var alphaFactor:Number = layer.replaceColor ? 1.0 : layer.alpha;
sOffset[0] = -layer.x / (texture.root.nativeWidth / texture.scale);
sOffset[1] = -layer.y / (texture.root.nativeHeight / texture.scale);
sColor[0] = Color.getRed(layer.color) * alphaFactor / 255.0;
sColor[1] = Color.getGreen(layer.color) * alphaFactor / 255.0;
sColor[2] = Color.getBlue(layer.color) * alphaFactor / 255.0;
sColor[3] = layer.alpha;
context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, i + 4, sOffset);
context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, i, sColor);
context.setTextureAt(i, texture.base);
RenderUtil.setSamplerStateAt(i, texture.mipMapping, textureSmoothing);
}
for (i=1; i<numLayers; ++i)
vertexFormat.setVertexBufferAt(i + 1, vertexBuffer, "texCoords" + i);
}
super.beforeDraw(context);
}
override protected function afterDraw(context:Context3D):void
{
var layers:Array = getUsedLayers(sLayers);
var numLayers:int = layers.length;
for (var i:int=0; i<numLayers; ++i)
{
context.setTextureAt(i, null);
context.setVertexBufferAt(i + 1, null);
}
super.afterDraw(context);
}
override public function get vertexFormat():VertexDataFormat
{
return VERTEX_FORMAT;
}
// properties
public function get numLayers():int { return _layers.length; }
override public function set texture(value:Texture):void
{
_layers[0].texture = value;
super.texture = value;
}
}
class CompositeLayer
{
public var texture:Texture;
public var x:Number;
public var y:Number;
public var color:uint;
public var alpha:Number;
public var replaceColor:Boolean;
public function CompositeLayer()
{
x = y = 0;
alpha = 1.0;
color = 0xffffff;
}
}

View file

@ -0,0 +1,355 @@
// =================================================================================================
//
// 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.Stage;
import starling.rendering.FilterEffect;
import starling.rendering.Painter;
import starling.textures.Texture;
/** The DisplacementMapFilter class uses the pixel values from the specified texture (called
* the map texture) to perform a displacement of an object. You can use this filter
* to apply a warped or mottled effect to any object that inherits from the DisplayObject
* class.
*
* <p>The filter uses the following formula:</p>
* <listing>dstPixel[x, y] = srcPixel[x + ((componentX(x, y) - 128) &#42; scaleX) / 256,
* y + ((componentY(x, y) - 128) &#42; scaleY) / 256]
* </listing>
*
* <p>Where <code>componentX(x, y)</code> gets the componentX property color value from the
* map texture at <code>(x - mapPoint.x, y - mapPoint.y)</code>.</p>
*/
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.<Number> = new <Number>[0.5, 0.5, 0.0, 0.0];
private static var sMatrix:Matrix3D = new Matrix3D();
private static var sMatrixData:Vector.<Number> =
new <Number>[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; }
}

View file

@ -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 <code>Math.ceil(value) × 2</code>.
* @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();
}
}
}
}

View file

@ -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.<FragmentFilter>;
// helpers
private static var sPadding:Padding = new Padding();
/** Creates a new chain with the given filters. */
public function FilterChain(...args)
{
_filters = new <FragmentFilter>[];
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<numFilters; ++i)
{
inTexture = outTexture;
outTexture = _filters[i].process(painter, helper, inTexture);
if (i) helper.putTexture(inTexture);
}
return outTexture;
}
/** @private */
override public function get numPasses():int
{
var numPasses:int = 0;
var numFilters:int = _filters.length;
for (var i:int=0; i<numFilters; ++i)
numPasses += _filters[i].numPasses;
return numPasses;
}
/** Returns the filter at a certain index. If you pass a negative index,
* '-1' will return the last filter, '-2' the second to last filter, etc. */
public function getFilterAt(index:int):FragmentFilter
{
if (index < 0) index += numFilters;
return _filters[index];
}
/** Adds a filter to the chain. It will be appended at the very end. */
public function addFilter(filter:FragmentFilter):void
{
addFilterAt(filter, _filters.length);
}
/** Adds a filter to the chain at the given index. */
public function addFilterAt(filter:FragmentFilter, index:int):void
{
_filters.insertAt(index, filter);
filter.addEventListener(Event.CHANGE, setRequiresRedraw);
setRequiresRedraw();
}
/** Removes a filter from the chain. If the filter is not part of the chain,
* nothing happens. If requested, the filter will be disposed right away. */
public function removeFilter(filter:FragmentFilter, dispose:Boolean=false):FragmentFilter
{
var filterIndex:int = getFilterIndex(filter);
if (filterIndex != -1) removeFilterAt(filterIndex, dispose);
return filter;
}
/** Removes the filter at a certain index. The indices of any subsequent filters
* are decremented. If requested, the filter will be disposed right away. */
public function removeFilterAt(index:int, dispose:Boolean=false):FragmentFilter
{
var filter:FragmentFilter = _filters.removeAt(index) as FragmentFilter;
filter.removeEventListener(Event.CHANGE, setRequiresRedraw);
if (dispose) filter.dispose();
setRequiresRedraw();
return filter;
}
/** Returns the index of a filter within the chain, or "-1" if it is not found. */
public function getFilterIndex(filter:FragmentFilter):int
{
return _filters.indexOf(filter);
}
private function updatePadding():void
{
sPadding.setTo();
for each (var filter:FragmentFilter in _filters)
{
var padding:Padding = filter.padding;
if (padding.left > 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<numFilters; ++i) _filters[i].dispatchEvent(event);
}
/** Indicates the current chain length. */
public function get numFilters():int { return _filters.length; }
}
}

View file

@ -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.filters
{
import flash.display3D.Context3DProfile;
import flash.geom.Matrix3D;
import flash.geom.Rectangle;
import starling.core.Starling;
import starling.core.starling_internal;
import starling.display.DisplayObject;
import starling.textures.SubTexture;
import starling.textures.Texture;
import starling.utils.MathUtil;
use namespace starling_internal;
/** @private
*
* This class manages texture creation, pooling and disposal of all textures
* during filter processing.
*/
internal class FilterHelper implements IFilterHelper
{
private var _width:Number;
private var _height:Number;
private var _nativeWidth:int;
private var _nativeHeight:int;
private var _pool:Vector.<Texture>;
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 <Texture>[];
_projectionMatrix = new Matrix3D();
_targetBounds = new Rectangle();
setSize(_sizeStep, _sizeStep);
}
/** Purges the pool. */
public function dispose():void
{
purge();
}
/** Starts a new round of rendering. If <code>numPasses</code> is greater than zero, each
* <code>getTexture()</code> call will be counted as one pass; the final pass will then
* return <code>null</code> 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; }
}
}

View file

@ -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
* <code>filter</code> property.
*
* <p>A fragment filter works in the following way:</p>
* <ol>
* <li>The object to be filtered is rendered into a texture.</li>
* <li>That texture is passed to the <code>process</code> method.</li>
* <li>This method processes the texture using a <code>FilterEffect</code> subclass
* that processes the input via fragment and vertex shaders to achieve a certain
* effect.</li>
* <li>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.</li>
* <li>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.</li>
* <li>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 <code>alwaysDrawToBackBuffer</code> property.</li>
* </ol>
*
* <p>All of this is set up by the basic FragmentFilter class. Concrete subclasses
* just need to override the protected method <code>createEffect</code> and (optionally)
* <code>process</code>. Multi-pass filters must also override <code>numPasses</code>.</p>
*
* <p>Typically, any properties on the filter are just forwarded to an effect instance,
* which is then used automatically by <code>process</code> to render the filter pass.
* For a simple example on how to write a single-pass filter, look at the implementation of
* the <code>ColorMatrixFilter</code>; for a composite filter (i.e. a filter that combines
* several others), look at the <code>GlowFilter</code>.
* </p>
*
* <p>Beware that a filter instance may only be used on one object at a time!</p>
*
* <p><strong>Animated filters</strong></p>
*
* <p>The <code>process</code> 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 <code>ENTER_FRAME</code>-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.</p>
*
* <p><strong>Caching</strong></p>
*
* <p>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 <code>cache()</code> again in order for any changes to show up.</p>
*
* @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 <code>helper</code>) that
* contains the filtered output. To to do this, it configures the FilterEffect
* (provided via <code>createEffect</code>) and calls its <code>render</code> method.
*
* <p>In a standard filter, only <code>input0</code> 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.</p>
*
* <p>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.</p>
*/
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.
*
* <p>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 <code>cache</code> again or change the filter settings.
* </p>
*
* <p>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 <em>can</em> cache a 2D object that has 3D children!</p>
*/
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 <code>createEffect</code>. */
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 <code>1</code>. */
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 <code>cache</code> 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.
*
* <p>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).</p>
*
* <p>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.</p>
*
* @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);
}
}

View file

@ -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 <code>Math.ceil(value) × 2</code>.
* @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();
}
}
}
}

View file

@ -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 <code>helper</code> object passed
* to the <code>process</code> 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.
*
* <p>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 <code>null</code> for the last pass! That's the
* sign for the <code>process</code> method to draw to the back buffer. If you receive
* <code>null</code> too soon, the filter class probably didn't correctly override
* <code>numPasses</code>.</p>
*/
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;
}
}

View file

@ -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.
*
* <p>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.</p>
*/
public class Polygon
{
private var _coords:Vector.<Number>;
// Helper object
private static var sRestIndices:Vector.<uint> = new <uint>[];
/** 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 <Number>[];
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<numCoords; ++i)
clone._coords[i] = _coords[i];
return clone;
}
/** Reverses the order of the vertices. Note that some methods of the Polygon class
* require the vertices in clockwise order. */
public function reverse():void
{
var numCoords:int = _coords.length;
var numVertices:int = numCoords / 2;
var tmp:Number;
for (var i:int=0; i<numVertices; i += 2)
{
tmp = _coords[i];
_coords[i] = _coords[numCoords - i - 2];
_coords[numCoords - i - 2] = tmp;
tmp = _coords[i + 1];
_coords[i + 1] = _coords[numCoords - i - 1];
_coords[numCoords - i - 1] = tmp;
}
}
/** Adds vertices to the polygon. Pass either a list of 'Point' instances or alternating
* 'x' and 'y' coordinates. */
public function addVertices(...args):void
{
var i:int;
var numArgs:int = args.length;
var numCoords:int = _coords.length;
if (numArgs > 0)
{
if (args[0] is Point)
{
for (i=0; i<numArgs; i++)
{
_coords[numCoords + i * 2 ] = (args[i] as Point).x;
_coords[numCoords + i * 2 + 1] = (args[i] as Point).y;
}
}
else if (args[0] is Number)
{
for (i=0; i<numArgs; ++i)
_coords[numCoords + i] = args[i];
}
else throw new ArgumentError("Invalid type: " + getQualifiedClassName(args[0]));
}
}
/** Moves a given vertex to a certain position or adds a new vertex at the end. */
public function setVertex(index:int, x:Number, y:Number):void
{
if (index >= 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<numVertices; ++i)
{
var ix:Number = _coords[i * 2];
var iy:Number = _coords[i * 2 + 1];
var jx:Number = _coords[j * 2];
var jy:Number = _coords[j * 2 + 1];
if ((iy < y && jy >= 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.
*
* <p>If you pass an indexData object, the new indices will be appended to it.
* Otherwise, a new instance will be created.</p> */
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<numVertices; ++i) sRestIndices[i] = i;
restIndexPos = 0;
numRestIndices = numVertices;
var a:Point = Pool.getPoint();
var b:Point = Pool.getPoint();
var c:Point = Pool.getPoint();
var p:Point = Pool.getPoint();
while (numRestIndices > 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<numVertices; ++i)
target.setPoint(targetVertexID + i, attrName, _coords[i * 2], _coords[i * 2 + 1]);
}
/** Creates a string that contains the values of all included points. */
public function toString():String
{
var result:String = "[Polygon";
var numPoints:int = this.numVertices;
if (numPoints > 0) result += "\n";
for (var i:int=0; i<numPoints; ++i)
{
result += " [Vertex " + i + ": " +
"x=" + _coords[i * 2 ].toFixed(1) + ", " +
"y=" + _coords[i * 2 + 1].toFixed(1) + "]" +
(i == numPoints - 1 ? "\n" : ",\n");
}
return result + "]";
}
// factory methods
/** Creates an ellipse with optimized implementations of triangulation, hitTest, etc. */
public static function createEllipse(x:Number, y:Number, radiusX:Number, radiusY:Number):Polygon
{
return new Ellipse(x, y, radiusX, radiusY);
}
/** Creates a circle with optimized implementations of triangulation, hitTest, etc. */
public static function createCircle(x:Number, y:Number, radius:Number):Polygon
{
return new Ellipse(x, y, radius, radius);
}
/** Creates a rectangle with optimized implementations of triangulation, hitTest, etc. */
public static function createRectangle(x:Number, y:Number,
width:Number, height:Number):Polygon
{
return new Rectangle(x, y, width, height);
}
// helpers
/** Calculates if the area of the triangle a->b->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 <code>O(n^2)</code>. */
public function get isSimple():Boolean
{
var numCoords:int = _coords.length;
if (numCoords <= 6) return true;
for (var i:int=0; i<numCoords; i += 2)
{
var ax:Number = _coords[ i ];
var ay:Number = _coords[ i + 1 ];
var bx:Number = _coords[(i + 2) % numCoords];
var by:Number = _coords[(i + 3) % numCoords];
var endJ:Number = i + numCoords - 2;
for (var j:int = i + 4; j<endJ; j += 2)
{
var cx:Number = _coords[ j % numCoords];
var cy:Number = _coords[(j + 1) % numCoords];
var dx:Number = _coords[(j + 2) % numCoords];
var dy:Number = _coords[(j + 3) % numCoords];
if (areVectorsIntersecting(ax, ay, bx, by, cx, cy, dx, dy))
return false;
}
}
return true;
}
/** Indicates if the polygon is convex. In a convex polygon, the vector between any two
* points inside the polygon lies inside it, as well. */
public function get isConvex():Boolean
{
var numCoords:int = _coords.length;
if (numCoords < 6) return true;
else
{
for (var i:int = 0; i < numCoords; i += 2)
{
if (!isConvexTriangle(_coords[i], _coords[i+1],
_coords[(i+2) % numCoords], _coords[(i+3) % numCoords],
_coords[(i+4) % numCoords], _coords[(i+5) % numCoords]))
{
return false;
}
}
}
return true;
}
/** Calculates the total area of the polygon. */
public function get area():Number
{
var area:Number = 0;
var numCoords:int = _coords.length;
if (numCoords >= 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<numSides; ++i)
{
vertices[i * 2 ] = Math.cos(angle) * _radiusX + _x;
vertices[i * 2 + 1] = Math.sin(angle) * _radiusY + _y;
angle += angleDelta;
}
return vertices;
}
override public function triangulate(indexData:IndexData=null, offset:int=0):IndexData
{
if (indexData == null) indexData = new IndexData((numVertices - 2) * 3);
var from:uint = 1;
var to:uint = numVertices - 1;
for (var i:int=from; i<to; ++i)
indexData.addTriangle(offset, offset + i, offset + i + 1);
return indexData;
}
override public function contains(x:Number, y:Number):Boolean
{
var vx:Number = x - _x;
var vy:Number = y - _y;
var a:Number = vx / _radiusX;
var b:Number = vy / _radiusY;
return a * a + b * b <= 1;
}
override public function get area():Number
{
return Math.PI * _radiusX * _radiusY;
}
override public function get isSimple():Boolean
{
return true;
}
override public function get isConvex():Boolean
{
return true;
}
}
class Rectangle extends ImmutablePolygon
{
private var _x:Number;
private var _y:Number;
private var _width:Number;
private var _height:Number;
public function Rectangle(x:Number, y:Number, width:Number, height:Number)
{
_x = x;
_y = y;
_width = width;
_height = height;
super([x, y, x + width, y, x + width, y + height, x, y + height]);
}
override public function triangulate(indexData:IndexData=null, offset:int=0):IndexData
{
if (indexData == null) indexData = new IndexData(6);
indexData.addTriangle(offset, offset + 1, offset + 3);
indexData.addTriangle(offset + 1, offset + 2, offset + 3);
return indexData;
}
override public function contains(x:Number, y:Number):Boolean
{
return x >= _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;
}
}

View file

@ -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.<MeshBatch>;
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 <MeshBatch>[];
_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, <code>onBatchComplete</code> 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 <code>null</code> 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 <code>modelviewMatrix</code>).
*/
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<numBatches; ++i)
_batchPool.put(_batches[i]);
_batches.length = 0;
_currentBatch = null;
_currentStyleType = null;
_cacheToken.reset();
}
/** Returns the batch at a certain index. */
public function getBatchAt(batchID:int):MeshBatch
{
return _batches[batchID];
}
/** Disposes all batches that are currently unused. */
public function trim():void
{
_batchPool.purge();
}
/** Sets all properties of the given token so that it describes the current position
* within this instance. */
public function fillToken(token:BatchToken):BatchToken
{
token.batchID = _cacheToken.batchID;
token.vertexID = _cacheToken.vertexID;
token.indexID = _cacheToken.indexID;
return token;
}
/** The number of batches currently stored in the BatchProcessor. */
public function get numBatches():int { return _batches.length; }
/** This callback is executed whenever a batch is finished and replaced by a new one.
* The finished MeshBatch is passed to the callback. Typically, this callback is used
* to actually render it. */
public function get onBatchComplete():Function { return _onBatchComplete; }
public function set onBatchComplete(value:Function):void { _onBatchComplete = value; }
}
}
import flash.utils.Dictionary;
import starling.display.MeshBatch;
class BatchPool
{
private var _batchLists:Dictionary;
public function BatchPool()
{
_batchLists = new Dictionary();
}
public function purge():void
{
for each (var batchList:Vector.<MeshBatch> in _batchLists)
{
for (var i:int=0; i<batchList.length; ++i)
batchList[i].dispose();
batchList.length = 0;
}
}
public function get(styleType:Class):MeshBatch
{
var batchList:Vector.<MeshBatch> = _batchLists[styleType];
if (batchList == null)
{
batchList = new <MeshBatch>[];
_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.<MeshBatch> = _batchLists[styleType];
if (batchList == null)
{
batchList = new <MeshBatch>[];
_batchLists[styleType] = batchList;
}
meshBatch.clear();
batchList[batchList.length] = meshBatch;
}
}

View file

@ -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.
*
* <p>Starling uses these tokens in its render cache. Each call to
* <code>painter.pushState()</code> or <code>painter.popState()</code> 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.</p>
*
* @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);
}
}
}

View file

@ -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.
*
* <p><strong>Using the Effect class</strong></p>
*
* <p>Effects are mostly used by the <code>MeshStyle</code> and <code>FragmentFilter</code>
* 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.</p>
*
* <p>Using an effect always follows steps shown in the example below. You create the
* effect, configure it, upload vertex data and then: draw!</p>
*
* <listing>
* // 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);</listing>
*
* <p>Note that the <code>VertexData</code> being uploaded has to be created with the same
* format as the one returned by the effect's <code>vertexFormat</code> property.</p>
*
* <p><strong>Extending the Effect class</strong></p>
*
* <p>The base <code>Effect</code>-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.</p>
*
* <p>Normally, you won't extend this class directly, but either <code>FilterEffect</code>
* or <code>MeshEffect</code>, 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:</p>
*
* <ul>
* <li><code>createProgram():Program</code> 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.</li>
* <li><code>get programVariantName():uint</code> (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.</li>
* <li><code>get vertexFormat():String</code> (optional) must return the
* <code>VertexData</code> format that this effect requires for its vertices. If
* the effect does not require any special attributes, you can leave this out.</li>
* <li><code>beforeDraw(context:Context3D):void</code> Set up your context by
* configuring program constants and buffer attributes.</li>
* <li><code>afterDraw(context:Context3D):void</code> Will be called directly after
* <code>context.drawTriangles()</code>. Clean up any context configuration here.</li>
* </ul>
*
* <p>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 <code>FilterEffect</code> and <code>MeshEffect</code>
* classes to see how to approach sub-classing.</p>
*
* @see FilterEffect
* @see MeshEffect
* @see starling.styles.MeshStyle
* @see starling.filters.FragmentFilter
* @see starling.utils.RenderUtil
*/
public class Effect
{
/** The vertex format expected by <code>uploadVertexData</code>:
* <code>"position:float2"</code> */
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
* <code>Context3DBufferUsage</code>. 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
* <code>Context3DBufferUsage</code>. 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 <code>beforeDraw</code>, <code>context.drawTriangles</code>, and
* <code>afterDraw</code>, 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 <code>render</code>, directly before
* <code>context.drawTriangles</code>. It activates the program and sets up
* the context with the following constants and attributes:
*
* <ul>
* <li><code>vc0-vc3</code> MVP matrix</li>
* <li><code>va0</code> vertex position (xy)</li>
* </ul>
*/
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 <code>render</code>, directly after
* <code>context.drawTriangles</code>. 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 <code>Painter</code> and re-used by all instances of this effect.
*
* <p>The basic implementation always outputs pure white.</p>
*/
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
* <code>Painter</code>.
*
* <p>The default implementation efficiently combines the program's base and variant
* names (e.g. <code>LightEffect#42</code>). It shouldn't be necessary to override
* this method.</p>
*/
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
* <code>createProgram</code>) or by getting it from the <code>Painter</code>.
* Do not override this method! Instead, implement <code>createProgram</code>. */
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:
* <code>"position:float2"</code> */
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; }
}
}

View file

@ -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.
*
* <p>For more information about the usage and creation of effects, please have a look at
* the documentation of the parent class, "Effect".</p>
*
* @see Effect
* @see MeshEffect
* @see starling.filters.FragmentFilter
*/
public class FilterEffect extends Effect
{
/** The vertex format expected by <code>uploadVertexData</code>:
* <code>"position:float2, texCoords:float2"</code> */
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.
*
* <p>Reserve 4 bits for the variant name of the base class.</p>
*/
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 <code>render</code>, directly before
* <code>context.drawTriangles</code>. It activates the program and sets up
* the context with the following constants and attributes:
*
* <ul>
* <li><code>vc0-vc3</code> MVP matrix</li>
* <li><code>va0</code> vertex position (xy)</li>
* <li><code>va1</code> texture coordinates (uv)</li>
* <li><code>fs0</code> texture</li>
* </ul>
*/
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 <code>render</code>, directly after
* <code>context.drawTriangles</code>. 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 <code>tex</code> 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:
* <code>"position:float2, texCoords:float2"</code> */
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; }
}
}

View file

@ -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. <em>You only have to work with this class if you're writing
* your own rendering code (e.g. if you create custom display objects).</em>
*
* <p>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.</p>
*
* <p>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.</p>
*
* <strong>Basic Quad Layout</strong>
*
* <p>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!</p>
*
* <p>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:</p>
*
* <pre>n, n+1, n+2, n+1, n+3, n+2</pre>
*
* <p>The subsequent quad has to use <code>n+4</code> as starting value, the next one
* <code>n+8</code>, etc. Here is an example with 3 quads / 6 triangles:</p>
*
* <pre>0, 1, 2, 1, 3, 2, 4, 5, 6, 5, 7, 6, 8, 9, 10, 9, 11, 10</pre>
*
* <p>If you are describing quad-like meshes, make sure to always use this layout.</p>
*
* @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.<uint> = new <uint>[];
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
* <code>numIndices</code> 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:
*
* <p>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!</p>
*
* <p>Thus, be sure to always make a generous educated guess, depending on the planned
* usage of your IndexData instances.</p>
*/
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.
*
* <p>By passing a non-zero <code>offset</code>, you can raise all copied indices
* by that value in the target object.</p>
*/
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<numIndices; ++i)
keepsQuadLayout &&=
getBasicQuadIndexAt(indexID + i) + offset ==
getBasicQuadIndexAt(targetIndexID + i);
}
if (keepsQuadLayout) return;
else target.switchToGenericData();
}
sourceData = sQuadData;
targetData = target._rawData;
if ((offset & 3) == 0) // => 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<endIndex; ++i)
setIndex(i, getIndex(i) + offset);
}
/** Appends three indices representing a triangle. Reference the vertices clockwise,
* as this defines the front side of the triangle. */
public function addTriangle(a:uint, b:uint, c:uint):void
{
if (_useQuadLayout)
{
if (a == getBasicQuadIndexAt(_numIndices))
{
var oddTriangleID:Boolean = (_numIndices & 1) != 0;
var evenTriangleID:Boolean = !oddTriangleID;
if ((evenTriangleID && b == a + 1 && c == b + 1) ||
(oddTriangleID && c == a + 1 && b == c + 1))
{
_numIndices += 3;
ensureQuadDataCapacity(_numIndices);
return;
}
}
switchToGenericData();
}
_rawData.position = _numIndices * INDEX_SIZE;
_rawData.writeShort(a);
_rawData.writeShort(b);
_rawData.writeShort(c);
_numIndices += 3;
}
/** Appends two triangles spawning up the quad with the given indices.
* The indices of the vertices are arranged like this:
*
* <pre>
* a - b
* | / |
* c - d
* </pre>
*
* <p>To make sure the indices will follow the basic quad layout, make sure each
* parameter increments the one before it (e.g. <code>0, 1, 2, 3</code>).</p>
*/
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.<uint>=null):Vector.<uint>
{
if (out == null) out = new Vector.<uint>(_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 <code>numIndices</code> 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.
*
* <p>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.</p>
*
* <p>If you set the number of indices to zero, quad layout will be restored.</p> */
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.
*
* <p>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 <code>clear</code> or
* manually enabling the property will restore quad layout.</p>
*
* <p>If you enable this property on an instance, all indices will immediately be
* replaced with indices following standard quad layout.</p>
*
* <p>Please look at the class documentation for more information about that kind
* of layout, and why it is important.</p>
*
* @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;
}
}
}

View file

@ -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.
*
* <p>For more information about the usage and creation of effects, please have a look at
* the documentation of the root class, "Effect".</p>
*
* @see Effect
* @see FilterEffect
* @see starling.styles.MeshStyle
*/
public class MeshEffect extends FilterEffect
{
/** The vertex format expected by <code>uploadVertexData</code>:
* <code>"position:float2, texCoords:float2, color:bytes4"</code> */
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.<Number> = new Vector.<Number>(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 <code>render</code>, directly before
* <code>context.drawTriangles</code>. It activates the program and sets up
* the context with the following constants and attributes:
*
* <ul>
* <li><code>vc0-vc3</code> MVP matrix</li>
* <li><code>vc4</code> alpha value (same value for all components)</li>
* <li><code>va0</code> vertex position (xy)</li>
* <li><code>va1</code> texture coordinates (uv)</li>
* <li><code>va2</code> vertex color (rgba), using premultiplied alpha</li>
* <li><code>fs0</code> texture</li>
* </ul>
*/
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 <code>render</code>, directly after
* <code>context.drawTriangles</code>. 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:
* <code>"position:float2, texCoords:float2, color:bytes4"</code> */
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 <code>MeshEffect</code>
* 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; }
}
}

View file

@ -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.
*
* <p>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 <code>Starling.painter</code>.</p>
*
* <p>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.</p>
*
* <strong>The State Stack</strong>
*
* <p>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 <code>state</code> property. Use the methods
* <code>pushState</code> and <code>popState</code> to store a specific state and restore
* it later. That makes it easy to write rendering code that doesn't have any side effects.</p>
*
* <listing>
* 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</listing>
*
* @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.<Rectangle>;
private var _batchCacheExclusions:Vector.<DisplayObject>;
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.<RenderState>;
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 <Rectangle>[];
_batchProcessorCurr = new BatchProcessor();
_batchProcessorCurr.onBatchComplete = drawBatch;
_batchProcessorPrev = new BatchProcessor();
_batchProcessorPrev.onBatchComplete = drawBatch;
_batchProcessorSpec = new BatchProcessor();
_batchProcessorSpec.onBatchComplete = drawBatch;
_batchProcessor = _batchProcessorCurr;
_batchCacheExclusions = new Vector.<DisplayObject>();
_state = new RenderState();
_state.onDrawRequired = finishMeshBatch;
_stateStack = new <RenderState>[];
_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.
*
* <p>Beware: if <code>shareContext</code> 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.</p>
*
* @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.
*
* <p>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.</p>
*/
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 <code>modelviewMatrix</code>.
* @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.
*
* <p>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.</p>
*/
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.
*
* <p>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.</p>
*
* <p>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 <code>starling.display.Quad</code> and is aligned parallel to the stage
* axes.</p>
*
* <p>Note that masking breaks the render cache; the masked object must be redrawn anew
* in the next frame. If you pass <code>maskee</code>, the method will automatically
* call <code>excludeFromCache(maskee)</code> for you.</p>
*/
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.
*
* <p>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 <code>drawMask()</code> call.</p>
*/
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.<Rectangle> = _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.<Rectangle> = _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 <code>null</code>, 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; i<length; ++i) _batchCacheExclusions[i].excludeFromCache();
_batchCacheExclusions.length = 0;
}
/** Resets the current state, state stack, batch processor, stencil reference value,
* clipping rectangle, and draw count. Furthermore, depth testing is disabled. */
public function nextFrame():void
{
// update batch processors
_batchProcessor = swapBatchProcessors();
_batchProcessor.clear();
_batchProcessorSpec.clear();
// enforce reset of basic context settings
_actualBlendMode = null;
_actualCulling = null;
_context.setDepthTest(false, Context3DCompareMode.ALWAYS);
// reset everything else
stencilReferenceValue = 0;
_clipRectStack.length = 0;
_drawCount = 0;
_stateStackPos = -1;
_state.reset();
}
private function swapBatchProcessors():BatchProcessor
{
var tmp:BatchProcessor = _batchProcessorPrev;
_batchProcessorPrev = _batchProcessorCurr;
return _batchProcessorCurr = tmp;
}
/** Draws all meshes from the render cache between <code>startToken</code> and
* (but not including) <code>endToken</code>. 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 <code>setRequiresRedraw()</code>, this does not indicate that the object
* has changed in any way, but just that it doesn't support being drawn from cache.
*
* <p>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.</p>
*/
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
* <code>context.drawTriangles()</code>.
*/
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".
*
* <p>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.</p>
*/
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 <strong>if</strong> the render cache is enabled;
* otherwise, returns zero. To get the frameID regardless of the render cache, call
* <code>Starling.frameID</code> 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 <code>null</code>
* 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;
}
}
}

View file

@ -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.
*
* <p>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.</p>
*
* <p>It is recommended to store programs in Starling's "Painter" instance via the methods
* <code>registerProgram</code> and <code>getProgram</code>. That way, your programs may
* be shared among different display objects or even Starling instances.</p>
*
* @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;
}
}
}
}

View file

@ -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.
*
* <p>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.</p>
*
* <p>Beware that any context-related settings are not applied on the context
* right away, but only after calling <code>painter.prepareToDraw()</code>.
* However, the Painter recognizes changes to those settings and will finish the current
* batch right away if necessary.</p>
*
* <strong>Matrix Magic</strong>
*
* <p>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.</p>
*
* <p>By multiplying vertex coordinates with the <code>modelviewMatrix</code>, 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
* <code>Sprite3D</code> containers.)</p>
*
* <p>By feeding the result of the previous transformation into the
* <code>projectionMatrix</code>, you'll end up with so-called "clipping coordinates",
* which are in the range <code>[-1, 1]</code> (just as needed by the graphics pipeline).
* If you've got vertices in the 3D space, this matrix will also execute a perspective
* projection.</p>
*
* <p>Finally, there's the <code>mvpMatrix</code>, which is short for
* "modelviewProjectionMatrix". This is simply a combination of <code>modelview-</code> and
* <code>projectionMatrix</code>, combining the effects of both. Pass this matrix
* to the vertex shader and all your vertices will automatically end up at the right
* position.</p>
*
* @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.<String> = new <String>
[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.
*
* <p>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.</p>
*
* <p>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.</p>
*
* <p>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.</p>
*/
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 <code>projectionMatrix3D</code> was changed
* other than via <code>setProjectionMatrix</code>.
*/
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 <code>setProjectionMatrixChanged</code>.
* @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 <code>null</code> 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: <code>0 - 16</code>).
* 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 <code>null</code>
* to render into the back buffer. On assignment, calls <code>setRenderTarget</code>
* 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 <code>null</code>.
*
* @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 <code>setRenderTarget</code>. */
public function get renderTargetAntiAlias():int
{
return _miscOptions & 0xf;
}
/** Indicates if the render target (set via <code>setRenderTarget</code>)
* 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 <code>true</code> 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; }
}
}

File diff suppressed because it is too large Load diff

View file

@ -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 <code>public</code>
* 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
}
}
}

View file

@ -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.
*
* <p>The format is set up via a simple String. Here is an example:</p>
*
* <listing>
* format = VertexDataFormat.fromString("position:float2, color:bytes4");</listing>
*
* <p>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
* <code>Context3DVertexBufferFormat</code> class:
* <code>float1, float2, float3, float4, bytes4</code>.)</p>
*
* <p>You cannot create a VertexData instance with its constructor; instead, you must use the
* static <code>fromString</code>-method. The reason for this behavior: the class maintains
* a cache, and a call to <code>fromString</code> will return an existing instance if an
* equivalent format has already been created in the past. That saves processing time and
* memory.</p>
*
* <p>VertexDataFormat instances are immutable, i.e. they are solely defined by their format
* string and cannot be changed later.</p>
*
* @see VertexData
*/
public class VertexDataFormat
{
private var _format:String;
private var _vertexSize:int;
private var _attributes:Vector.<VertexDataAttribute>;
// format cache
private static var sFormats:Dictionary = new Dictionary();
/** Don't use the constructor, but call <code>VertexDataFormat.fromString</code> instead.
* This allows for efficient format caching. */
public function VertexDataFormat()
{
_attributes = new Vector.<VertexDataAttribute>();
}
/** 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.:
*
* <pre>"position:float2, texCoords:float2, color:bytes4"</pre>
*
* <p>This set of attributes will be allocated for each vertex, and they will be
* stored in exactly the given order.</p>
*
* <ul>
* <li>Names are used to access the specific attributes of a vertex. They are
* completely arbitrary.</li>
* <li>The available formats can be found in the <code>Context3DVertexBufferFormat</code>
* class in the <code>flash.display3D</code> package.</li>
* <li>Both names and format strings are case-sensitive.</li>
* <li>Always use <code>bytes4</code> for color data that you want to access with the
* respective methods.</li>
* <li>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.</li>
* </ul>
*/
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: <code>float1, float2, float3, float4, bytes4</code>. */
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; i<numAttributes; ++i)
if (_attributes[i].name == attrName) return true;
return false;
}
// context methods
/** Specifies which vertex data attribute corresponds to a single vertex shader
* program input. This wraps the <code>Context3D</code>-method with the same name,
* automatically replacing <code>attrName</code> with the corresponding values for
* <code>bufferOffset</code> and <code>format</code>. */
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<numParts; ++i)
{
var attrDesc:String = parts[i];
var attrParts:Array = attrDesc.split(":");
if (attrParts.length != 2)
throw new ArgumentError("Missing colon: " + attrDesc);
var attrName:String = StringUtil.trim(attrParts[0]);
var attrFormat:String = StringUtil.trim(attrParts[1]);
if (attrName.length == 0 || attrFormat.length == 0)
throw new ArgumentError(("Invalid format string: " + attrDesc));
var attribute:VertexDataAttribute =
new VertexDataAttribute(attrName, attrFormat, offset);
offset += attribute.size;
_format += (i == 0 ? "" : ", ") + attribute.name + ":" + attribute.format;
_attributes[_attributes.length] = attribute; // avoid 'push'
}
_vertexSize = offset;
}
else
{
_format = "";
}
}
/** Returns the normalized format string. */
public function toString():String
{
return _format;
}
// internal methods
/** @private */
internal function getAttribute(attrName:String):VertexDataAttribute
{
var i:int, attribute:VertexDataAttribute;
var numAttributes:int = _attributes.length;
for (i=0; i<numAttributes; ++i)
{
attribute = _attributes[i];
if (attribute.name == attrName) return attribute;
}
return null;
}
/** @private */
internal function get attributes():Vector.<VertexDataAttribute>
{
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;
}
}
}

Some files were not shown because too many files have changed in this diff Show more