package {
[SWF(width="384", heigh="256", frameRate="20") ]
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Rectangle;
import flash.ui.Keyboard;
import flash.utils.ByteArray;
import flash.utils.getTimer;
import flash.text.TextField;
public class Fractal extends Sprite
{
private var data:BitmapData;
private var image:Bitmap;
private var array:ByteArray;
private var xMin:Number = -2;
private var xMax:Number = .5;
private var yMin:Number = -1;
private var yMax:Number = 1;
private var isMoving:Boolean = false;
private var isZoom:Boolean = true;
private var colorMap:Array;
private const gridWidth:uint = 384;
private const gridHeight:uint = 256;
private const gridScaleX:uint = 1;
private const gridScaleY:uint = 1;
private const maxChunkSize:uint = 64;
private const maxiter:uint = 255;
public function Fractal()
{
stage.scaleMode = "noScale";
createColorMap();
var holder:Sprite = new Sprite;
data = new BitmapData(gridWidth, gridHeight, false, 0x000000);
image = new Bitmap(data, "auto", true);
array = new ByteArray();
scaleX = gridScaleX;
scaleY = gridScaleY;
holder.addChild(image);
holder.addEventListener(MouseEvent.MOUSE_DOWN, startMoving);
holder.addEventListener(MouseEvent.MOUSE_UP, stopMoving);
holder.addEventListener(Event.ENTER_FRAME, move);
stage.addEventListener(KeyboardEvent.KEY_DOWN, zoomMode);
stage.addEventListener(KeyboardEvent.KEY_UP, zoomMode);
addChild(holder);
draw();
}
public final function startMoving(event:MouseEvent):void {
isMoving = true;
}
public final function stopMoving(event:MouseEvent):void {
isMoving = false;
draw();
}
public final function zoomMode(event:KeyboardEvent):void {
if (event.keyCode == Keyboard.CONTROL) {
isZoom = (event.type == KeyboardEvent.KEY_UP);
}
}
public final function move(event:Event):void {
if (isMoving) {
if (isZoom) {
zoom();
} else {
unzoom();
}
}
}
public final function zoom():void {
var mx:int = image.mouseX;
var my:int = image.mouseY;
var xCenter:Number = xMin + (xMax - xMin) * mx / gridWidth;
var yCenter:Number = yMin + (yMax - yMin) * my / gridHeight;
xMin += (xCenter-xMin)/16;
xMax += (xCenter-xMax)/16;
yMin += (yCenter-yMin)/16;
yMax += (yCenter-yMax)/16;
if ((xMax - xMin < 1e-12)||(yMax - yMin < 1e-12)) {
return unzoom();
}
draw(true);
}
public final function unzoom():void {
var mx:int = image.mouseX;
var my:int = image.mouseY;
var xCenter:Number = xMin + (xMax - xMin) * (gridWidth-mx) / gridWidth;
var yCenter:Number = yMin + (yMax - yMin) * (gridHeight-my) / gridHeight;
xMin -= (xCenter-xMin)/16;
xMax -= (xCenter-xMax)/16;
yMin -= (yCenter-yMin)/16;
yMax -= (yCenter-yMax)/16;
if ((xMax - xMin > 4)||(yMax - yMin > 4)) {
return zoom();
}
draw(true);
}
private final function outOfTime(fast:Boolean, start:int):Boolean {
if (!fast) return false; var now:int = getTimer();
return (now-start > 30);
}
private final function draw(fast:Boolean = false):void {
data.lock();
var color:uint = 0;
var parent_x:uint;
var parent_y:uint;
array.length = 0;
var startTime:int = getTimer();
var mask:uint = (2*maxChunkSize)-1;
for (var chunkSize:uint = maxChunkSize; chunkSize>0&&(!outOfTime(fast, startTime)); chunkSize>>=1) {
var yInc:Number = (yMax - yMin) * chunkSize / gridHeight;
var xInc:Number = (xMax - xMin) * chunkSize / gridWidth;
var x0:Number = xMin;
var y0:Number = yMin;
var x:Number = 0;
var y:Number = 0;
var chunk2:uint = 2*chunkSize;
var rect:Rectangle = new Rectangle(0, 0, chunkSize, chunkSize);
for (var yPixel:uint = 0; yPixel < gridHeight; yPixel+=chunkSize) {
x0 = xMin;
parent_y = yPixel;
if (yPixel & mask) {
parent_y -= chunkSize;
}
const pyg:uint = parent_y * gridWidth;
const ypg:uint = yPixel * gridWidth;
for (var xPixel:uint = 0; xPixel < gridWidth; xPixel+=chunkSize) {
x = x0;
y = y0;
parent_x = xPixel;
if (xPixel & mask) {
parent_x -= - chunkSize;
}
var do_eval:Boolean = true;
if (chunkSize==maxChunkSize) {
do_eval = true;
} else if ( (xPixel == parent_x) && (yPixel == parent_y) ) {
do_eval = false;
} else if (same_neighbors(parent_x, parent_y, chunk2)) {
do_eval = false;
}
if (do_eval) {
color = escapeIteration(x0, y0) %256;
} else {
color = array[pyg + parent_x];
}
array[ypg + xPixel] = color;
rect.y = yPixel;
rect.x = xPixel;
data.fillRect(rect, colorMap[color]);
x0 += xInc;
}
y0 += yInc;
}
mask >>= 1;
}
data.unlock();
}
private final function same_neighbors(x:uint, y:uint, l:uint):Boolean {
if (y<l) return false;
if (x<l) return false;
var c1:uint = array[(y-l)*gridWidth+(x-l)];
var c2:uint = array[(y)*gridWidth+(x-l)];
if (c1!=c2) return false;
var c3:uint = array[(y+l)*gridWidth+(x-l)];
if (c1!=c3) return false;
var c4:uint = array[(y-l)*gridWidth+(x)];
if (c1!=c4) return false;
var c5:uint = array[(y+l)*gridWidth+(x)];
if (c1!=c5) return false;
var c6:uint = array[(y-l)*gridWidth+(x+l)];
if (c1!=c6) return false;
var c7:uint = array[(y)*gridWidth+(x+l)];
if (c1!=c7) return false;
var c8:uint = array[(y+l)*gridWidth+(x+l)];
if (c1!=c8) return false;
return true;
}
private final function escapeIteration(x:Number, y:Number):uint {
var x0:Number = x;
var y0:Number = y;
var y2:Number = y*y;
var x2:Number = x*x;
var iter:uint = 0;
while ( (x2+y2 < 4) && (iter < maxiter) ) {
y = 2*x*y + y0;
x = x2 - y2 + x0;
x2 = x*x;
y2 = y*y;
++iter;
}
return iter;
}
/**
* That was supposed to make things prettier.
* but it don't work. at all. :(
*/
private final function mapColor(val:uint):uint {
var g:uint = val;
var b:uint = 256 - val
var r:uint = ((val>>4)&15) | ((val << 4)&240)
return (r<<16)|(g<<8)|b;
}
private final function createColorMap():void {
colorMap = [];
for (var i:int =0;i<256;i++) {
colorMap[i] = mapColor(i);
}
}
}
}