3D Box Transition Effect in Flash Player 10

Playing with another transition effect made possible by the 3d capabilities of Flash Player 10, gave rise to the example below.

The initial object seen is just a quickly thrown together MovieClip to demonstrate that interactive items can be transited as well as static images. After that first mc, though, all you’ll see is a bunch of random movie posters.

[kml_flashembed movie=”http://blog.onebyonedesign.com/wp-content/uploads/2008/08/boxtrans.swf” height=”400″ width=”500″ /]

Because I added a new transition to my transitions package, I touched up the OBO_BillboardTransition class a little bit and made both implement an ITransition interface. If interested, all script is below:

The interface:

package com.onebyonedesign.transitions {
	
	import flash.display.DisplayObject;
	
	public interface ITransition  {
		function start():void;
		function get direction():String;
		function set direction(value:String):void;
		function get currentimage():DisplayObject;
		function set currentimage(value:DisplayObject):void;
		function get newimage():DisplayObject;
		function set newimage(value:DisplayObject):void;
		function set time(value:Number):void;
		function get time():Number;
	}
}

The OBO_BoxTransition class:

package com.onebyonedesign.transitions {
	
	import caurina.transitions.Tweener;
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	
	/**
	* 3D "Box like" transition between to images, sprites, or movieclips.
	* Throws Event.COMPLETE when transition is finished.
	* Requires caurina.transitions package.
	* @author Devon O.
	*/
	public class OBO_BoxTransition extends EventDispatcher implements ITransition {
		
		public static const UP:String = "up";
		public static const DOWN:String = "down";
		public static const LEFT:String = "left";
		public static const RIGHT:String = "right";
		
		private var _direction:String;
		private var _currentimage:DisplayObject;
		private var _newimage:DisplayObject;
		private var _time:Number;
		
		private var _imgparent:DisplayObjectContainer;
		private var _box:Sprite;
		private var _orgIndex:int;
		private var _orgY:Number;
		private var _orgX:Number;
		
		/**
		 * 
		 * @param	image that will be transitioned out (must already be on display list).
		 * @param	image that will be transitioned in
		 * @param	direction in which the out image should travel.
		 * 			Choices are OBO_BoxTransition.LEFT, OBO_BoxTransition.RIGHT, OBO_BoxTransition.UP, or OBO_BoxTransition.DOWN
		 * @param	time in seconds transition should take place.
		 */
		public function OBO_BoxTransition(currentimage:DisplayObject, newimage:DisplayObject, direction:String = "left", time:Number = 1) {
			_imgparent = currentimage.parent;
			_orgX = currentimage.x;
			_orgY = currentimage.y;
			_currentimage = currentimage;
			_newimage = newimage;
			_time = time;
			_direction = direction;
		}
		
		public function start():void {
			init();
			beginTransition();
		}
		
		public function get direction():String { return _direction; }
		
		public function set direction(value:String):void {
			_direction = value;
		}
		
		public function get currentimage():DisplayObject { return _currentimage; }
		
		public function set currentimage(value:DisplayObject):void {
			_currentimage = value;
		}
		
		public function get newimage():DisplayObject { return _newimage; }
		
		public function set newimage(value:DisplayObject):void {
			_newimage = value;
		}
		
		public function get time():Number { return _time; }
		
		public function set time(value:Number):void {
			_time = value;
		}
		
		private function init():void {
			var w:int = _currentimage.width * .5;
			var h:int = _currentimage.height * .5;
			_orgIndex = _imgparent.getChildIndex(_currentimage);
			_box = new Sprite();
			_box.x = _currentimage.x;
			_box.y = _currentimage.y;
			_imgparent.addChildAt(_box, _orgIndex);
			_box.x += _currentimage.width * .5;
			_box.y += _currentimage.height * .5;
			_currentimage.y = -_currentimage.height * .5;
			_currentimage.x = -_currentimage.width * .5;
			
			switch(_direction) {
				case "up" :
					_box["z"] = h;
					_currentimage["z"] -= h;
					_newimage.x = _currentimage.x;
					_newimage.y = _currentimage.y + (h * 2);
					_newimage["rotationX"] = 90;
					_newimage["z"] -= h;
					_box.addChild(_newimage);
					break;
				case "down" :
					_box["z"] = h;
					_currentimage["z"] -= h;	
					_newimage.x = _currentimage.x;
					_newimage.y = _currentimage.y;
					_newimage["rotationX"] = -90;
					_newimage["z"] += h;
					_box.addChild(_newimage);
					break;
				case "left" :
					_box["z"] = w;
					_currentimage["z"] -= w;
					_newimage.x = _currentimage.x + (w * 2);
					_newimage.y = _currentimage.y;
					_newimage["rotationY"] = -90;
					_newimage["z"] -= w;
					_box.addChild(_newimage);
					break;
				case "right" :
					_box["z"] = w;
					_currentimage["z"] -= w;
					_newimage.x = _currentimage.x;
					_newimage.y = _currentimage.y;
					_newimage["rotationY"] = 90;
					_newimage["z"] += w;
					_box.addChild(_newimage);
					break;
			}
			
			_box.addChild(_currentimage);
		}
		
		private function beginTransition():void {
			switch(_direction) {
				case "up" :
					flipUp();
					break;
				case "down" :
					flipDown();
					break;
				case "left" :
					flipLeft();
					break;
				case "right" :
					flipRight();
					break;
			}
		}
		
		private function flipLeft():void {
			Tweener.addTween(_box, { rotationY:90, time:_time, transition:"easeOutQuad", onUpdate:checkAngle, onComplete:transitionDone } );
		}
		
		private function flipRight():void {
			Tweener.addTween(_box, { rotationY:-90, time:_time, transition:"easeOutQuad", onUpdate:checkAngle, onComplete:transitionDone } );
		}
		
		private function flipUp():void {
			Tweener.addTween(_box, { rotationX:-90, time:_time, transition:"easeOutQuad", onUpdate:checkAngle, onComplete:transitionDone } );
		}
		
		private function flipDown():void {
			Tweener.addTween(_box, { rotationX:90, time:_time, transition:"easeOutQuad", onUpdate:checkAngle, onComplete:transitionDone } );
		}
		
		private function checkAngle():void {
			if (Math.abs(_box["rotationY"]) > 75 || Math.abs(_box["rotationX"]) > 75) {
				_currentimage.visible = false;
			}
		}
		
		private function transitionDone():void {
			_imgparent.removeChild(_box);
			_box = null;
			_newimage.x = _orgX;
			_newimage.y = _orgY;
			_newimage["z"] = 0;
			_newimage["rotationY"] = 0;
			_newimage["rotationX"] = 0;
			_imgparent.addChildAt(_newimage, _orgIndex);
			dispatchEvent(new Event(Event.COMPLETE));
		}
	}	
}

The OBO_BillboardTransition class (same effect as my last post, so no need for a demo .swf):

package com.onebyonedesign.transitions {

	import caurina.transitions.Tweener;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.TimerEvent;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.Timer;
	
	/**
	* @author Devon O.
	*/
	public class OBO_BillboardTransition extends EventDispatcher implements ITransition {
		
		public static const HORIZONTAL:String = "horizontal";
		public static const VERTICAL:String = "vertical";
		
		private var _imgparent:DisplayObjectContainer;
		private var _timer:Timer;
		
		private var _direction:String;
		private var _currentimage:DisplayObject;
		private var _newimage:DisplayObject;
		private var _time:Number;
		private var _numsections:int;
		
		private var _sections:Vector.;
		private var _bmpdataObjects:Vector.;
		private var _bmpObjects:Vector.;
		private var _numDone:int;
		private var _sectionHolder:Sprite = new Sprite();
		
		/**
		 * OBO_BillboardTransition class. Transitions two DisplayObject instances in a "spinning billboard" fashion.
		 * Throws Event.COMPLETE when transition is finished.
		 * Requires caurina.transitions package. Google for "Tweener" on GoogleCode.
		 * 
		 * @param	The DisplayObjectContainer parenting the image that will be transitioned out.
		 * @param	The image to be transitioned out (must already be on display list)
		 * @param	The image that will be transitioned in (should be same size as currentimage)
		 * @param	The number of spinning sections
		 * @param	Either OBO_BillboardTransition.HORIZONTAL or OBO_BillboardTransition.VERTICAL ("horizontal" or "vertical")
		 */
		public function OBO_BillboardTransition(currentimage:DisplayObject, newimage:DisplayObject, numsections:int, direction:String = "horizontal", time:Number = .25) {
			_imgparent = currentimage.parent;
			_currentimage = currentimage;
			_newimage = newimage;
			_numsections = numsections;
			_time = time;
			_direction = direction;
		}
		
		/**
		 * Call this to begin the transition.
		 */
		public function start():void {
			init();
			_timer = new Timer(100, _numsections);
			_timer.addEventListener(TimerEvent.TIMER, timerHandler);
			_timer.start();
		}
		
		public function get direction():String { return _direction; }
		
		public function set direction(value:String):void {
			_direction = value;
		}
		
		public function get currentimage():DisplayObject { return _currentimage; }
		
		public function set currentimage(value:DisplayObject):void {
			_currentimage = value;
		}
		
		public function get newimage():DisplayObject { return _newimage; }
		
		public function set newimage(value:DisplayObject):void {
			_newimage = value;
		}
		
		public function get time():Number { return _time; }
		
		public function set time(value:Number):void {
			_time = value;
		}
		
		public function get numsections():int { return _numsections; }
		
		public function set numsections(value:int):void {
			_numsections = value;
		}
		
		private function init():void {
			_numDone = 0;
			_sections = new Vector.();
			_bmpdataObjects = new Vector.();
			_bmpObjects = new Vector.();
			_sectionHolder = new Sprite();
			
			switch(_direction) {
				case "horizontal" :
					createHorizontalSections();
					break;
				case "vertical" :
					createVerticalSections();
					break;
			}
		}
		
		private function createHorizontalSections():void {
			
			var sectionHeight:Number = _currentimage.height / _numsections;
			var sectionWidth:Number = _currentimage.width;
			var point:Point = new Point();
			var frontData:BitmapData = new BitmapData(_currentimage.width, _currentimage.height);
			var backData:BitmapData = new BitmapData(_newimage.width, _newimage.height);
			
			frontData.draw(_currentimage);
			backData.draw(_newimage, new Matrix(1, 0, 0, -1, 0, _newimage.height));
			_bmpdataObjects.push(frontData, backData);
			
			for (var i:int = 0; i < _numsections; i++) {
				var fbmd:BitmapData = new BitmapData(sectionWidth, sectionHeight);
				var bbmd:BitmapData = new BitmapData(sectionWidth, sectionHeight);
				fbmd.copyPixels(frontData, new Rectangle(0, i * sectionHeight, sectionWidth, sectionHeight), point);
				bbmd.copyPixels(backData, new Rectangle(0, ((_numsections - 1) - i) * sectionHeight, sectionWidth, sectionHeight), point);
				_bmpdataObjects.push(fbmd, bbmd);
				var fbmp:Bitmap = new Bitmap(fbmd);
				var bbmp:Bitmap = new Bitmap(bbmd);
				_bmpObjects.push(fbmp, bbmp);
				var s:Sprite = new Sprite();
				fbmp.x -= sectionWidth * .5;
				fbmp.y -= sectionHeight * .5;
				bbmp.x -= sectionWidth * .5;
				bbmp.y -= sectionHeight * .5;
				s.addChild(bbmp);
				s.addChild(fbmp);
				s.x = sectionWidth * .5;
				s.y = i * sectionHeight + sectionHeight * .5;
				_sections.push(s);
				_sectionHolder.addChild(s);
			}
			
			_sectionHolder.x = _currentimage.x;
			_sectionHolder.y = _currentimage.y;
			_imgparent.addChildAt(_sectionHolder, _imgparent.getChildIndex(_currentimage));
			_imgparent.removeChild(_currentimage);
		}
		
		private function createVerticalSections():void {
			var sectionHeight:Number = _currentimage.height;
			var sectionWidth:Number = _currentimage.width / _numsections;
			var point:Point = new Point();
			var frontData:BitmapData = new BitmapData(_currentimage.width, _currentimage.height);
			var backData:BitmapData = new BitmapData(_newimage.width, _newimage.height);
			
			frontData.draw(_currentimage);
			backData.draw(_newimage, new Matrix(-1, 0, 0, 1, _newimage.width));
			_bmpdataObjects.push(frontData, backData);
			
			for (var i:int = 0; i < _numsections; i++) {
				var fbmd:BitmapData = new BitmapData(sectionWidth, sectionHeight);
				var bbmd:BitmapData = new BitmapData(sectionWidth, sectionHeight);
				fbmd.copyPixels(frontData, new Rectangle(i * sectionWidth, 0, sectionWidth, sectionHeight), point);
				bbmd.copyPixels(backData, new Rectangle(((_numsections - 1) - i) * sectionWidth, 0, sectionWidth, sectionHeight), point);
				_bmpdataObjects.push(fbmd, bbmd);
				var fbmp:Bitmap = new Bitmap(fbmd);
				var bbmp:Bitmap = new Bitmap(bbmd);
				_bmpObjects.push(fbmp, bbmp);
				var s:Sprite = new Sprite();
				fbmp.x -= sectionWidth * .5;
				fbmp.y -= sectionHeight * .5;
				bbmp.x -= sectionWidth * .5;
				bbmp.y -= sectionHeight * .5;
				s.addChild(bbmp);
				s.addChild(fbmp);
				s.x = i * sectionWidth  + sectionWidth * .5;
				s.y = sectionHeight * .5;
				_sections.push(s);
				_sectionHolder.addChild(s);
			}
			
			_sectionHolder.x = _currentimage.x;
			_sectionHolder.y = _currentimage.y;
			_imgparent.addChildAt(_sectionHolder, _imgparent.getChildIndex(_currentimage));
			_imgparent.removeChild(_currentimage);
		}
		
		private function timerHandler(event:TimerEvent):void {
			switch(_direction) {
				case "horizontal" :
					flipHorizontalSection(event.currentTarget.currentCount - 1);
					break;
				case "vertical" :
					flipVerticalSection(event.currentTarget.currentCount - 1);	
					break;
			}
		}
		
		private function flipHorizontalSection(count:int):void {
			var section:Sprite = _sections[count];
			Tweener.addTween(section, { rotationX:180, time:_time, transition:"easeOutQuad", onUpdate:checkAngle, onUpdateParams:[section], onComplete:tallyDone } );
		}
		
		private function flipVerticalSection(count:int):void {
			var section:Sprite = _sections[count];
			Tweener.addTween(section, { rotationY:-180, time:_time, transition:"easeOutQuad", onUpdate:checkAngle, onUpdateParams:[section], onComplete:tallyDone } );
		}
		
		private function checkAngle(s:Sprite):void {
			if (s["rotationX"] > 90 || s["rotationY"] < -90) {
				s.getChildAt(1).visible = false;
			}
		}
		
		private function tallyDone():void {
			if (++_numDone == _numsecti繁ns) {
				swapImage();
				cleanup();
				dispatchEvent(new Event(Event.COMPLETE));
			}
		}
		
		private function swapImage():void {
			_newimage.x = _sectionHolder.x;
			_newimage.y = _sectionHolder.y;
			var ind:int = _imgparent.getChildIndex(_sectionHolder);
			_imgparent.removeChild(_sectionHolder);
			_imgparent.addChildAt(_newimage, ind);	
		}
		
		private function cleanup():void {
			var i:int = _bmpdataObjects.length;
			while (i--) {
				_bmpdataObjects[i].dispose();
				delete _bmpdataObjects[i];
			}
			
			i = _bmpObjects.length;
			while (i--) {
				delete _bmpObjects[i];
			}
			
			i = _sections.length;
			while (i--) {
				delete _sections[i];
			}
			
			_sections = null;
			_bmpdataObjects = null;
			_bmpObjects = null;
			_sectionHolder = null;
			_currentimage = null;
			
			_timer.removeEventListener(TimerEvent.TIMER, timerHandler);
			_timer.reset();
			_timer = null;
		}
	}
}

And finally, a document class to test the box transition:

package {
	
	import com.onebyonedesign.transitions.OBO_BoxTransition;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.MovieClip;
	import flash.display.SimpleButton;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.filters.DropShadowFilter;
	
	/**
	* Demonstration of OBO_BoxTransition
	*/
	public class Main extends MovieClip {
		
		// items in library or on stage
		public var upButton:SimpleButton;
		public var downButton:SimpleButton;
		public var leftButton:SimpleButton;
		public var rightButton:SimpleButton;
		private var data1:BitmapData = new Img1(0, 0);
		private var data2:BitmapData = new Img2(0, 0);
		private var data3:BitmapData = new Img3(0, 0);
		private var data4:BitmapData = new Img4(0, 0);
		private var data5:BitmapData = new Img5(0, 0);
		
		private var bitmapDataArray:Array = [];
		private var buttonArray:Array = [];
		private var imageHolder:Sprite = new Sprite();
		private var trans:OBO_BoxTransition;
		private var dropShadow:DropShadowFilter = new DropShadowFilter(3, 90, 0x000000, 1, 5, 5, 1, 3);
		
		public function Main():void {
			bitmapDataArray.push(data1, data2, data3, data4, data5);
			buttonArray.push(upButton, downButton, leftButton, rightButton);
			
			var interactiveImage:Clip = new Clip();
			imageHolder.x = 125;
			imageHolder.y = 100;
			imageHolder.filters = [dropShadow];
			imageHolder.addChild(interactiveImage);
			addChild(imageHolder);
			
			trans = new OBO_BoxTransition(interactiveImage, randomImage());
			trans.addEventListener(Event.COMPLETE, transitionCompleteHandler);

			downButton.addEventListener(MouseEvent.CLICK, downHandler);
			upButton.addEventListener(MouseEvent.CLICK, upHandler);
			leftButton.addEventListener(MouseEvent.CLICK, leftHandler);
			rightButton.addEventListener(MouseEvent.CLICK, rightHandler);
		}
		
		private function downHandler(event:MouseEvent):void {
			enableButtons(false);
			trans.direction = OBO_BoxTransition.DOWN;
			trans.start();
		}
		
		private function upHandler(event:MouseEvent):void {
			enableButtons(false);
			trans.direction = OBO_BoxTransition.UP;
			trans.start();
		}
		
		private function leftHandler(event:MouseEvent):void {
			enableButtons(false);
			trans.direction = OBO_BoxTransition.LEFT;
			trans.start();
		}
		
		private function rightHandler(event:MouseEvent):void {
			enableButtons(false);
			trans.direction = OBO_BoxTransition.RIGHT;
			trans.start();
		}
		
		private function transitionCompleteHandler(event:Event):void {
			trans.currentimage = trans.newimage;
			trans.newimage = randomImage();
			enableButtons(true);
		}
		
		private function enableButtons(whichWay:Boolean):void {
			var i:int = buttonArray.length;
			while (i--) {
				buttonArray[i].enabled = whichWay;
			}
		}
		
		private function randomImage():Bitmap {
			var dat:BitmapData = bitmapDataArray[Math.floor(Math.random() * bitmapDataArray.length)];
			return new Bitmap(dat);
		}
	}
}

And completely offtopic, but while doing a little google surfing after the Virgin Prunes, I discovered this lengthy but interesting read on the Goth music scene in England back in the day. Like strolling down the halls of high school. Interesting and entertaining stuff for folks into that sort of thing. You know who you are...

Date: