Two Sided Plane – FP 10
Posted by Jesse Freeman | Filed under Code
Since Flash Player 10 was only officially released a month or so ago its hard to dig up any good examples on how to use the new 3d properties on DisplayObjects. I was interested in creating a simple 2 sided plane that would correctly show the front and back when it rotated. My searches led me once again to Lee Brimelow’s http://gotoandlearn.com/. He has a great example on how to do a simple 3D Video Flip. I find his site is always an excellent springboard for my code ideas but I am not a fan of using the Flash IDE. So here is an reusable class of a two sided plane that takes advantage of Flash Player 10’s native 3d Support.
You will need FlashPlayer 10 installed to see the above example. Click on the plane to make it flip.
Creating these kind flipping planes is old hat to me now, I used it all over my FlashBum website but now with Flash Player 10 the hardest part (the perspective math) is taken care of. This only leaves the logic to display the correct side of the plane. Here is the code:
{
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.events.Event;
import flash.utils.Dictionary;
public class TwoSidedPlane extends Sprite
{
public static const FRONT:String = "front";
public static const BACK:String = "back";
public static const FRONT_ROTATION_Y:Number = 0;
public static const BACK_ROTATION_Y:Number = 180;
protected var _sideInstances:Dictionary;
protected var _targetRotationY:Number = 0;
protected var _flipSpeed:Number = 1;
protected var _flipTime:Number = 5;
protected var _flipped:Boolean;
protected var _debug:Boolean = true;
protected var _rotating:Boolean;
protected var _autoRotateSpeed:Number;
protected var _registrationPoint:Sprite;
/**
* <p>By overriding the rotationY setter we can test for when to swap
* the side images. This removes the need for having a EnterFrame loop
* testing for the rotation and helps optimize transition.</p>
*
* @param value
*
*/
override public function set rotationY(value:Number):void{
super.rotationY = value;
if(rotationY >= 90 && rotationY < = 270){
if(_sideInstances[FRONT].stage) removeChild(_sideInstances[FRONT]);
addChild(_sideInstances[BACK]);
_flipped = false;
}else{
addChild(_sideInstances[FRONT]);
if(_sideInstances[BACK].stage) removeChild(_sideInstances[BACK]);
_flipped = true;
}
if(rotationY >= 360) rotationY = 0;
if(_debug && _registrationPoint)
addChild(_registrationPoint);
}
/**
* <p>Simple two sided plane then manages depth sorting of its front and
* back asset.</p>
*
* @param front: DisplayObject used for front side.
* @param back: DisplayObject used for back side.
*
*/
public function TwoSidedPlane(front:DisplayObject, back:DisplayObject)
{
init(front,back);
}
/**
* <p>This sets up the front and back, flips the back image horizontally
* so it doesn’t appear reversed, and recent all the sides do the
* registration mark is in the center of the plane.</p>
*
* @param front: DisplayObject used for front side.
* @param back: DisplayObject used for back side.
*
*/
protected function init(front:DisplayObject, back:DisplayObject):void{
_sideInstances = new Dictionary();
// Set up front image
_sideInstances[FRONT] = front;
_sideInstances[FRONT].x = -50;
_sideInstances[FRONT].y = -50;
// Set up back image
back.scaleX = – 1;
_sideInstances[BACK] = back;
_sideInstances[BACK].x = 50;
_sideInstances[BACK].y = -50;
rotationY = 0;
if(_debug)
showRegistrationPoint();
}
/**
* <p>Handles setting a target y for the rotation and starting the onFlip
* loop.</p>
*
*/
public function flip():void{
// Makes sure that the plane isn’t already rotating, if so it gets
// stopped.
if(_rotating)
stopAutoRotate();
// Detect what side is currently showing and set the target y for
// the opposite side
if(_flipped)
_targetRotationY = BACK_ROTATION_Y;
else
_targetRotationY = FRONT_ROTATION_Y;
// Add the EnterFrame loop to calculate the flip
addEventListener(Event.ENTER_FRAME,onFlip);
}
/**
* <p>This calculates the difference between the target_y and the actual
* rotationY of the plane then eases based on the time and speed.</p>
*
* <p>When the rotationY is equal to the _targetRotationY the Enterframe
* loop is removed.</p>
*
* @param e
*
*/
internal function onFlip(e:Event):void{
var c:Number = _targetRotationY – rotationY;
var t:Number = _flipSpeed;
var d:Number = _flipTime;
var b:Number = rotationY;
rotationY = Number(c * Math.sin(t/d * (Math.PI/2)) + b);
if(rotationY == _targetRotationY)
removeEventListener(Event.ENTER_FRAME,onFlip);
}
/**
* <p>Autorotates the pane around its Y access.</p>
*
* @param speed: Number representing the speed of the rotation
*
*/
public function autoRotate(speed:Number = 1):void{
_autoRotateSpeed = speed;
_rotating = true;
addEventListener(Event.ENTER_FRAME,onAutoRotateEnterframe);
}
/**
* <p>Loop that handles the Rotation animation.</p>
*
* @param e
*
*/
internal function onAutoRotateEnterframe(e:Event):void{
rotationY += _autoRotateSpeed;
}
/**
* <p>Kills the autoRotation loop.</p>
*
*/
public function stopAutoRotate():void{
_rotating = false;
removeEventListener(Event.ENTER_FRAME,onAutoRotateEnterframe);
}
/**
* <p>This is for debug only and shows an X to mark the registration point
* of the plane. This can be helpful for positioning.</p>
*
*/
internal function showRegistrationPoint():void{
_registrationPoint = new Sprite();
_registrationPoint.graphics.lineStyle(1);
_registrationPoint.graphics.moveTo(-5,0);
_registrationPoint.graphics.lineTo(5,0);
_registrationPoint.graphics.moveTo(0,-5);
_registrationPoint.graphics.lineTo(0,5);
addChild(_registrationPoint);
}
}
}
This may look like a lot of code but its really simple. Lets start with the basics, this class needs two sides a front and a back. Once you pass those in the TwoSidedPlane handles the rest. I commented the code so there isn’t really any point to going line by line through this but let me highlight some of the key principles and functionality.
So there are two ways to “flip” this plane, the first is the flip method and the second is to call the autoRotate method. Calling the flip() will set the target rotationY to the front (0 degrees) or back (180 degrees); yeah I know math sucks. From there we call an EnterFrame loop that calculates the different in the rotationY with the targetRotationY and changes the value accordingly.
var c:Number = _targetRotationY – rotationY;
var t:Number = _flipSpeed;
var d:Number = _flipTime;
var b:Number = rotationY;
rotationY = Number(c * Math.sin(t/d * (Math.PI/2)) + b);
if(rotationY == _targetRotationY)
removeEventListener(Event.ENTER_FRAME,onFlip);
}
Now why am I using this crazy looking function and not Tweener or the built in Tween functions? Lately I have been trying to get a better understanding of the math behind what I take for granted so I have been directly using Rober Pener’s Easing Equations. The other thing I am achieving is to decouple any unneeded dependancies on other people’s code and through encapsulation achieving OOP code. Plus why would I import Tweener (don’t get me wrong I love this library) when all I need is a simple easing equation? One last thing is I get to control exactly how this works and what happens on every frame of the animation. This is key to understanding what is really going on as well as giving you the most granular control over your code. So this about covers the flip() lets look at the autoRotation.
I threw in the AutoRotation as an easy test to make sure the correct side was getting shown. Its really simple, autoRotation just adds the speed onto the rotationY and animations the Plane. Here is the loop I use:
_autoRotateSpeed = speed;
_rotating = true;
addEventListener(Event.ENTER_FRAME,onAutoRotateEnterframe);
}
So now onto displaying the correct side. People, including myself, are making a big deal about Flash Player 10’s 3d support. Well I was a little unhappy to discover that it really isn’t 3d, its more like 2.5d. Whats the difference? Well in traditional 3d engines depth sorting is handles by the engine, in our case the Flash Player. Well in Flash Player 10 there is no depth sorting of 3d objects. You are on your own to create that logic. Here is a post that does a better job of demonstrating what I am talking about. So with the limitations in mind let see how we get around it.
In Lee Brimelow’s 3D Video Flip example he uses an enterframe to calculate when to sort the sides of the plane. That works well but I prefer to actually override the base code of the rotationY setter and add my sorting logic there. Here is the code:
super.rotationY = value;
if(rotationY >= 90 && rotationY < = 270){
if(_sideInstances[FRONT].stage) removeChild(_sideInstances[FRONT]);
addChild(_sideInstances[BACK]);
_flipped = false;
}else{
addChild(_sideInstances[FRONT]);
if(_sideInstances[BACK].stage) removeChild(_sideInstances[BACK]);
_flipped = true;
}
if(rotationY >= 360) rotationY = 0;
if(_debug && _registrationPoint)
addChild(_registrationPoint);
}
There is a really good reason why I put the logic on the setter, this instance can now automatically manage it’s display. Lets say you didn’t want to have an enterFrame constantly running, like if you have 30 of these on the stage, so that wouldn’t be practical to let them all be running that loop. By overriding the rotationY setter I can now tell any of the instance to rotate and they will automatically display the correct side. This is one of my biggest beefs with putting code on the timeline in the IDE because you lose the ability to override core functions of your classes. I use this technique all over my code, especially in masked off DisplayObjects. When you mask something off and try to get its width the full size of the object will be returned and not just the visible area. I usually override the get width function and have it return the width from the mask. You should give it a try, its a life saver.
Before I let you go here is my doc class that I used to test out the two sided plane:
import com.jessefreeman.plane.TwoSidedPlane;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.MouseEvent;
public class TwoSidedPlaneTest extends Sprite
{
/**
* <p>This is the main test function for this app. It creates two Sprites
* (front and back) then passes them into the TwoSidedPlane.</p>
*
* <p>Finally we call the autoRotate function to make the plane spine in
* 3d space;</p>
*
*/
public function TwoSidedPlaneTest()
{
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
// Create Sprite for front
var front:Sprite = new Sprite();
front.graphics.beginFill(0xff0000);
front.graphics.drawRect(0,0,100,100);
front.graphics.endFill();
// Create Sprite for back
var back:Sprite = new Sprite();
back.graphics.beginFill(0×0000ff);
back.graphics.drawRect(0,0,100,100);
back.graphics.endFill();
// Create TwoSidedPlane
var tempPlane:TwoSidedPlane = new TwoSidedPlane(front, back);
tempPlane.addEventListener(MouseEvent.CLICK, onClick);
tempPlane.mouseChildren = false;
// Add to display
addChild(tempPlane);
tempPlane.x = (stage.stageWidth * .5) – (tempPlane.width * .5);
tempPlane.y = (stage.stageHeight * .5) – (tempPlane.height * .5);
// Start plane’s auto rotation
tempPlane.autoRotate(5);
}
/**
* <p>Listens for a click event from the tempPlane and calls it’s flip
* function.</p>
*
* @param e
*
*/
internal function onClick(e:MouseEvent):void{
e.target.flip();
}
}
}
So this covers the basics of how to create a two sided plane that can be flipped in 3d space. Feel free to use this code as you see fit and make sure you add a comment with a link if you use it anywhere.
-
AJ
-
Luca
-
Justin












