Top | 戻る

MandelbrotSet

  AVMってどのくらい速いの? っていうことで、タイトな演算で有名なアレを試してみました。
見てみたい場所を右クリックしてコンテクストメニュー表示、「ZoomIn」「ZoomOut」できます。
  演算のループは、一度に実行すると ActionScript のタイムアウトとなってしまうので、 100ms 程度で実行できる範囲で少しずつ行うようにしています。
Command.as
package {

    public class Command {
        
        public var func : Function;
        public var label : String;
    
        public function Command(func : Function, label : String = null) {
            this.func = func;
            this.label = label;
        }
    }
}
Executor.as
package {
    
    public class Executor {
        
        private var code : Array;
        private var index : int;
        private var labelIndex : Object;
        
        public function Executor(code : Array) {
    
            this.code = code;    
            this.index = 0;
    
            labelIndex = new Object();
            for (var i : int = 0; i < code.length; i++) {
                if (code[i] is String) {
                    labelIndex[code[i]] = i;
                }
            }
        }
        
        public function next() : Boolean {
    
            if (code[index] is Command) {
                var cmd : Command = Command(code[index]);
                if (cmd.func() && cmd.label) {
                    index = labelIndex[cmd.label];
                    return true;
                }
            }    
    
            index++;
            return index < code.length;
        }
    }
}
MandelbrotSet.as
package {

    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.events.ContextMenuEvent;
    import flash.events.Event;
    import flash.ui.ContextMenu;
    import flash.ui.ContextMenuItem;
    
    public class MandelbrotSet extends Sprite {

        private var bitmap : BitmapData;

        private var executor : Executor;
        private var currentX : int;
        private var currentY : int;
        
        private var reC : Number = 0;
        private var imC : Number = 0;
        private var scale : Number = 4;

        private var mouseRe : Number;
        private var mouseIm : Number;

        public function MandelbrotSet() {

            bitmap = new BitmapData(400, 400);

            var contextMenu : ContextMenu = new ContextMenu();
            contextMenu.hideBuiltInItems();
            contextMenu.addEventListener(ContextMenuEvent.MENU_SELECT, function (e : ContextMenuEvent) : void {
                mouseRe = mouseX;
                mouseIm = mouseY;
            } );

            var zoomInItem : ContextMenuItem = new ContextMenuItem("ZoomIn");
            zoomInItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, function (e : ContextMenuEvent) : void {
                reC = getRe(mouseRe);
                imC = getIm(mouseIm);
                scale /= 2;
                draw();
            } );
            contextMenu.customItems.push(zoomInItem);

            var zoomOutItem : ContextMenuItem = new ContextMenuItem("ZoomOut");
            zoomOutItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, function (e : ContextMenuEvent) : void {
                reC = getRe(mouseRe);
                imC = getIm(mouseIm);
                scale *= 2;
                draw();
            } );
            contextMenu.customItems.push(zoomOutItem);

            this.contextMenu = contextMenu;

            addEventListener(Event.ENTER_FRAME, onEnterFrame);
 
            draw();
        }

        private function onEnterFrame(e : Event) : void {

            if (executor) {
                
                bitmap.lock();

                // 100ms 程度でできるだけたくさん実行する。        
                var beginTime : Number = new Date().getTime();
                while (new Date().getTime() - beginTime < 100) {
                    if (!executor.next() ) {
                        executor = null;
                        break;            
                    }
                }
                
                bitmap.unlock();
            }
            
            graphics.clear();
            graphics.beginBitmapFill(bitmap);
            graphics.drawRect(0, 0, bitmap.width, bitmap.height);
            graphics.endFill();
        }

        private function draw() : void {
            executor = new Executor([
                new Command(function() : * { currentY = 0; }),
                "yloop",
                new Command(function() : * { currentX = 0; }),
                "xloop",
                new Command(calcPixel),
                new Command(function() : * { currentX++; return currentX < bitmap.width; }, "xloop"),
                new Command(function() : * { currentY++; return currentY < bitmap.height; }, "yloop")
            ]);
        }

        private function getRe(x : int) : Number {
            return (x - bitmap.width / 2) / bitmap.height * scale + reC;
        }

        private function getIm(y : int) : Number {
            return (bitmap.height / 2 - y) / bitmap.height * scale + imC;
        }
        
        private function calcPixel() : void {

            var re0 : Number = getRe(currentX);
            var im0 : Number = getIm(currentY);

            var re : Number = re0;
            var im : Number = im0;

            for (var limit : int = 1000; limit > 0; limit--) {

                if (re * re + im * im >= 4) {
                    break;
                }

                var tmpRe : Number = re;
                re = re * re - im * im + re0;
                im = 2 * tmpRe * im + im0;
            }

            var dark : Boolean = (limit % 2 == 1);

            var color : uint = (limit == 0)? 0x000000 : 
                  getRGB(limit / 100, dark? 0.8 : 1);

            bitmap.setPixel(currentX, currentY, color);
        }

        private static function getRGB(hue : Number, bright : Number) : uint {
            var r : int = getGrad(hue) * 255 * bright;
            var g : int = getGrad(hue + 1/3) * 255 * bright;
            var b : int = getGrad(hue + 2/3) * 255 * bright;
            return r << 16 | g << 8 | b;
        }
        
        private static function getGrad(n : Number) : Number {
        
            n = (n >= 0)? n % 1 : 1 - (-n) % 1; 
        
            if (n < 1/6) {
                return 1;
            } else if (n < 2/6) {
                return -6 * n + 2;
            } else if (n < 4/6) {
                return 0;
            } else if (n < 5/6) {
                return 6 * n - 4;
            } else {
                return 1;
            }
        }        
    }
}
実行結果

Contents Copyright © Kazuhiko Arase