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; //starting with a neutral displacement lets us shift things negatively as well as positively. 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(); //target = map = surface = surfaceBitmap = filter = null; } public function attachDisplayObject(mc:IBitmapDrawable):void { target = mc; targetProjection = new Matrix(); } public function createMap(width:Number, height:Number):void { //TODO: clear old maps if this is called twice 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; //blue = horizontal compression, arcsin from -1 to 1 //red = vertical arching, sqrt(1-x^2) 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); //goes from -1 to 1 like a sideways sin value = NEUTRAL_DISPLACE + NEUTRAL_DISPLACE * value; //now it goes from 0 to 255 blueValue = (int(value) & 0xff); value = Math.sqrt( 1 - redX*redX ); //goes from 0 to 1 to 0 value = NEUTRAL_DISPLACE - 1 + NEUTRAL_DISPLACE * value; //now it goes from 128 to 255 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); } } }