In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

2009 07 22 ECLIPSE!!!! に勝手にコメントをつけてみた

Happy Eclipse! を勝手に解説する
ソースが長いので途中で力尽きたけど、だいたいの雰囲気は分かるはず。
下記コメントの「@see http://www.flickr.com/photos/auroracrowley/」
は Fork 元「Flickr Tricks For Aurora Crowley!!」の参照ページの
模様です。
Fork 元:
- http://wonderfl.net/code/448a0195da618461603cb61f9172fc6aff8420cf
以下、オリジナルのソースコードのはじまりはじまり。
Happy Eclipse!!
ActionScript3 Thread library のサンプルになれば幸いです :-)
*
*  @see    http://www.flickr.com/photos/auroracrowley/
*  @see    http://www.libspark.org/htdocs/as3/thread-files/document/
/**
 * Copyright nitoyon ( http://wonderfl.net/user/nitoyon )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/b06l
 */

// Happy Eclipse! を勝手に解説する
// ソースが長いので途中で力尽きたけど、だいたいの雰囲気は分かるはず。
//
// 下記コメントの「@see http://www.flickr.com/photos/auroracrowley/」
// は Fork 元「Flickr Tricks For Aurora Crowley!!」の参照ページの
// 模様です。
//
// Fork 元:
//  - http://wonderfl.net/code/448a0195da618461603cb61f9172fc6aff8420cf
//
// 以下、オリジナルのソースコードのはじまりはじまり。

//Happy Eclipse!!

// forked from soundkitchen's Flickr Tricks For Aurora Crowley!!
/**
 *  ActionScript3 Thread library のサンプルになれば幸いです :-)
 *
 *  @see    http://www.flickr.com/photos/auroracrowley/
 *  @see    http://www.libspark.org/htdocs/as3/thread-files/document/
 */

package
{
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageQuality;
    import flash.display.StageScaleMode;
    import flash.display.StageDisplayState;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Rectangle;
    import org.libspark.thread.EnterFrameThreadExecutor;
    import org.libspark.thread.Thread;

    [SWF(frameRate=60, width=465, height=465, backgroundColor=0x000000)]
    // ドキュメントクラス
    public class Eclipse extends Sprite
    {
        // コンストラクタ
        public function Eclipse()
        {
            // ステージの設定
            stage.align = StageAlign.TOP_LEFT;
            stage.quality = StageQuality.HIGH;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.fullScreenSourceRect = new Rectangle(0, 0, 465 , 465);

            // クリック時にはフルスクリーンに
            stage.addEventListener(MouseEvent.CLICK,function(e:Event):void{
                stage.displayState = (StageDisplayState.FULL_SCREEN==stage.displayState)?StageDisplayState.NORMAL:StageDisplayState.FULL_SCREEN
            })

            // ステージに追加完了時に initialize 関数を呼ぶ
            addEventListener(Event.ADDED_TO_STAGE, initialize);
        }

        private function initialize(evt:Event):void
        {
            // 即座に removeEventListener する。素敵な癖ですね。
            removeEventListener(Event.ADDED_TO_STAGE, initialize);

            // そうめん 初期化
            if (!Thread.isReady)
            {
                Thread.initialize(new EnterFrameThreadExecutor());
            }

            // MainThread を開始する
            new MainThread(this).start();
        }
    }
}

// import いっぱい!
// まずは flash.***
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BlendMode;
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Stage;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.filters.BitmapFilter;
import flash.filters.BitmapFilterQuality;
import flash.filters.BlurFilter;
import flash.geom.ColorTransform;
import flash.geom.Point;
import flash.net.URLRequest;
import flash.net.URLVariables;
import flash.system.LoaderContext;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.ui.Mouse;
import flash.utils.Timer;

// JSON シリアライズ用のライブラリ(as3corelibより)
import com.adobe.serialization.json.JSON;

// そうめん
import org.libspark.thread.Thread;
import org.libspark.thread.threads.display.LoaderThread;
import org.libspark.thread.threads.net.URLLoaderThread;
import org.libspark.thread.utils.IProgress;
import org.libspark.thread.utils.IProgressNotifier;
import org.libspark.thread.utils.MultiProgress;
import org.libspark.thread.utils.ParallelExecutor;
import org.libspark.thread.utils.SerialExecutor;

// フィルタエフェクト
internal var FILTER_BLUR:BlurFilter = new BlurFilter(4, 4, BitmapFilterQuality.MEDIUM);
// 高速化用の(0,0)を指す Point オブジェクト
internal var POINT_ZERO:Point = new Point();

import flash.display.Shape;

// メインの処理を実行するスレッド
// 実行順は次の通り:
//   1. run() メソッド
//      WaitAnimationThread を実行・待機
//   2. loadImage() メソッド
//      LoadImageThread, LoadImageProgressThread を実行・待機
//   3. loadComplete() メソッド
//      画像表示用の canvas を準備
//   4. changeImage() メソッド
//      HandleImageThread を実行・待機
//      その後、再び changeImage() メソッドを実行
internal class MainThread extends Thread
{
    private var layer:DisplayObjectContainer;
    private var imageLoader:LoadImageThread;
    private var contents:Array;
    private var canvas:Bitmap;
    private var currentIndex:uint;

    // コンストラクタ
    // DocumentRoot を layter に記録しておく
    public function MainThread(layer:DisplayObjectContainer)
    {
        this.layer = layer;
    }

    // スレッドのエントリポイント
    override protected function run():void
    {
        var t:Thread;

        // ローディングを開始して、終了まで待機する
        t = new WaitAnimationThread(layer);
        t.start();
        t.join();

        // WaitAnimationThread が終了したら、loadImage を呼ぶ
        next(loadImage);
    }

    // 画像をロードする
    private function loadImage():void
    {
        // 並列実行を行う
        var executor:ParallelExecutor;
        executor = new ParallelExecutor();

        // 画像ロード用のスレッドを追加
        imageLoader = new LoadImageThread();
        executor.addThread(imageLoader);

        // ロード状況を表示するスレッドを追加
        executor.addThread(new LoadImageProgressThread(layer, imageLoader.progress));

        // 両方が終了するまで待機
        executor.start();
        executor.join();

        // 終了後は loadComplete を実行する
        next(loadComplete);
    }

    // ロード完了時の処理
    private function loadComplete():void
    {
        var s:Stage,
            data:BitmapData;

        contents = imageLoader.contents.concat();
        imageLoader.contents.length = 0;
        imageLoader.contents = null;

        currentIndex = 0;

        s = layer.stage;
        data = new BitmapData(450, 450, true, 0);
        canvas = new Bitmap(data);
        canvas.x = (s.stageWidth - canvas.width) / 2;
        canvas.y = (s.stageHeight - canvas.height) / 2;
        layer.addChild(canvas);

        next(changeImage);
    }

    // 表示する画像を変更する
    private function changeImage():void
    {
        if (currentIndex >= contents.length)
        {
            currentIndex = 0;
        }

        var image:Bitmap,
            th:Thread;

        // イメージ表示のスレッドを準備する
        image = contents[currentIndex++] as Bitmap;
        th = new HandleImageThread(image, canvas);

        // 終了まで待機する
        th.start();
        th.join();

        // 次は再びこのメソッド
        next(changeImage);
    }

    // 終了時の処理
    override protected function finalize():void
    {
        layer = null;
        imageLoader = null;
    }
}

// 開始時の表示を行うスレッドクラス
// クラス名に「Animation」と入っているが、実際にはアニメーションは
// 行わず、文字を表示後、即座に終了する。
internal class WaitAnimationThread extends Thread
{
    private var layer:DisplayObjectContainer;
    private var message:Bitmap;

    // コンストラクタ
    // layer はドキュメントクラスのインスタンス
    public function WaitAnimationThread(layer:DisplayObjectContainer)
    {
        this.layer = layer;
    }

    // スレッドのエントリポイント
    override protected function run():void
    {
        var s:Stage,
            txt:TextField,
            fmt:TextFormat,
            bmd:BitmapData;

        // ステージ
        s = layer.stage;

        // 文字列を準備
        fmt = new TextFormat();
        fmt.color = 0xFFFFFF;
        fmt.size = 24;
        fmt.font = 'Trebuchet MS';
        txt = new TextField();
        txt.autoSize = TextFieldAutoSize.LEFT;
        txt.defaultTextFormat = fmt;
        txt.text = '2009 07 22 ECLIPSE PHOTO STREAM.';

        // BitmapData に文字列を描画
        bmd = new BitmapData(txt.textWidth, txt.textHeight, true, 0);
        bmd.draw(txt);

        // ビットマップを作成し、ステージの真ん中に配置
        message = new Bitmap(bmd);
        message.blendMode = BlendMode.INVERT;
        message.x = (s.stageWidth - message.width) / 2;
        message.y = (s.stageHeight - message.height) / 2;

        // ドキュメントクラスのインスタンスに追加する
        layer.addChild(message);

        // next() がないのでスレッドは終了する
    }

    // 終了処理
    // message の参照は切るが、テキストは表示されたまま
    override protected function finalize():void
    {
        layer = null;
        message = null;
    }
}

// 画像をロードするスレッド
// 「進捗通知機構」を備えるために IProgressNotifier を実装している
// 進捗状況は LoadImageProgressThread に伝えられる
internal class LoadImageThread extends Thread
implements IProgressNotifier
{
    public var contents:Array;
    private var mainProgress:MultiProgress;
    private var subProgress:MultiProgress;
    private var jsonLoader:URLLoaderThread;
    private var imageLoader:SerialExecutor;

    // IProgressNotifier で実装すべき唯一のメソッド
    public function get progress():IProgress
    {
        return mainProgress;
    }

    // コンストラクタ
    public function LoadImageThread()
    {
        var req:URLRequest,
            data:URLVariables;

        // 写真検索の URLRequest を準備する
        // (YQL を利用して Flickr の写真を検索する)
        req = new URLRequest('http://query.yahooapis.com/v1/public/yql');
        data = new URLVariables();
        data['q'] = "select * from flickr.photos.search where text='eclipse or 日食' and max_taken_date='2009-07-22'";
        data['format'] = 'json';
        req.data = data;

        // Loader 用のスレッドを準備しておく
        jsonLoader = new URLLoaderThread(req);
        mainProgress = new MultiProgress();
        subProgress = new MultiProgress();

        mainProgress.addProgress(jsonLoader.progress, 0.1);
        mainProgress.addProgress(subProgress);
    }

    // スレッドのエントリポイント
    override protected function run():void
    {
        // 写真検索を実施して、完了を待機する
        jsonLoader.start();
        jsonLoader.join();

        // ロードが完了したら loadDataComplete を呼び出す
        next(loadDataComplete);
    }

    // 写真検索完了時の処理
    private function loadDataComplete():void
    {
        var json:Object,
            row:Object,
            th:LoaderThread,
            req:URLRequest,
            ctx:LoaderContext,
            flickr:FlickrPhoto;

        json = JSON.decode(jsonLoader.loader.data);
        ctx = new LoaderContext(true);

        // 順次実行のスレッドを作成する
        imageLoader = new SerialExecutor();

        // 順次実行スレッドに画像ロードのスレッドを追加していく
        for each (row in json.query.results.photo)
        {
            flickr = new FlickrPhoto(row);
            req = new URLRequest(flickr.thumbnailURL);
            th = new LoaderThread(req, ctx);
            imageLoader.addThread(th);
            subProgress.addProgress(th.progress);
        }

        // 画像ロード処理を開始
        imageLoader.start();
        imageLoader.join();

        // 全部終わったら loadImageComplete を呼び出す
        next(loadImageComplete);
    }

    // 全ての画像のロードが完了したときの処理
    private function loadImageComplete():void
    {
        var i:uint,
            l:uint,
            th:LoaderThread;

        contents = [];
        l = imageLoader.numThreads;
        for (i=0; i<l; i++)
        {
            th = imageLoader.getThreadAt(i) as LoaderThread;
            contents.push(th.loader.content);
        }
    }

    override protected function finalize():void
    {
        jsonLoader = null;
        imageLoader = null;
    }
}

// LoadImageThread の進捗情報に基づいて画面描画を行うスレッド
// 実際にはローディング表示は何もやっていないが、雛形だけは用意されている。
internal class LoadImageProgressThread extends Thread
{
    private var progress:IProgress;
    private var layer:DisplayObjectContainer;
    private var indicator:HandleIndicatorThread;

    // コンストラクタ
    // progress 経由で LoadImageThread の進捗状況を取得できる
    public function LoadImageProgressThread(layer:DisplayObjectContainer, progress:IProgress)
    {
        this.layer = layer;
        this.progress = progress;
    }

    // スレッドのエントリポイント
    override protected function run():void
    {
        indicator = new HandleIndicatorThread(layer);
        indicator.start();

        next(step);
    }

    private function step():void
    {
        if (progress.isCompleted||progress.isFailed||progress.isCanceled)
        {
            next(shutDown);

            return;
        }

        // Loading の進捗状況を取得できる
        //  TODO: なにかしらやる。
        var percent:Number = progress.percent;

        // Loading 完了したら shutDown に移る
        // 完了しない限りは繰り返し step を実行する
        if (percent >= 1)
        {
            next(shutDown);
        }
        else
        {
            next(step);
        }
    }

    private function shutDown():void
    {
        // HandleIndicatorThread に停止するよう要請する
        // 実際に停止するかどうかは HandleIndicatorThread の実装に依存する
        indicator.interrupt();
    }

    override protected function finalize():void
    {
        progress = null;
        layer = null;
        indicator = null;
    }
}

// 画像の処理を行うスレッド
internal class HandleImageThread extends Thread
{
    public static const PARTICLE_MARGIN:Number = 2;

    private var original:Bitmap;
    private var destination:Bitmap;
    private var showParticles:Vector.<ShowImageParticle>;
    private var hideParticles:Vector.<HideImageParticle>;

    // コンストラクタ
    public function HandleImageThread(original:Bitmap, destination:Bitmap)
    {
        this.original = original;
        this.destination = destination;
        this.showParticles = new Vector.<ShowImageParticle>();
        this.hideParticles = new Vector.<HideImageParticle>();
    }

    // エントリポイント
    override protected function run():void
    {
        var w:Number, h:Number, c:uint,
            tx:Number, ty:Number,
            sx:Number, sy:Number,
            cx:Number, cy:Number,
            angle:Number,
            i:uint, j:uint,
            s:Stage,
            data:BitmapData;

        // 画像の情報を取得する
        s = destination.stage;
        data = original.bitmapData;
        w = data.width;
        h = data.height;
        cx = s.stageWidth / 2;
        cy = s.stageHeight / 2;

        // それぞれの座標について処理を行う
        for (i=0; i<w; i+=2)
        {
            for (j=0; j<h; j+=2)
            {
                // 色を取得する
                c = data.getPixel32(i, j);

                // 真っ黒なら何もしない
                if (!c) continue;

                // スタート地点はランダムな角度に 500px 移動させる
                angle = Math.random() * Math.PI * 2;
                sx = Math.cos(angle) * 500 + cx;
                sy = Math.sin(angle) * 500 + cy;

                // 終了地点は PARTICLE_MARGIN 倍した場所
                tx = i * PARTICLE_MARGIN;
                ty = j * PARTICLE_MARGIN;

                // 開始と終了のパーティクルを準備
                showParticles.push(new ShowImageParticle(sx, sy, tx, ty, c));
                hideParticles.push(new HideImageParticle(tx, ty, sx, sy, c, s.frameRate));
            }
        }

        // 次は showStep
        next(showStep);
    }

    // パーティクルが集まってくる処理
    private function showStep():void
    {
        var i:uint,
            l:uint,
            c:uint,
            a:Number,
            f:Boolean,
            p:ShowImageParticle,
            data:BitmapData;

        data = destination.bitmapData;
        data.lock();

        // ぼかしつつ
        data.applyFilter(data, data.rect, POINT_ZERO, FILTER_BLUR);

        // 座標を更新する
        l = showParticles.length;
        f = false;
        for (i=0; i<l; i++)
        {
            p = showParticles[i];

            if (p.isAlive)
            {
                f = true;
                p.update();
            }
            data.setPixel32(p.x, p.y, p.color);
        }
        data.unlock();

        // 全ての移動が完了すれば fixedImage へ
        if (!f)
        {
            next(fixedImage);
        }
        // そうでなければ再度 showStep へ
        else
        {
            next(showStep);
        }
    }

    // 非表示にしていく処理
    private function hideStep():void
    {
        var i:uint,
            l:uint,
            c:uint,
            a:Number,
            f:Boolean,
            p:HideImageParticle,
            data:BitmapData;

        data = destination.bitmapData;
        data.lock();

        // 透明にしつつ、ぼかしつつ
        data.colorTransform(data.rect, new ColorTransform(1, 1, 1, .97, 0, 0, 0, 0));
        data.applyFilter(data, data.rect, POINT_ZERO, FILTER_BLUR);

        l = hideParticles.length;
        for (i=0; i<l; i++)
        {
            p = hideParticles[i];
            p.update();
            data.setPixel32(p.x, p.y, p.color);

            // 画面外に移動したら消す
            if (!data.rect.contains(p.x, p.y))
            {
                hideParticles.splice(i, 1);
                i--;
                l = hideParticles.length;
            }
        }
        data.unlock();
        //trace(p.x, p.y);

        // 全部消えない限りは hideStep を繰り返す
        if (l) next(hideStep);
    }

    // 固定のままの状態
    // 3秒待機して hideStep へ
    private function fixedImage():void
    {
        sleep(3000);

        next(hideStep);
    }

    // 終了処理
    override protected function finalize():void
    {
        original = null;
        destination = null;
        showParticles.length = 0;
        showParticles = null;
        hideParticles.length = 0;
        hideParticles = null;
    }
}

internal class HandleIndicatorThread extends Thread
{
    private var layer:DisplayObjectContainer;
    private var indicator:DisplayObject;

    public function HandleIndicatorThread(layer:DisplayObjectContainer)
    {
        this.layer = layer;
    }

    override protected function run():void
    {
        Mouse.hide();

        layer.stage.mouseChildren = false;

        indicator = new Indicator();
        indicator.x = layer.mouseX;
        indicator.y = layer.mouseY;

        layer.addChild(indicator);

        next(step);
    }

    private function step():void
    {
        if (checkInterrupted()) return;

        indicator.rotation = (indicator.rotation + 360 / indicator.stage.frameRate) % 360;
        indicator.x = layer.mouseX;
        indicator.y = layer.mouseY;

        next(step);
    }

    override protected function finalize():void
    {
        Mouse.show();

        layer.stage.mouseChildren = true;
        layer.removeChild(indicator);
        layer = null;
        indicator = null;
    }
}

internal class HideMessageParticle
{
    public var x:Number;
    public var y:Number;
    public var ax:Number;
    public var ay:Number;
    public var vx:Number;
    public var vy:Number;
    public var color:uint;

    public function HideMessageParticle(x:Number, y:Number, color:Number)
    {
        var len:Number,
            angle:Number;

        len = Math.random() * 5;
        angle = Math.random() * Math.PI * 2;

        this.ax = Math.cos(angle) * len;
        this.ay = Math.sin(angle) * len;
        this.x = x;
        this.y = y;
        this.color = color;
        this.vx = 0;
        this.vy = 0;
    }

    public function update():void
    {
        vx += ax;
        vy += ay;
        x += vx;
        y += vy;
    }
}

import flash.geom.Point;

internal class ShowImageParticle
{
    public var x:Number;
    public var y:Number;
    public var color:uint;
    private var targetX:Number;
    private var targetY:Number;
    private var _isAlive:Boolean;

    public function get isAlive():Boolean
    {
        return _isAlive;
    }

    public function ShowImageParticle(x:Number, y:Number, targetX:Number, targetY:Number, color:uint)
    {
        this.x = x;
        this.y = y;
        this.color = color;
        this.targetX = targetX;
        this.targetY = targetY;
        this._isAlive = true;
    }

    public function update():void
    {
        x += (targetX - x) * .05;
        y += (targetY - y) * .05;

        if (Math.abs(targetX - x) < .5 && Math.abs(targetY - y) < .5)
        {
            x = targetX;
            y = targetY;
            _isAlive = false;
        }
    }
}

internal class HideImageParticle
{
    public var x:Number;
    public var y:Number;
    public var color:uint;
    private var vx:Number;
    private var vy:Number;
    private var ax:Number;
    private var ay:Number;
    private var rate:Number;

    public function HideImageParticle(x:Number, y:Number, targetX:Number, targetY:Number, color:uint, rate:Number)
    {
        var dx:Number,
            dy:Number,
            len:Number,
            angle:Number;

        dx = targetX - x;
        dy = targetY - y;
        len = 9.8;
        angle = Math.atan2(dy, dx);

        this.x = x;
        this.y = y;
        this.vx = 0;
        this.vy = 0;
        this.ax = Math.cos(angle) * len;
        this.ay = Math.sin(angle) * len;
        this.color = color;
        this.rate = rate;
    }

    public function update():void
    {
        vx += ax / rate;
        vy += ay / rate;
        x += vx;
        y += vy;
    }
}

internal class FlickrPhoto
{
    private var _id:String;
    public function get id():String
    {
        return _id;
    }

    private var _secret:String;
    public function get secret():String
    {
        return _secret;
    }

    private var _server:String;
    public function get server():String
    {
        return _server;
    }

    private var _farm:String;
    public function get farm():String
    {
        return _farm;
    }

    public function get thumbnailURL():String
    {
        return ['http://farm', farm, '.static.flickr.com/', server, '/', id, '_', secret, '_m.jpg'].join('');
    }

    public function FlickrPhoto(data:Object)
    {
        this._id = data['id'] || '';
        this._secret = data['secret'] || '';
        this._server = data['server'] || '';
        this._farm = data['farm'] || '';
    }
}

// ローディング用のカーソル
internal class Indicator extends Shape
{
    public function Indicator()
    {
        var i:uint,
            cx:Number, cy:Number,
            numNeedles:uint = 12,
            innerR:Number = 7,
            outerR:Number = 5,
            cAngle:Number = -Math.PI / 2,
            nAngle:Number;

        nAngle = Math.PI * 2 / numNeedles;
        for (i=0; i<numNeedles; i++)
        {
            cAngle += nAngle;
            cx = Math.cos(cAngle) * innerR;
            cy = Math.sin(cAngle) * innerR;
            graphics.moveTo(cx, cy);

            cx = Math.cos(cAngle) * outerR;
            cy = Math.sin(cAngle) * outerR;
            graphics.lineStyle(2, 0xffffff, i/numNeedles);
            graphics.lineTo(cx, cy);
        }
    }
}