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

ABC flyer + clouds

The gradation colors are refered from psyrak's BumpyPlanet 
and nemu90kWw's 水平線.
arrows or [wasd] to move, [shift/x/m] to accel.
------------------------------------------------------------
Get Adobe Flash player
by gyuque 21 Jan 2009
// forked from keim_at_Si's ABC flyer
// forked from keim_at_Si's ABC ground
// The gradation colors are refered from psyrak's BumpyPlanet 
// and nemu90kWw's 水平線.
// arrows or [wasd] to move, [shift/x/m] to accel.
//------------------------------------------------------------
package {
    import flash.display.*;
    import flash.text.*;
    import flash.geom.*;
    import flash.events.*;
    import flash.utils.*;

    [SWF(width='465', height='465', backgroundColor='#103860', frameRate='30')]
    public class main extends Sprite {
        function main() {
            _key = new KeyMapper(stage);
            _key.map(0,37,65).map(1,38,87).map(2,39,68).map(3,40,83).map(4,17,90,78).map(5,16,88,77);
            
            _shape3d.visible = false;
            addChild(_shape3d);
            
            _base.x = 232.5;
            _base.y = 232.5;
            addChild(_base);
            
            _landscape.rotationX = -85;
            _landscape.scaleX = 10;
            _landscape.scaleY = 8;
            _landscape.x = -1024-_base.x;
            _landscape.y = 310-_base.y;
            _landscape.z = 1800;
            _sky.scaleX = 5;
            _sky.scaleY = 5;
            _sky.x = -1520-_base.x;
            _sky.y = -1400-_base.y;
            _sky.z = 1800;
            _base.addChild(_landscape);

            _clouds.rotationX = -85;
            _clouds.scaleX = 10;
            _clouds.scaleY = 8;
            _clouds.x = -1024-_base.x;
            _clouds.y = 230-_base.y;
            _clouds.z = 1800;
            _base.addChild(_clouds);

            _base.addChild(_sky);
            
            addChild(new Bitmap(_screen));
            
            _pitch = 0;
            _roll = 0;
            _player.init(0, 0, 100, _modelManager.mdlFlyer);
            addEventListener("enterFrame", _onEnterFrame);
        }

        private function _onEnterFrame(e:Event) : void {
            // move
            var inkey:uint = _key.keys;
            _roll  += ((inkey & 1)      - ((inkey & 4)>>2))*5 - _roll*0.1;
            _pitch += (((inkey & 2)>>1) - ((inkey & 8)>>3))*2 - _pitch*0.1;
            _globalVel.z += 0.5 - _globalVel.z * ((inkey & 32) ? 0.03 : 0.06);
            _globalVel.x = (_roll) * 0.1 - 0.5;
            _base.rotationX = _pitch;
            _base.rotationZ = _roll;
            
            _landscape.update();
            _clouds.update();
            _player.update(null);
            
            _screen.fillRect(_screen.rect, 0);
            _player.render();
        }
    }
}


// internal variables
var _key:KeyMapper;
var _modelManager:ModelManager = new ModelManager();
var _playerManager:ActorManager = new ActorManager(Player);
var _player:Player = Player(_playerManager.alloc());

var _base:Sprite = new Sprite();
var _landscape:Landscape = new Landscape(256, 256);
var _clouds:Clouds       = new Clouds(256, 256);
var _sky:Sky = new Sky();

var _light:Light = new Light(-1,-1,-1);
var _matrix:Matrix3D = new Matrix3D();
var _shape3d:Shape3D = new Shape3D();
var _screen:BitmapData = new BitmapData(465, 465, true, 0);
var _mat2d:Matrix = new Matrix(1,0,0,1,232,232);
var _pitch:Number, _roll:Number, _globalVel:Vector3D = new Vector3D();
var _homingAbility:Number = 0.1;

import flash.display.*;
import flash.geom.*;
import flash.events.*;
import flash.filters.*;
import flash.utils.*;
import mx.utils.Base64Decoder;

// Resource
//----------------------------------------------------------------------------------------------------
class ModelManager {
    public var mdlFlyer:Model;
    public var mdlFire:Model;
    public var texFire:Vector.<BitmapData> = new Vector.<BitmapData>(8, true);
    private var _dec:Base64Decoder = new Base64Decoder();
    private const SCALE:Number = 0.1;
    function ModelManager() {
        var i:int, vdata:String="", idata:String="", shp:Shape=new Shape(), mtx:Matrix=new Matrix();
        // Flyer
        vdata += "iN94Art14Ar5234tF43zutz4Au503zwR4HwxNz4Awt030zN34Ar524ItF44Mu504MwR4IQwt04LiN94Aft73Yft93AtF43znt53Q";
        vdata += "mt93AwR4HwvR6nmtN6nQvSB3mmuD4AmuF3gmuB3AiOD4AfuF3gfuB3AzN34A0SEIA0uAHm1N73w1N54A4eDYA4uAHz4t+324t94A";
        vdata += "7OB4AhN734at73Aat53Iat53Yat534at74AVt73AVt53IVt53YVt73gWR73rVt/3AVuB3IVuB3YVt/3gWR/3rat/3AauB3IauB3Y";
        vdata += "at/3gat/4AfuD3oiN94AUt6nQUt7XZUt7XGUt93cUt93DUuAHGUuAHZUuA3QWt93QWt93QiN94Aft74oft95AtF44Mnt54wmt95A";
        vdata += "wR4IQvR6oZtN6owvSB4ZmuD4AmuF4gmuB5AiOD4AfuF4gfuB5AzN34A0SEIA0uAIZ1N74Q1N54A4eDYA4uAIM4t+4J4t94A7OB4A";
        vdata += "hN74Iat75Aat544at54oat54Iat74AVt75AVt544Vt54oVt74gWR74UVt/5AVuB44VuB4oVt/4gWR/4Uat/5AauB44auB4oat/4g";
        vdata += "fuD4YiN94AUt6owUt7YmUt7Y5Ut94jUt948UuAI5UuAImUuA4wWt94wWt94wbN/3AbN/2QZt/0gdV93AdV92AgB93AgB92AcN/0I";
        vdata += "mt93AgB/3AgB/2AdV/3AdV/2Amt93AbN/5AbN/5wZt/7gdV95AdV96AgB95AgB96AcN/74mt95AgB/5AgB/6AdV/5AdV/6Amt95A";
        vdata += "UN/1wat93AWV92YX193ASt/2AUt92gVN93ATt/24VN93AWB/3AX193Aat93AUN/6Qat95AWV95oX195ASt/6AUt95gVN95ATt/5I";
        vdata += "VN95AWB/5AX195Aat95AWtj3IUtj3IVt53IVt53Qat53Iat53QWtj44Utj44Vt544Vt54wat544at54w"
        idata += "AAQIAAAgMABCQgHBCQYIBCAYFAAAoBAAAsKBBw4JBDg0JBDA0OAFRYTAEhUTADxITAEA8TAGBcWAFxsUAGRoYAJiglAJygmAJSgk";
        idata += "AHyMiAFh8iAFhUfAFiIhAGBYhAHjo5AKywQALDEyALSwyAPh0cAPxApAOh4dAOzodAGBobAFxgbAIBkYAISAYAQEExAMEAxAQUMy";
        idata += "AMUEyAQ0Y3AMkM3ARkc2AN0Y2AR0U1ANkc1ARUQ0ANUU0AREIvANEQvAQkAwAL0IwCSEZDCQUhDCRUdICSERFCSEJECSEBCCQUBI";
        idata += "ATlFQATlBNATk1KATkpLAUVJTAT1ZSAU1VUAYGNhAYWNiAX2NgAXV5aAXVpRAWlBRAXF1RAXFFTAdHVZAS2dmAbWxnAbWdoAV1h4";
        idata += "AZEt5AWFl1AWHV2AVlVTAVlNSAU1RbAU1tcAbHt6AbHprAbX17AbXtsAcoB9Acn1tAcYGAAcYByAcH+BAcIFxAb35/Ab39wAanx+";
        idata += "Aan5vAa3p8Aa3xqCfYCCCfYJ7CgoF/Cf36CCfnyCCfHqCCgnp7AbmkzAPXM4AFhMXAFBMXAT05SAUU5SAiIaFAhYaQAi4qRAiomR";
        idata += "Ai5GOAjI2OAk5SWAnpSTAn5iZAn5eYAnJ+ZAnJuaAp6WkAo6qhAqKmmAo6GiAqKepApKmnAqaSgAoKKrAq6mgAsLGzArbavAsrW0";
        idata += "Arq2vAtbO0As7WwArLC1At66sArLW3Auru5AuL28Av8HAAwsO+BBAUCBAQQCBBQYDBAgUDBCAUEBBwgEBCgwEBAQoEBCw0MBCgsM";
        idata += "BBAwOBBwQOAFBEQAExQQAFxQTAFhcTAHB0aAGRwaAHR4bAGh0bAFBseAERQeAIiYlAISIlAJyYiAIyciAJCAhAJSQhAKxARAKisR";
        idata += "ALSkQALC0QALg8pALS4pALzArAKi8rAMDEsAKzAsAMy4tAMjMtANzgzAMjczAOTo1ANDk1AOjs2ANTo2AOzw3ANjs3APD04ANzw4";
        idata += "AHT48AOx08APhw9APD49AOSoRAHjkRANC8qAOTQqCR0ZICSUdIAS0xPATktPATk9SAUU5SAVFVYAV1RYAVVZZAWFVZATFlWAT0xW";
        idata += "AYGFdAXGBdAXWFiAXl1iAXFtfAYFxfATEtmAZUxmAS2RoAZ0toAZEppAaGRpAZWZrAamVrAZmdsAa2ZsAaGluAbWhuAbW5zAcm1z";
        idata += "Ab3B1AdG91AcHF2AdXB2AcXJ3AdnF3AcnM9Ad3I9Adnd4AWHZ4APVd4Adz14AWUxlAdFllAdGVqAb3RqCg4KACgYOAAOHNuAMzhu";
        idata += "Ah4iFAhIeFAiYqIAh4mIAi4aIAiouIAj5COAjY+OAkIaLAjpCLAhIWQAj4SQAkpOWAlZKWAlZaYAl5WYAlpSZAmJaZAm5yeAnZue";
        idata += "AnJmUAnpyUAnZ6TAkp2TApaKgApKWgApaajAoqWjAp6imApaemArK6xAsKyxArq+yAsa6yAsbK0As7G0Aubu9AuLm9Aurm4AvLq4";
        idata += "AvsPBAv77BAwr6/AwMK/"
        mdlFlyer = _loadModel(vdata, idata, new Material(0xc0c0c0), new Material(0x203040), new Material(0xffc040));
        // Fire
        mtx.createGradientBox(32,32,0,0);
        for (i=0; i<8; i++) {
            texFire[i] = new BitmapData(32, 32, true, 0);
            shp.graphics.clear();
            shp.graphics.beginGradientFill(GradientType.RADIAL, [0x80c0f0, 0x80c0f0], [0,0.5-i*0.05], [0,255], mtx);
            shp.graphics.drawCircle(16,16,16);
            shp.graphics.endFill();
            texFire[i].draw(shp);
        }
        mdlFire = new Model(Vector.<Number>([-1,-1,0,-1,1,0,1,-1,0,1,1,0]), Vector.<Number>([0,0,0,0,1,0,1,0,0,1,1,0]));
        mdlFire.face(0,1,2).face(3,2,1);
    }
    
    private function _loadModel(vdata:String, idata:String, ...materials) : Model {
        var i:int, ui:uint, model:Model = new Model(null, null, Vector.<Material>(materials));
        for (i=0; i<vdata.length; i+=5) {
            _dec.decode(vdata.substr(i, 5) + "A==");
            ui = (_dec.toByteArray().readUnsignedInt())>>2;
            model.vertices.push(((ui&1023)-512)*SCALE, (((ui>>10)&1023)-512)*SCALE, (((ui>>20)&1023)-512)*SCALE);
        }
        for (i=0; i<idata.length; i+=5) {
            _dec.decode(idata.substr(i, 5) + "A==");
            ui = (_dec.toByteArray().readUnsignedInt())>>2;
            model.face(ui&255, (ui>>8)&255, (ui>>16)&255, (ui>>24)&63);
        }
        return model;
    }
}

class KeyMapper {
    public  var keys:uint = 0;
    private var _map:Vector.<int> = new Vector.<int>(256, true);
    
    function KeyMapper(stage:Stage) : void { 
        for (var i:int=0; i<128; i++) _map[i]=32;
        stage.addEventListener("keyDown", function(e:KeyboardEvent) : void {
            keys |= 1<<_map[e.keyCode];
        });
        stage.addEventListener("keyUp",   function(e:KeyboardEvent) : void {
            keys &= ~(1<<_map[e.keyCode]);
        });
    }
    
    public function map(bit:int, ...args) : KeyMapper {
        for (var i:int=0; i<args.length; i++) _map[args[i]] = bit;
        return this;
    }
}

// Background
//----------------------------------------------------------------------------------------------------
class Sky extends Shape {
    // This color gradation is refered from nemu90kWw's 水平線
    // http://wonderfl.kayac.com/code/2b527a2efe155b7f69330822a3c7f7733ab6ea7e
    public var gradation:* = {
        color:[0x103860, 0x0070A8, 0x60B0E0, 0xc0e6F0, 0xd0f0f0, 0x66ddff], 
        alpha:[100, 100, 100, 100, 100, 0], ratio:[0, 128, 192, 216, 224, 255]
    };
    function Sky() {
        var mat:Matrix = new Matrix();
        mat.createGradientBox(700, 380, Math.PI/2);
        graphics.beginGradientFill("linear", gradation.color, gradation.alpha, gradation.ratio, mat);
        graphics.drawRect(0, 0, 700, 380);
        graphics.endFill();
    }
}

class BumpTexture extends Bitmap {
    protected var vel_factor:Number = 1.0;
    protected function get gradation():* {
        return null;
    }

    public var pixels:BitmapData, texture:BitmapData, rect:Rectangle;
    function BumpTexture(w:int, h:int, use_alpha:Boolean = false, octv:int = 10) {
        texture = new BitmapData(w*2, h*2, use_alpha, 0);
        pixels = new BitmapData(w, h, use_alpha, 0);
        rect = new Rectangle(0, 0, w, h);
        super(pixels);
        
        // height map
        var hmap:BitmapData = new BitmapData(w, h, false, 0);
        hmap.perlinNoise(w*0.5, h*0.5, octv, Math.random()*0xffffffff, true, false, 0, true);
        hmap.colorTransform(hmap.rect, new ColorTransform(1.5, 1.5, 1.5, 1, -64, -64, -64, 0));
        
        // texture
        var mapR:Array=new Array(256), mapG:Array=new Array(256), mapB:Array=new Array(256), mapA:Array=new Array(256);
        var gmap:BitmapData = new BitmapData(256,1,use_alpha,0), render:Shape = new Shape(), mat:Matrix = new Matrix();
        mat.createGradientBox(256,1,0,0,0);
        render.graphics.clear();
        render.graphics.beginGradientFill("linear", gradation.color, gradation.alpha, gradation.ratio, mat);
        render.graphics.drawRect(0,0,256,1);
        render.graphics.endFill();
        gmap.draw(render);
        for (var i:int=0; i<256; i++) {
            var col:uint = gmap.getPixel32(i, 0);
            mapR[i] = col & 0xff0000;
            mapG[i] = col & 0x00ff00;
            mapB[i] = col & 0xff0000ff;
            mapA[i] = 0;
        }
        gmap.dispose();
        mat.identity();
        texture.paletteMap(hmap, hmap.rect, hmap.rect.topLeft, mapR, mapG, mapB, use_alpha ? mapA : null);

        if (!use_alpha) {
            // shading
            var smap:BitmapData = new BitmapData(w, h, false, 0);
            smap.applyFilter(hmap, hmap.rect, hmap.rect.topLeft, new ConvolutionFilter(3,3,[-1,-1,0,-1,0,1,0,1,1],1,0,true,true));
            texture.draw(smap, null, new ColorTransform(3, 3, 3, 1, 130, 140, 160, 0), "multiply");
        }        

        // copy 2x2
        pt.x = w; pt.y = h; texture.copyPixels(texture, hmap.rect, pt);
        pt.x = 0; pt.y = h; texture.copyPixels(texture, hmap.rect, pt);
        pt.x = w; pt.y = 0; texture.copyPixels(texture, hmap.rect, pt);
        pt.x = 0; pt.y = 0;
    }
    
    private var pt:Point = new Point();
    public function update() : void {
        rect.x = (int(rect.x-_globalVel.x)) & (pixels.width-1);
        rect.y = (int(rect.y-_globalVel.z*vel_factor)) & (pixels.height-1);
        pixels.copyPixels(texture, rect, pt);
    }
}

class Landscape extends BumpTexture {
    private var kGradation:* = {
        // This color gradation is refered from psyrak's BumpyPlanet
        // http://wonderfl.kayac.com/code/d79cd85845773958620f42cb3e6cb363c2020c73
        color:[0x203050, 0x226699, 0xcc9933, 0x337722, 0x55bb33, 0x886633, 0xffffff, 0x886633], 
        alpha:[100     , 100     , 100     , 100     , 100     , 100     , 100     , 100     ],
        ratio:[0       , 96      , 96      , 100     , 128     , 168     , 192     , 255     ]
    };

    function Landscape(w:int, h:int) {
        super(w, h);
    }

    protected override function get gradation():* {
        return kGradation;
    }
}


class Clouds extends BumpTexture {
    private var kGradation:* = {
        color:[0xffffff, 0xaaaaaa, 0x666666], 
        alpha:[0.9, 0.3, 0], ratio:[0, 50, 160]
    };

    function Clouds(w:int, h:int) {
        super(w, h, true, 27);
        vel_factor = 1.5;
    }

    protected override function get gradation():* {
        return kGradation;
    }
} 



// Actor
//----------------------------------------------------------------------------------------------------
class ActorManager {
    private var _actorClass:Class, _evaluator:ActorManager, _freeList:Actor, _activeList:Actor;
    
    public function get isEmpty() : Boolean { return (_activeList.next==_activeList); }
    
    function ActorManager(actorClass:Class, evaluator:ActorManager=null) {
        _actorClass = actorClass;
        _evaluator = evaluator;
        _freeList = new Actor(this);
        _activeList = new Actor(this);
    }
    
    public function alloc() : Actor {
        var actor:Actor = _freeList.pop() || new _actorClass(this);
        _activeList.push(actor);
        return actor;
    }
    
    public function free(act:Actor) : Actor {
        var next:Actor = act.next;
        act.prodNum++;
        act.remove();
        _freeList.push(act);
        return next;
    }
    
    public function updateAll(_evaluator:ActorManager) : void {
        for (var act:Actor=_activeList.next; act!=_activeList; act=act.update(_evaluator));
    }
    
    public function renderAll() : void {
        for (var act:Actor=_activeList.next; act!=_activeList; act=act.render());
    }
    
    public function freeAll() : void {
        for (var act:Actor=_activeList.next; act!=_activeList; act=free(act));
    }
}

class Actor {
    public var p:Vector3D=new Vector3D(), v:Vector3D=new Vector3D(), a:Vector3D=new Vector3D(), ac:int;
    public var childManager:ActorManager, model:Model, prodNum:int;
    protected var _manager:ActorManager;
    
    function Actor(manager:ActorManager) {
        _manager = manager;
        prev = this;
        next = this;
    }
    
    public function init(x:Number, y:Number, z:Number, model:Model) : Actor {
        pos(x, y, z).vel(0, 0, 0).acc(0, 0, 0, 0);
        this.model = model;
        return this;
    }
    
    public function pos(x:Number, y:Number, z:Number) : Actor {
        p.x = x;
        p.y = y;
        p.z = z;
        return this;
    }
    
    public function vel(vx:Number, vy:Number, vz:Number, term:int=0) : Actor {
        if (term == 0) {
            v.x = vx;
            v.y = vy;
            v.z = vz;
        } else {
            var n:Number = 1 / term;
            acc((vx-v.x)*n, (vy-v.y)*n, (vz-v.z)*n, term);
        }
        return this;
    }
    
    public function acc(ax:Number, ay:Number, az:Number, ac:int) : Actor {
        a.x = ax;
        a.y = ay;
        a.z = az;
        this.ac = ac;
        return this;
    }
    
    public function update(eval:ActorManager) : Actor {
        p.x += v.x + a.x * 0.5;
        p.y += v.y + a.y * 0.5;
        p.z += v.z + a.z * 0.5;
        v.x += a.x;
        v.y += a.y;
        v.z += a.z;
        if (--ac == 0) { a.x=0; a.y=0; a.z=0; }
        if (p.z<0) return _manager.free(this);
        return next;
    }
    
    public function render() : Actor { return next; }
    public function kill() : void { _manager.free(this); }
    
    public var prev:Actor, next:Actor;
    public function push(act:Actor) : void { act.next=this; act.prev=prev; prev.next=act; prev=act; }
    public function pop() : Actor { return (prev==this) ? null : prev.remove(); }
    public function remove() : Actor { prev.next=next; next.prev=prev; return this; }
}

class Player extends Actor {
    private var afterBurner:Boolean = false;
    
    function Player(manager:ActorManager) { super(manager); }
    
    override public function update(eval:ActorManager) : Actor {
        // move
        var inkey:uint = _key.keys;
        a.x = ((inkey & 1) - ((inkey & 4)>>2))*1.2;
        a.y = (((inkey & 2)>>1) - ((inkey & 8)>>3))*0.8;
        v.x = v.x * 0.8 - p.x*0.05;
        v.y = v.y * 0.8 - p.y*0.05;
        afterBurner = Boolean(inkey & 32);
        return super.update(eval);
    }
    
    override public function render() : Actor {
        _shape3d.pushMatrix();
        _matrix.identity();
        _matrix.prependTranslation(p.x, p.y+10, p.z);
        _matrix.prependRotation(v.x*6-_roll*0.5, Vector3D.Z_AXIS);
        _matrix.prependRotation(v.x*3, Vector3D.Y_AXIS);
        _matrix.prependRotation(v.y*-4, Vector3D.X_AXIS);
        _shape3d.matrix.prepend(_matrix);
        _shape3d.project(model);
        _shape3d.render(model, _light);
        _screen.draw(_shape3d, _mat2d);
        var scale:Number = (afterBurner) ? 1.7 : 1.5,
            length:Number = (afterBurner) ? 1.5 : 1.0,
            rand:Number;
        for (var i:int=0; i<8; i++) {
            rand = scale*(0.9+Math.random()*0.1);
            _matrix.identity();
            _matrix.prependTranslation(-4.8, -0.9, -18.1-i*length);
            _matrix.prependScale(rand, rand, 1);
            _shape3d.pushMatrix();
            _shape3d.matrix.prepend(_matrix);
            _shape3d.project(_modelManager.mdlFire).renderTex(_modelManager.mdlFire, _modelManager.texFire[i]);
            _screen.draw(_shape3d, _mat2d, null, "add");
            _shape3d.popMatrix();
            _matrix.identity();
            _matrix.prependTranslation(4.8, -0.9, -18.1-i*length);
            _matrix.prependScale(rand, rand, 1);
            _shape3d.pushMatrix();
            _shape3d.matrix.prepend(_matrix);
            _shape3d.project(_modelManager.mdlFire).renderTex(_modelManager.mdlFire, _modelManager.texFire[i]);
            _screen.draw(_shape3d, _mat2d, null, "add");
            _shape3d.popMatrix();
        }
        _shape3d.popMatrix();
        return next;
    }
}

// 3D Render
//----------------------------------------------------------------------------------------------------
class Shape3D extends Shape {
    public var matrix:Matrix3D;
    
    private var _faceProjected:Vector.<Face> = new Vector.<Face>();
    private var _vertexOnWorld:Vector.<Number> = new Vector.<Number>();
    private var _vout:Vector.<Number> = new Vector.<Number>();
    private var _projectionMatrix:Matrix3D;
    private var _matrixStac:Vector.<Matrix3D> = new Vector.<Matrix3D>();
    private var _commands:Vector.<int> = Vector.<int>([1,2,2]);
    private var _data:Vector.<Number> = new Vector.<Number>(6, true);
    private var _focus:Number;
    
    function Shape3D(focus:Number=300) {
        var projector:PerspectiveProjection = new PerspectiveProjection()
        _focus = focus;
        projector.focalLength = focus;
        _projectionMatrix = projector.toMatrix3D();
        matrix = new Matrix3D();
        _matrixStac.length = 1;
        _matrixStac[0] = matrix;
    }

    public function clearMatrix() : Shape3D { 
        matrix = _matrixStac[0];
        _matrixStac.length = 1;
        return this;
    }
    
    public function pushMatrix() : Shape3D {
        _matrixStac.push(matrix.clone());
        return this;
    }
    
    public function popMatrix() : Shape3D {
        if (_matrixStac.length == 1) return this;
        matrix = _matrixStac.pop();
        return this;
    }
    
    public function project(model:Model) : Shape3D {
        var i0x3:int, i1x3:int, i2x3:int, x01:Number, x02:Number, y01:Number, y02:Number, z01:Number, z02:Number,
            viewx:Number, viewy:Number, viewz:Number;
        matrix.transformVectors(model.vertices, _vertexOnWorld);
        _faceProjected.length = 0;
        var vertices:Vector.<Number> = _vertexOnWorld;
        for each (var face:Face in model.faces) {
            i0x3 = (face.i0<<1) + face.i0;
            i1x3 = (face.i1<<1) + face.i1;
            i2x3 = (face.i2<<1) + face.i2;
            face.x = (vertices[i0x3] + vertices[i1x3] + vertices[i2x3]) * 0.333333333333;
            x01 = vertices[i1x3] - vertices[i0x3];
            x02 = vertices[i2x3] - vertices[i0x3];
            i0x3++; i1x3++; i2x3++;
            face.y = (vertices[i0x3] + vertices[i1x3] + vertices[i2x3]) * 0.333333333333;
            y01 = vertices[i1x3] - vertices[i0x3];
            y02 = vertices[i2x3] - vertices[i0x3];
            i0x3++; i1x3++; i2x3++;
            face.z = (vertices[i0x3] + vertices[i1x3] + vertices[i2x3]) * 0.333333333333;
            z01 = vertices[i1x3] - vertices[i0x3];
            z02 = vertices[i2x3] - vertices[i0x3];
            face.normal.z = x02*y01 - x01*y02;
            face.normal.x = y02*z01 - y01*z02;
            face.normal.y = z02*x01 - z01*x02;
            if (face.x * face.normal.x + face.y * face.normal.y + face.z * face.normal.z <= 0) {
                face.normal.normalize();
                _faceProjected.push(face);
            }
        }
        _faceProjected.sort(function(f1:Face, f2:Face) : Number { return f2.z - f1.z; });
        return this;
    }

    public function render(model:Model, light:Light) : Shape3D {
        var idx:int, mat:Material;
        Utils3D.projectVectors(_projectionMatrix, _vertexOnWorld, _vout, model.texCoord);
        graphics.clear();
        for each (var face:Face in _faceProjected) {
            idx = face.i0<<1;
            _data[0] = _vout[idx]; idx++;
            _data[1] = _vout[idx]; 
            idx = face.i1<<1;
            _data[2] = _vout[idx]; idx++;
            _data[3] = _vout[idx]; 
            idx = face.i2<<1;
            _data[4] = _vout[idx]; idx++;
            _data[5] = _vout[idx]; 
            mat = model.materials[face.mat];
            graphics.beginFill(mat.getColor(light, face.normal), mat.alpha);
            graphics.drawPath(_commands, _data);
            graphics.endFill();
        }
        return this;
    }
    
    public function renderTex(model:Model, tex:BitmapData) : Shape3D {
        var idx:int, mat:Material;
        Utils3D.projectVectors(_projectionMatrix, _vertexOnWorld, _vout, model.texCoord);
        graphics.clear();
        graphics.beginBitmapFill(tex, null, false, true);
        graphics.drawTriangles(_vout, model.indices, model.texCoord);
        graphics.endFill();
        return this;
    }
}

class Face {
    public var i0:int, i1:int, i2:int, mat:int, x:Number, y:Number, z:Number, normal:Vector3D = new Vector3D();

    // Factory
    static private var _freeList:Vector.<Face> = new Vector.<Face>();
    static public function alloc() : Face { return _freeList.pop() || new Face(); }
    static public function free(face:Face) : void { _freeList.push(face); }
}

class Model {
    public var materials:Vector.<Material>;
    public var vertices:Vector.<Number>;
    public var texCoord:Vector.<Number>;
    public var faces:Vector.<Face> = new Vector.<Face>();
    private var _indices:Vector.<int> = new Vector.<int>();

    function Model(vertices:Vector.<Number>=null, texCoord:Vector.<Number>=null, materials:Vector.<Material>=null) {
        this.vertices = vertices || new Vector.<Number>();
        this.texCoord = texCoord || new Vector.<Number>();
        this.materials = materials || Vector.<Material>([new Material()]);
    }
    
    public function clear() : Model {
        for each (var face:Face in faces) Face.free(face);
        faces.length = 0;
        return this;
    }
    
    public function face(i0:int, i1:int, i2:int, mat:int=0) : Model {
        var face:Face = Face.alloc();
        face.i0 = i0;
        face.i1 = i1;
        face.i2 = i2;
        face.mat = mat;
        faces.push(face);
        return this;
    }
    
    public function get indices() : Vector.<int> {
        _indices.length = 0;
        for each (var face:Face in faces) _indices.push(face.i0, face.i1, face.i2);
        return _indices;
    }
}

class Light {
    private var _direction:Vector3D = new Vector3D();
    private var _halfVector:Vector3D = new Vector3D();
    public function get direction() : Vector3D { return _direction; }
    public function get halfVector() : Vector3D { return _halfVector; }
    
    function Light(x:Number=1, y:Number=1, z:Number=1) { setPosition(x, y, z); }
    
    public function setPosition(x:Number, y:Number, z:Number) : void {
        _direction.x = x;
        _direction.y = y;
        _direction.z = z; 
        _direction.normalize();
        _halfVector.x = _direction.x;
        _halfVector.y = _direction.y;
        _halfVector.z = _direction.z + 1; 
        _halfVector.normalize();
    }
}

class Material {
    public var colorTable:BitmapData = new BitmapData(256,256,false);
    public var alpha:Number = 1;
    private var _nega_filter:int = 0;
    
    function Material(color:uint=0xc0c0c0, alpha:Number=1.0) { setColor(color, alpha); }
    
    public function setColor(color:uint, alpha_:Number= 1.0, 
                             amb:int=64, dif:int=192, spc:int=0, pow:Number=8, emi:int=0, doubleSided:Boolean=false) : Material
    {
        var i:int, r:int, c:int,
            lightTable:BitmapData = new BitmapData(256, 256, false),
            rct:Rectangle = new Rectangle();
        
        // base color
        alpha = alpha_;
        colorTable.fillRect(colorTable.rect, color);

        // ambient/diffusion/emittance
        var ea:Number = (256-emi)*0.00390625,
            eb:Number = emi*0.5;
        r = dif - amb;
        rct.width=1; rct.height=256; rct.y=0;
        for (i=0; i<256; ++i) {
            rct.x = i;
            lightTable.fillRect(rct, (((i*r)>>8)+amb)*0x10101);
        }
        colorTable.draw(lightTable, null, new ColorTransform(ea,ea,ea,1,eb,eb,eb,0), BlendMode.HARDLIGHT);
        
        // specular/power
        if (spc > 0) {
            rct.width=256; rct.height=1; rct.x=0;
            for (i=0; i<256; ++i) {
                rct.y = i;
                c = int(Math.pow(i*0.0039215686, pow)*spc);
                lightTable.fillRect(rct, ((c<255)?c:255)*0x10101);
            }
            colorTable.draw(lightTable, null, null, BlendMode.ADD);
        }
        lightTable.dispose();

        // double sided
        _nega_filter = (doubleSided) ? -1 : 0;
        
        return this;
    }
    
    public function getColor(light:Light, normal:Vector3D) : uint {
        var ln:int, hn:int, sign:int;
        ln = int((light.direction.x * normal.x + light.direction.y * normal.y + light.direction.z * normal.z)*255);
        sign = ((ln & 0x80000000)>>31);
        ln = (ln ^ sign) & ((~sign) | _nega_filter);
        hn = int((light.halfVector.x * normal.x + light.halfVector.y * normal.y + light.halfVector.z * normal.z)*255);
        sign = ((hn & 0x80000000)>>31);
        hn = (hn ^ sign) & ((~sign) | _nega_filter);
        return colorTable.getPixel(ln, hn);
    }
    
    static public function calculateTexCoord(texCoord:Point, light:Light, normal:Vector3D, doubleSided:Boolean=false) : void {
        texCoord.x = light.direction.x * normal.x + light.direction.y * normal.y + light.direction.z * normal.z;
        if (texCoord.x < 0) texCoord.x = (doubleSided) ? -texCoord.x : 0;
        texCoord.y = light.halfVector.x * normal.x + light.halfVector.y * normal.y + light.halfVector.z * normal.z;
        if (texCoord.y < 0) texCoord.y = (doubleSided) ? -texCoord.y : 0;
    }
}