package com.yourmajesty.effects.distortion
{
import flash.display.*;
import flash.filters.DisplacementMapFilter;
import flash.filters.DisplacementMapFilterMode;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.utils.ByteArray;
/**
* Distorts any IBitmapDrawable display object so that it appears as if it were mapped on the front
* of a cylinder. Using this class you may rotate the cylinder around its primary axis, modify the amount
* that the image is compressed on the edges, and how much it arcs (up or down).
* To use the class, simply create one, attach a display object to it, and add its "wet" property to your
* display list. The wet image is the post-processed image.
*
* If you use this class, please retain the following author tag:
* @author Roger Braunstein | Your Majesty Co | 2007
*/
public class CylinderMap implements IDistortionMap
{
protected static const ORIGIN:Point = new Point();
protected static const X_CHANNEL:uint = BitmapDataChannel.BLUE;
protected static const Y_CHANNEL:uint = BitmapDataChannel.RED;
protected var target:IBitmapDrawable;
protected var targetProjection:Matrix;
protected var map:BitmapData;
protected var mapSprite:Sprite;
protected var mapGraphics:Graphics;
protected var surface:BitmapData;
protected var surfaceBitmap:Bitmap;
protected var filter:DisplacementMapFilter;
public function get wet():Bitmap {return surfaceBitmap;}
/** height of the arc expressed as a percentage of the width, so that 1 should represent a semicircle. Negative numbers are acceptable */
public function set arc(value:Number):void {_arc = value; updateMap();}
public function get arc():Number {return _arc;}
protected var _arc:Number = 0.2;
/** amount of perspective applied to the edges of the cylinder as you look straight on it */
public function set sideCompression(value:Number):void {_sideCompression = value; updateMap();}
public function get sideCompression():Number {return _sideCompression;}
protected var _sideCompression:Number = 50;
/** allows you to simulate the cylinder rotating around. Expressed in degrees. Clockwise. From off the surface left = 0 to off the surface right = 360. */
public function set rotation(value:Number):void { _rotation = value; updateRotation();}
public function get rotation():Number {return _rotation;}
protected var _rotation:Number = 0;
protected const NEUTRAL_DISPLACE:int = 128;
public function CylinderMap(width:Number = NaN, height:Number = NaN, mc:IBitmapDrawable = null)
{
if (mc) attachDisplayObject(mc);
if (!isNaN(width) && !isNaN(height)) createMap(width, height);
}
public function destroy():void
{
if (surface) surface.dispose();
if (map) map.dispose();
}
public function attachDisplayObject(mc:IBitmapDrawable):void
{
target = mc;
targetProjection = new Matrix();
}
public function createMap(width:Number, height:Number):void
{
map = new BitmapData(int(width), int(height), true, 0);
targetProjection.ty = int(0.5 * (map.height - DisplayObject(target).height));
mapSprite = new Sprite();
mapGraphics = mapSprite.graphics;
surface = new BitmapData(int(width), int(height), true, 0);
surfaceBitmap = new Bitmap(surface);
filter = new DisplacementMapFilter(map, ORIGIN, X_CHANNEL, Y_CHANNEL, 0, 0, DisplacementMapFilterMode.COLOR, 0, 0);
initializeMap();
updateMap();
}
protected function initializeMap():void
{
var xColors:Array = new Array(map.width);
var yColors:Array = new Array(map.height);
var horizPixelStrip:ByteArray = new ByteArray();
var scale:Number = 2 / Math.PI;
var x:int, y:int, limit:int, value:Number;
var redValue:uint, blueValue:uint, redX:Number, blueX:Number, redDX:Number, blueDX:Number;
var X:Number, gap:Number;
for (x = 0, limit = map.width,
blueX = -1, blueDX = 2 / map.width,
redX = -1, redDX = 2 / map.width;
x < limit; x++, redX += redDX, blueX += blueDX)
{
value = scale * Math.asin( blueX ) * Math.pow(blueX, 6); value = NEUTRAL_DISPLACE + NEUTRAL_DISPLACE * value; blueValue = (int(value) & 0xff);
value = Math.sqrt( 1 - redX*redX ); value = NEUTRAL_DISPLACE - 1 + NEUTRAL_DISPLACE * value; redValue = (int(value) & 0xff);
horizPixelStrip.writeUnsignedInt(0xff000000 + (redValue << 16) + blueValue);
}
var r:Rectangle = new Rectangle(0, 0, map.width, 1);
for (y = 0, limit = map.height; y < limit; y++, r.y++)
{
horizPixelStrip.position = 0;
map.setPixels(r, horizPixelStrip);
}
}
protected function updateRotation():void
{
if (!targetProjection) return;
var value:Number = _rotation % 360;
var w:Number = DisplayObject(target).width;
targetProjection.tx = (map.width + w) * (value / 360) - w;
}
public function updateMap():void
{
filter.scaleY = _arc * map.width;
filter.scaleX = _sideCompression;
}
public function draw():void
{
surface.fillRect(surface.rect, 0);
surface.draw(target, targetProjection);
surface.applyFilter(surface, surface.rect, new Point(0, 0), filter);
}
}
}