scaffold.libs_as.starling.display.Sprite3D.as Maven / Gradle / Ivy
// =================================================================================================
//
// Starling Framework
// Copyright 2011-2015 Gamua. All Rights Reserved.
//
// This program is free software. You can redistribute and/or modify it
// in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
package starling.display
{
import flash.geom.Matrix;
import flash.geom.Matrix3D;
import flash.geom.Point;
import flash.geom.Vector3D;
import starling.events.Event;
import starling.rendering.Painter;
import starling.utils.MathUtil;
import starling.utils.MatrixUtil;
import starling.utils.rad2deg;
/** A container that allows you to position objects in three-dimensional space.
*
* Starling is, at its heart, a 2D engine. However, sometimes, simple 3D effects are
* useful for special effects, e.g. for screen transitions or to turn playing cards
* realistically. This class makes it possible to create such 3D effects.
*
* Positioning objects in 3D
*
* Just like a normal sprite, you can add and remove children to this container, which
* allows you to group several display objects together. In addition to that, Sprite3D
* adds some interesting properties:
*
*
* - z - Moves the sprite closer to / further away from the camera.
* - rotationX — Rotates the sprite around the x-axis.
* - rotationY — Rotates the sprite around the y-axis.
* - scaleZ - Scales the sprite along the z-axis.
* - pivotZ - Moves the pivot point along the z-axis.
*
*
* With the help of these properties, you can move a sprite and all its children in the
* 3D space. By nesting several Sprite3D containers, it's even possible to construct simple
* volumetric objects (like a cube).
*
* Note that Starling does not make any z-tests: visibility is solely established by the
* order of the children, just as with 2D objects.
*
* Setting up the camera
*
* The camera settings are found directly on the stage. Modify the 'focalLength' or
* 'fieldOfView' properties to change the distance between stage and camera; use the
* 'projectionOffset' to move it to a different position.
*
* Limitations
*
* On rendering, each Sprite3D requires its own draw call — except if the object does not
* contain any 3D transformations ('z', 'rotationX/Y' and 'pivotZ' are zero). Furthermore,
* it interrupts the render cache, i.e. the cache cannot contain objects within different
* 3D coordinate systems. Flat contents within the Sprite3D will be cached, though.
*
*/
public class Sprite3D extends DisplayObjectContainer
{
private static const E:Number = 0.00001;
private var _rotationX:Number;
private var _rotationY:Number;
private var _scaleZ:Number;
private var _pivotZ:Number;
private var _z:Number;
private var _transformationMatrix:Matrix;
private var _transformationMatrix3D:Matrix3D;
private var _transformationChanged:Boolean;
private var _is2D:Boolean;
/** Helper objects. */
private static var sHelperPoint:Vector3D = new Vector3D();
private static var sHelperPointAlt:Vector3D = new Vector3D();
private static var sHelperMatrix:Matrix3D = new Matrix3D();
/** Creates an empty Sprite3D. */
public function Sprite3D()
{
_scaleZ = 1.0;
_rotationX = _rotationY = _pivotZ = _z = 0.0;
_transformationMatrix = new Matrix();
_transformationMatrix3D = new Matrix3D();
_is2D = true; // meaning: this 3D object contains only 2D content
setIs3D(true); // meaning: this display object supports 3D transformations
addEventListener(Event.ADDED, onAddedChild);
addEventListener(Event.REMOVED, onRemovedChild);
}
/** @inheritDoc */
public override 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.popState();
}
}
/** @inheritDoc */
public override 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);
}
}
public override function setRequiresRedraw():void
{
var was2D:Boolean = _is2D;
_is2D = _z > -E && _z < E &&
_rotationX > -E && _rotationX < E &&
_rotationY > -E && _rotationY < E &&
_pivotZ > -E && _pivotZ < E;
if (_is2D != was2D) updateSupportsRenderCache();
super.setRequiresRedraw();
}
protected override function get supportsRenderCache():Boolean
{
return _is2D && super.supportsRenderCache;
}
// 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