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

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