package { import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.Loader; import flash.display.Sprite; import flash.events.*; import flash.geom.Point; import flash.geom.Rectangle; import flash.net.URLRequest; public class Reproject extends Sprite { private var url:String = "http://www.reenigne.org/reproject/land_shallow_topo_2048.jpg"; private var size:Object = new Object(); private var sourceSize:Object = new Object(); private var sourceData:BitmapData; private var destinationData:BitmapData; // Look-up tables private var cosx:Array = new Array(); private var sinx:Array = new Array(); private var cosy:Array = new Array(); private var siny:Array = new Array(); private var atan0:Array = new Array(); private var atan1:Array = new Array(); private var atan2:Array = new Array(); private var atan3:Array = new Array(); private var atan4:Array = new Array(); private var atan5:Array = new Array(); private var atan6:Array = new Array(); private var atan7:Array = new Array(); private var acos:Array = new Array(); // Current transformation private var xx:Number = 1; private var xy:Number = 0; private var xz:Number = 0; private var yx:Number = 0; private var yy:Number = 1; private var yz:Number = 0; private var zx:Number = 0; private var zy:Number = 0; private var zz:Number = 1; // Transformation as it was at the start of the drag private var xx0:Number = 1; private var xy0:Number = 0; private var xz0:Number = 0; private var yx0:Number = 0; private var yy0:Number = 1; private var yz0:Number = 0; private var zx0:Number = 0; private var zy0:Number = 0; private var zz0:Number = 1; // Start point of drag in screen space private var lonFromScreen:Number; private var latFromScreen:Number; public function Reproject() { size.x = 600; size.y = 300; // Init look-up tables for (var xPixel:int = 0; xPixel < size.x; ++xPixel) { cosx[xPixel] = Math.cos(xPixel*2*Math.PI/size.x); sinx[xPixel] = Math.sin(xPixel*2*Math.PI/size.x); } for (var yPixel:int = 0; yPixel < size.y; ++yPixel) { cosy[yPixel] = Math.cos(yPixel*Math.PI/size.y); siny[yPixel] = Math.sin(yPixel*Math.PI/size.y); } var loader:Loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler); var request:URLRequest = new URLRequest(url); loader.load(request); } private function updateBitmap():void { destinationData.lock(); for (var yPixel:int = 0; yPixel < size.y; ++yPixel) { var cosY:Number = cosy[yPixel]; var sinY:Number = siny[yPixel]; for (var xPixel:int = 0; xPixel < size.x; ++xPixel) { var cosX:Number = cosx[xPixel]; var sinX:Number = sinx[xPixel]; // Find screen space coordinates for this pixel var xScreen:Number = sinY*cosX; var yScreen:Number = sinY*sinX; var zScreen:Number = cosY; // Find world space coordinates for this pixel var xWorld:Number = xx*xScreen + xy*yScreen + xz*zScreen; var yWorld:Number = yx*xScreen + yy*yScreen + yz*zScreen; var zWorld:Number = zx*xScreen + zy*yScreen + zz*zScreen; var xSource:int; var ySource:int; if (yWorld > 0) if (xWorld > 0) if (xWorld > yWorld) xSource = atan0[int(2048*yWorld/xWorld)]; else xSource = atan1[int(2048*xWorld/yWorld)]; else if (-xWorld < yWorld || xWorld == 0) xSource = atan2[int(-2048*xWorld/yWorld)]; else xSource = atan3[int(-2048*yWorld/xWorld)]; else if (xWorld < 0) if (xWorld < yWorld) xSource = atan4[int(2048*yWorld/xWorld)]; else xSource = atan5[int(2048*xWorld/yWorld)]; else if (-yWorld >= xWorld || xWorld == 0) xSource = atan6[int(-2048*xWorld/yWorld)]; else xSource = atan7[int(-2048*yWorld/xWorld)]; ySource = acos[int(2048*zWorld + 2048)]; // Copy this pixel from the original destinationData.setPixel(xPixel, yPixel, sourceData.getPixel(xSource, ySource)); } } destinationData.unlock(); } private function completeHandler(event:Event):void { var loader:Loader = Loader(event.target.loader); var image:Bitmap = Bitmap(loader.content); sourceData = image.bitmapData; sourceSize.x = image.width; sourceSize.y = image.height; destinationData = new BitmapData(size.x, size.y, false); var t:int; for (t = 0; t <= 2048; ++t) { atan0[t] = (int(( 0 + Math.atan(t/2048))*sourceSize.x/(2*Math.PI)) + sourceSize.x)%sourceSize.x; atan1[t] = (int(( Math.PI/2 - Math.atan(t/2048))*sourceSize.x/(2*Math.PI)) + sourceSize.x)%sourceSize.x; atan2[t] = (int(( Math.PI/2 + Math.atan(t/2048))*sourceSize.x/(2*Math.PI)) + sourceSize.x)%sourceSize.x; atan3[t] = (int(( Math.PI - Math.atan(t/2048))*sourceSize.x/(2*Math.PI)) + sourceSize.x)%sourceSize.x; atan4[t] = (int(( Math.PI + Math.atan(t/2048))*sourceSize.x/(2*Math.PI)) + sourceSize.x)%sourceSize.x; atan5[t] = (int((3*Math.PI/2 - Math.atan(t/2048))*sourceSize.x/(2*Math.PI)) + sourceSize.x)%sourceSize.x; atan6[t] = (int((3*Math.PI/2 + Math.atan(t/2048))*sourceSize.x/(2*Math.PI)) + sourceSize.x)%sourceSize.x; atan7[t] = (int((2*Math.PI - Math.atan(t/2048))*sourceSize.x/(2*Math.PI)) + sourceSize.x)%sourceSize.x; } for (t = 0; t <= 4096; ++t) { var v:int = int(Math.acos(t/2048 - 1)*sourceSize.y/Math.PI); if (v < 0) v = 0; if (v >= sourceSize.y) v = sourceSize.y - 1; acos[t] = v; } updateBitmap(); var modified:Bitmap = new Bitmap(destinationData); addChild(modified); stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler); stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler); } private function mouseDownHandler(event:MouseEvent):void { var xPixelFrom:Number = event.stageX; var yPixelFrom:Number = event.stageY; lonFromScreen = xPixelFrom*2*Math.PI/size.x; latFromScreen = yPixelFrom*Math.PI/size.y; xx0 = xx; xy0 = xy; xz0 = xz; yx0 = yx; yy0 = yy; yz0 = yz; zx0 = zx; zy0 = zy; zz0 = zz; stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler); startDrag(); } private function mouseUpHandler(event:MouseEvent):void { stopDrag(); stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler); } private function mouseMoveHandler(event:MouseEvent):void { var xPixelTo:Number = event.stageX; var yPixelTo:Number = event.stageY; var lonToScreen:Number = xPixelTo*2*Math.PI/size.x; var latToScreen:Number = yPixelTo*Math.PI/size.y; var theta:Number; var c:Number; var s:Number; var xxt:Number; var xyt:Number; var xzt:Number; var yxt:Number; var yyt:Number; var yzt:Number; var zxt:Number; var zyt:Number; var zzt:Number; xx = xx0; xy = xy0; xz = xz0; yx = yx0; yy = yy0; yz = yz0; zx = zx0; zy = zy0; zz = zz0; // (xx xy xz) = (xx xy xz)(ct -st 0) // (yx yy yz) = (yx yy yz)(st ct 0) // (zx zy zz) = (zx zy zz)( 0 0 1) theta = lonFromScreen; c = Math.cos(theta); s = Math.sin(theta); xxt = c*xx + s*xy; xyt = -s*xx + c*xy; xzt = xz; yxt = c*yx + s*yy; yyt = -s*yx + c*yy; yzt = yz; zxt = c*zx + s*zy; zyt = -s*zx + c*zy; zzt = zz; xx = xxt; xy = xyt; xz = xzt; yx = yxt; yy = yyt; yz = yzt; zx = zxt; zy = zyt; zz = zzt; // (xx xy xz) = (xx xy xz)(ct 0 -st) // (yx yy yz) = (yx yy yz)( 0 1 0) // (zx zy zz) = (zx zy zz)(st 0 ct) theta = latToScreen - latFromScreen; c = Math.cos(theta); s = Math.sin(theta); xxt = c*xx + s*xz; xyt = xy; xzt = -s*xx + c*xz; yxt = c*yx + s*yz; yyt = yy; yzt = -s*yx + c*yz; zxt = c*zx + s*zz; zyt = zy; zzt = -s*zx + c*zz; xx = xxt; xy = xyt; xz = xzt; yx = yxt; yy = yyt; yz = yzt; zx = zxt; zy = zyt; zz = zzt; // (xx xy xz) = (xx xy xz)(ct -st 0) // (yx yy yz) = (yx yy yz)(st ct 0) // (zx zy zz) = (zx zy zz)( 0 0 1) theta = -lonToScreen; c = Math.cos(theta); s = Math.sin(theta); xxt = c*xx + s*xy; xyt = -s*xx + c*xy; xzt = xz; yxt = c*yx + s*yy; yyt = -s*yx + c*yy; yzt = yz; zxt = c*zx + s*zy; zyt = -s*zx + c*zy; zzt = zz; xx = xxt; xy = xyt; xz = xzt; yx = yxt; yy = yyt; yz = yzt; zx = zxt; zy = zyt; zz = zzt; updateBitmap(); x = 0; y = 0; } } }